Thursday, March 26, 2015

論理削除はなぜ「筋が悪い」か

論理削除が云々について - mike-neckのブログ」を読んで。

データベース設計において、「テーブルの書き換えをするな、immutableなマスタと更新ログによって全てを構成しろ」というこの記事の主張はモデリング論として全く正しい

だが、残念なことに、ディスクやメモリが貴重な資源だった時代の技術であるRDBは、そのようなモデリングに基づいて設計されたデータベースには必ずしも適していない

第一の問題は、RDBに対してなされる様々な「更新」(トランザクション)は不定形(どのテーブルをどのように修正するかはアプリケーション依存)だという点。不定形な「更新」を時系列にそってRDBに記録していくのは、設計と並走性の点において困難あるいは煩雑なコーディングが必要になる(というか、そのような「イベント」による「変化」はREDOログに書き、その更新された「状態」をテーブルに反映していくというのがRDBの「一般的な使われ方」)。

第二の問題は、ほとんどのデータベースアクセスは、更新が蓄積された結果である「現在の状態」を問い合わせるものになるが、immutableなマスタと更新ログによって構成されるデータベース設計においては、そのような問い合わせに効率的に応答するのが難しいという点である。

従って、現実的なデータベース設計においては、多くのテーブルが「現在の状態」をもつ、immutableなマスタと更新ログから合成可能な「現在の状態を表現するビュー」を実体化したものとして表現されることになる。

ふりかえって、論理削除とはなにか。「現在の状態を表現する実体化ビュー」に、過去の状態(かつて存在したデータであることを意味する「削除済」)をフラグとして付与したものである。

「現在の状態を表現する」ことを前提にしたビューであるところのテーブルに、過去の状態の一部だけを表現するフラグを追加するのが「筋が悪い」設計だというのは明らかだ。

過去の状態を参照すべき要件があるなら、そのテーブルが表現する情報についてはimmutableなマスタと更新ログを用意し、その射影として表現されるビューとして「現在の状態」を実現すべきである。そのようなビューは自動生成できるならしても良いし、手動で更新するなら、ストアドを使うか、あるいはアプリケーションのデータベースアクセス層において手続きをまとめる方法を考えるべきだろう。

ただ、immutableなマスタ、更新ログと「現時点のビュー」の3テーブルを準備・運用するというのはそれなりにコストがかかるので、筋が悪い「削除フラグ」(より一般化すれば状態フラグ)を使うかどうかはケースバイケースで判断するのもひとつの見識である。


まとめ:
  • データベース設計にあたっては、テーブルを「現在の状態」を表現するものとして設計するか、それとも「immutableなマスタと更新ログ、および現時点の状態を表現するビュー」として設計するかという、2つの選択肢がある
  • 前者は簡潔で性能が高くなるが、過去の情報を参照できないという問題がある
  • 「削除フラグ」というのは、「現在の状態」を表現するテーブルに過去の状態の一部を表現する機能を場当たり的に足したもの(なので筋が悪い)
  • それよりも、過去の情報を参照する要件がある場合(もしくはそのような要件が発生すると想定される場合)は、テーブル単位で「immutableなマスタと更新ログ」という設計を採用しつつ、現在の情報をビューとして表現することを考えた方がよい

こういった点を理解・検討した上で、「それでも削除フラグの方が楽だろう」という判断を下しているなら良いと思います。


追記: Re: 論理削除はなぜ「筋が悪い」か - Blog by Sadayuki Furuhashiで挙げられていた疑問点について
効率的にSELECTや更新ができるスキーマを作ろうとすると、VIEWやFUNCTIONなど、側に実装するコードが増えてくる。それらのコードは、上記のようにDB側に実装しても良い(するべき)だろうか?それともアプリケーションに実装するべきだろうか?
アプリケーション要件によるでしょう。

が、アプリケーション要件に関わらず正しく動作する方法が何かという問に対しては、RDB側でトリガ(あるいはストアド)として実装するのが安全だというのが答えになるでしょう。また、アプリケーションが操作可能な処理は「immutableなマスタ」と更新ログテーブルへの追記のみとし、両者への操作から「現在の状態を表現するビュー」を生成すべきであって、「現在の状態を表現するビュー」をアプリケーションが操作し、その変更内容をトリガでログに出力するという方式は避けるべきという話になるかと思います。

なぜか。そうしないと分離レベルや主キーの発番処理に起因する込み入った条件が色々…続きはお近くのDBAにご相談ください。
テーブルのスキーマは停止時間なしで変更する手法をいくつか思いつくが(PostgreSQLなら)、上記のレコードを削除する操作などはアプリケーションの変更を伴うので難しい(アプリケーションとDBのスキーマをアトミックに変更できない)
カラムのアクセス権は無停止で変更できませんか? つまり、アプリケーションを拡張した結果として削除フラグの存在が冗長になったのであれば、そのフラグの更新をストアドあるいはトリガで行われるように変更し、書き込み権限をドロップすればいいでしょう。

こんな感じかと思います。

Tuesday, March 24, 2015

released Server::Starter 0.21; no more external dependencies, easy to fat-pack

I am happy to announce the release of Server-Starter version 0.21.

In the release I have removed dependencies to perl modules not in core (e.g. Proc::Wait3, List::MoreUtils, Scope::Guard).

Without dependencies on XS modules it would now be easier to install the module on any system.

The change also opens the possibility to fat-pack the `start-server` script; it can be accomplished by just running the fatpack-simple script.
$ fatpack-simple start_server
-> perl-strip Server/Starter.pm
-> perl-strip Server/Starter/Guard.pm
-> Successfully created start_server.fatpack
$ 

Have fun!

Thursday, March 19, 2015

「技術的負債」は避けるべき? - 割引率を使って考えてみた

「技術的負債」をコントロールする定量評価手法への期待 からの続きです。

ソフトウェアサービス企業における技術責任者の最も重要な仕事のひとつが、エンジニアリングの効率化です。そのためには、サービスの初期開発コストだけでなく、運用コストを織り込んだ上で正しい技術的判断を行っていく必要があります。

「技術的負債」という言葉は、この運用コスト最適化の重要性を指摘する上で、とてもキャッチーなフレーズだと考えられます。しかし、「技術的負債」を産まないように、あるいは負債を早めに返していこうとすると、開発工数が大きくなってしまうという問題もあります。

初期開発コストと運用コストのバランス注1を、どのようにとっていけば良いのでしょう?

同等の機能を提供する「ソフトA」と「ソフトB」を考えてみます。ソフトAは、初期開発工数が6だが、2年目以降の維持工数が毎年4かかるとします注2。ソフトBは、初期開発工数が10で、維持工数が毎年1かかるとしましょう。ソフトAはPHPファイル1枚に必要な処理をひたすら羅列したクソコード、ソフトBはMVCを念頭にレイヤ分割された、きれいなコードなんてイメージすると、こんな工数感覚になってもおかしくないかと思います。

ソフトAとソフトB、どちらを開発すべきかという問いに対する答えが、そのソフトを使い続ける(改善し続ける)期間によって変わることは、表1を見ずとも明らかだと思います。では、その期間を合理的に見積もる手法が必要になります。
表1. 初期開発と維持工数
年度ソフトAソフトB
工数(単年)工数(累計)工数(単年)工数(累計)
1661010
2410111
3414112
4418113
5422114


ここで登場するのが「割引率」という概念です。

割引率は一般に、将来の発生する可能性のある利益を現在の価値に変換して評価するために用いられる概念ですが、同様に、将来発生する可能性のある費用を評価する目的でも使うことができます注3

たとえば、新規サービスの場合を考えると、2年目も継続してサービスを改善しつづける可能性は決して高くないと思います(仮に改善を続ける可能性が50%だとすると、割引率(1÷改善継続可能性−1)は100%になります)注4。n年後に改善を続けている可能性が0.5nであるなら、現在価値に修正した累計コストは以下のような表になります。
表2. 初期開発と維持工数(割引率100%)
年度ソフトAソフトB
工数(単年)工数(累計)工数(単年)工数(累計)
1661010
2280.510.5
3190.2510.75
40.59.50.12510.875
50.259.750.062510.9375

表からも明らかなように、ソフトAの累計工数がソフトBを上回ることはありません(厳密に言うと、ソフトAの割引後の累計工数が10に収束するのに対し、ソフトBの工数は11に収束する)。この結果はつまり、仮に翌年も継続してサービスを改善しつづける可能性が50%なら、ソフトBのアプローチよりも、ソフトAのアプローチのほうがコスト効率的に優れているということを示しています。

逆に、上場企業の事業の基盤となっているようなソフトウェアについては、割引率が10%を切ることがあってもおかしくないはずですが、仮にn年後に改善を続けている可能性が0.9n(割引率11%)であるなら、現在価値に修正した累計コストは下表のようになり、今度は逆に、ソフトBのアプローチの方が優れているということになります。
表3. 初期開発と維持工数(割引率11%)
年度ソフトAソフトB
工数(単年)工数(累計)工数(単年)工数(累計)
1661010
23.69.60.910.9
33.2412.840.8111.71
42.91615.7560.72912.439
52.624418.38040.656113.0951

以上のように、技術選択における正解は割引率の高低によって大きく左右されます

また、割引率は事業の形態のみならず、ソフトウェアの種類によっても大きく異なります。私個人の経験を振り返っても、サーバサイドの管理用ミドルウェア → データベース等ハードウェアの能力に影響をうけるサーバサイドミドルウェア → クライアントサイドのミドルウェア → アプリケーションの順で割引率が高くなっていく印象があります。

適切な値がいくらか、ということは俄には答えにくい問題かもしれませんが、「割引率」という概念自体は経営層にとっては常識レベルのものなので、経営層と事業継続にかかる感覚値を普段から共有し、技術判断にフィードバックしていくということが、技術責任者に求められる役割なのかな、と思う次第です。

参考リンク:
とあるスタートアップを抜け、CTOを辞めた話。 - nobkzのブログ
「「技術的負債」を問いなおす」というタイトルでJAWS DAYS 2014で話してきた #jawsdays - delirious thoughts

注1: 企業の情報システム投資では、初期コストに保守等のコストを加えた「総所有コスト(TCO)」の極小化を目標とするのが有効なアプローチだと考えられています。
注2: ソフトウェアが陳腐化する外的要因は周辺技術の進歩です。その速度は一定であると置くのが妥当ですから、ソフトウェアの保守コストは毎年一定であると考えるのが正しいことになります。「ソフトウェアの保守が時間とともに難しくなる」と感じられるとしても、それは過去の保守投資が過小であった結果だと解釈すれば良い話です。モデルを無駄に複雑化する理由はありません。
注3: つまり、事前に使用年数を見積もることが困難なシステムがあるなら、通常のTCOではなく、保守を続ける年数を確率分布として修正したTCOを用いてコストを予測すれば良いということです。
注4: 割引率は毎年一定になるとは限らないでしょう。たとえば2年間で資金を使い切る前提でサービスを提供している場合には、2年間は割引率が低く、3年目にがくっと上がる形(失敗するサービスが過半であるという前提を置くなら、それこそ100%を超える値)になると思います。

Friday, March 6, 2015

H2O version 1.1.0 released with bug fixes and enhancements in proxy etc.

This is a release announcement of H2O version 1.1.0, the optimized HTTP server with support for HTTP/1 and HTTP/2.

In 1.1.0 we have gone through a major refactor of the proxy implementation, and added three notable features to the H2O standalone server.

Support for x-reproxy-url header #197

With help from @lestrrat, H2O now recognizes the x-reproxy-url header sent by upstream servers, and if found, substitutes the response with the response obtained from the URL specified by the header.

When the feature is activated (by adding the line reproxy: on to the configuration file), any HTTP URL can be served by using the x-reproxy-url header.

There is no support for x-reproxy-file header. Instead, if the authority of the URL matches one of the host entries of the configuration file, then the reproxied request will be handled internally by the handlers of H2O.

Load distribution among upstream servers #208

To resolve the address of the upstream server, H2O calls getaddrinfo each time it needs to connect to the servers. As of version 1.1.0 the call is executed asynchronously (by using dedicated threads for the task), and if multiple entries are returned by the name resolver (e.g. DNS, /etc/hosts, et. al.) then H2O connects to one of them selected at random.

The change neatly constitute as a basis for load balancing. By adjusting the name resolver, you can at any time add or remove an upstream server, or change the selection weight between the servers by using sophisticated DNS servers like
MyDNS
.

This is only a first step. We plan to polish up the feature for better load balancing.

Directives for tweaking the response headers #204

Following directives have been introduced for mangling the response headers, modeled after those provided by the Apache HTTP server: header.add, header.append, header.merge, header.set, header.setifempty, header.unset.


For more information, a full list of changes with references can be found in the version 1.1.0 changelog; list of configuration directives can be found by running h2o --help.

Thursday, February 19, 2015

H2O, the new HTTP server goes version 1.0.0 as HTTP/2 gets finalized

I am happy to announce the release of H2O version 1.0.0 on the same day HTTP/2 gets finalized. The momentum for HTTP/2 is building up fast.

According to mnot’s blog: HTTP/2 is Done posted today,
The IESG has formally approved the HTTP/2 and HPACK specifications, and they’re on their way to the RFC Editor, where they’ll soon be assigned RFC numbers, go through some editorial processes, and be published.
Web browser developers have already implemented the protocol. Mozilla Firefox is already providing support for the HTTP/2 draft. Google has announced that they would turn on support for HTTP/2 on Chrome within weeks. Internet Explorer 11 on Windows 10 Technical Preview also speaks HTTP/2.

Considering the facts, it seemed that we'd better freeze the configuration directives of H2O now, so that people could rely on the software for serving HTTP/2 requests (note: the library API should still be considered unstable).

Features provided by H2O version 1.0.0 include the following; please refer to the README and `--help` for more information.

  • support for HTTP/1.x and HTTP/2
  • static file serving and reverse proxy
  • HTTP/2 server-push
  • excellent performance outperforming Nginx
  • graceful restart and self-upgrade via Server::Starter

Started last summer, H2O is still a very young project. We would never have advanced this fast without so much help from the community (the fact is clear especially regarding the support for HTTP/2 if we look at H2O issue #133 as an example). I would like to express my gratitude for their advises and suggestions.

We plan to continue improving H2O rapidly. The primary focus is on performance, ease-of-use, and flexible (even autonomous) reconfiguration that suites the cloud era.

Today, HTTP is facing challengers. With the rise of smartphone apps, it is no longer the only protocol that can be used. But wouldn't it be better if we could all continue using a single, well-known protocol a.k.a. HTTP?

Our goal is by providing an excellent implementation, to keep the protocol as the primary choice of the developers, and furthermore, to expand the adoption of HTTP even more than before.

Stay tuned!

Tuesday, February 17, 2015

Writing signal-aware waitpid in Perl

As I have talked in YAPC::Asia couple of years ago, the wait functions (e.g. wait, waitpid) of Perl do not return EINTR when receiving a signal.

This is a problem if you would want to wait for child processes until receiving a signal. Proc::Wait3 can be a solution, however the module may be hard to install as it is an XS module. It should also be noted that the module provides replacement for wait only; no workaround exists for waitpid.

So today I have scrubbed my head wondering if I could come up with a pure-perl solution, and, here it is. The Perl script below launches a worker process (that just sleeps), and waits for the process to complete, or until SIGTERM is being received.

use strict;
use warnings;
use Errno ();

our $got_sigterm = 0;
our $sighandler_should_die = 0;

# fork a child process that does the task
my $child_pid = fork;
die "fork failed:$!"
    unless defined $child_pid;
if ($child_pid == 0) {
    # in child process, do something...
    sleep 100;
    exit 0;
}

$SIG{TERM} = sub {
    $got_sigterm = 1;
    die "dying to exit from waitpid"
        if $sighandler_should_die;
};

warn "master process:$$, child process:$child_pid";

# parent process, wait for child exit or SIGTERM
while (! $got_sigterm) {
    if (my_waitpid($child_pid, 0) == $child_pid) {
        # exit the loop if child died
        warn "child process exitted";
        $child_pid = -1;
        last;
    }
}

if ($child_pid != -1) {
    warn "got SIGTERM, stopping the child";
    kill 'TERM', $child_pid;
    while (waitpid($child_pid, 0) != $child_pid) {
    }
}

sub my_waitpid {
    my @args = @_;
    local $@;
    my $ret = eval {
        local $sighandler_should_die = 1;
        die "exit from eval"
            if $got_sigterm;
        waitpid($args[0], $args[1]);
    };
    if ($@) {
        $ret = -1;
        $! = Errno::EINTR;
    }
    return $ret;
} 

The trick is that waitpid is surrounded by a eval within the my_waitpid function, and the signal handler calls die to exit the eval if the $sighandler_should_die flag is being set. It is also essential to check the $got_sigterm flag within the eval block after setting the $sighandler_should_die flag, since otherwise there would be a race condition.

By using these tricks it has now become possible to implement process managers in pure-perl!

Tuesday, February 10, 2015

[Ann] H2O version 0.9.2 released incl. support for HTTP2 server-push, state-of-art prioritization of streams

I am glad to announce the release of H2O version 0.9.2.

This is the third release of H2O, including a number of changes that can be found in the Changes file. And here, I am happy to mention that some of the changes were brought by other people than me, in fact five people have committed into improving the H2O since the last release, whose names can also be found at the top of the README.

Among the changes introduced in version 0.9.3 are improvements to the HTTP/2 protocol implementation.

HTTP/2 Server Push

As of version 0.9.2, H2O automatically pushes content using HTTP/2 server push when suggested by the upstream server using the link:rel=preload header. By using the feature, web applications can push resources mandatory for rendering the web pages on the client side, which results in faster perceived response time from the end-users viewpoint. In other words, web application developers are encouraged to list the files that block the rendering using the link: rel=preload header for optimum rendering speed.

Below is an example of a response sent by a web application running behind H2O. H2O recognizes the link header and starts to push the contents of /assets/main.css even before the client recognizes that the CSS is a blocker for rendering the webpage.
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Link: </assets/main.css> rel=preload


It is also worth noting that H2O is not alone in providing support for HTTP/2 server push. Following the discussion on an H2O issue discussing the topic, nghttp2 has added support for server-push using the same header as well. What's good here is that the developers are working together on HTTP/2 to provide a logical and a vendor-neutral way of providing access to the new technology; I am so happy to be part of the effort.

Improved Scheduler for HTTP/2 Streams

The HTTP/2 specification defines a somewhat complex logic for prioritizing the streams. In H2O version 0.9.3 we have polished up the scheduler implementation used for the purpose. H2O now is not only highly compliant to the specification implementing all the aspects of the specification, but also excels in performance as the internal code-paths are guaranteed to be O(1).

Implementation of the prioritization logic fully conforming to the specification is essential for a HTTP/2 server, since web browsers would be tuned against the specification. We may see unnecessary delays in page rendering speed if any error (or missing parts) exist within the server program. I am glad that H2O is unlikely to fall into such problem now that we have a complete implementation.


I am pleased that all the changes have been done within three weeks since last release. And my thank you goes to the contributors and the people who have gave us advises on improving the product.

Please stay tuned for the next release!