この文書は Perl.com: Buildling a Large-scale E-commerce Site with Apache and mod_perl を翻訳したものです。2001年に書かれた文書であり、Apache/mod_perl/CPAN モジュールなど現在の状況にはそぐわない部分もある可能性があります。



大規模なeコマースサイトを Apache と mod_perl で構築する

By Perrin Harkins (Translated by Tatsuhiko Miyagawa <miyagawa@cpan.org>)


よくある神話

大規模な eコマース ウェブサイトの構築となると、アドバイスはそこら中に あります。デベロッパーたちは、C++ や Java (このどちらかは好みで)で作ら れたサイトでなければ、大量のトラフィックを処理できない、と言ってくるこ とでしょう。アプリケーションサーバベンダーは、パッケージングされたオー ルインワンのソフトウェアが必要だ、と主張するでしょう。ハードウェアベン ダーは、大規模サイトの運営には最高級のメガマシンが必要だ、と言ってくる でしょう。本記事では、われわれがどのようにして、主にオープンソースのソ フトウェアや日用ハードウェアを使って大規模 eコマースサイトを構築したか を紹介します。われわれはこれを実現したのです、あなたにも、可能なことで す。


Perl はセーブする

Perl は長らくの間、CGI スクリプトを記述する際の言語として人気がありま した。Perl はラピッドな開発とフレキシビリティを同時に実現しています。 Programming Perl はいまも O'Reilly のトップセールスを記録し、コミュ ニティのサポートも豊冨です。しかし近頃、Perl は、いくつかの方面からの 攻撃にさらされてきました。シリアスな開発には動作が遅く、Perl で書かれ たコードはメンテナンスしにくい、と中傷する人々がいるのです。

Apache モジュールの mod_perl は、Perl のパフォーマンス情勢を劇的に 改善しました。Apache の中に Perl インタプリタを埋め込むことによって、 Java サーブレットと同様のパフォーマンスを得ることができ、大規模なサイ ト構築における優れた選択枝となります。Perl のオブジェクト指向機能を利 用し、基本的なコーディングルールを守ることによって、楽にメンテナンスで き、他の言語に劣らないコードを構築することができるのです。


アプリケーションサーバの撰択

Apache, mod_perl と CPAN (Comprehensive Perl Archive Network) で入手可 能なオープンソースコードを組み合わせれば、商用アプリケーションサーバと 同様な機能を利用可能です。

それに、商用プロダクトでは得ることができないものもあります。たとえば、 メーリングリストを通じて、デベロップメントチームに直接コンタクトをとっ たり、パッチを待つことなく自分自身で問題を修正することができます。さら に、システムの各パートはあなたの管理下におくことができ、making you limited only by your team's abilities.


ケーススタディ: eToys.com

われわれが1999年に eToys にやってきたとき、多くのインターネットのスター トアップ会社に参加したみなさんと、ほとんど同じ経験をしました。システム は MySQL と連係する CGI スクリプトをベースにしていました。スタティック なファイルの吐きだしとダイナミックなコンテンツ生成は同じマシンでリソー スを共有していました。CGI のコードはほとんどが Perl4 流のスタイルで書 かれており、モジュラー化されていませんでしたが、小規模のチームが短期間 でつくったのですから、驚くべきことではありませんでした。

われわれの主要なタスクは、クリスマス期に予測されるトラフィックに耐えう るようにシステムをスケールアップする方策を把握することでした。おもちゃ 産業は季節モノで、売上のピーク時と、そうでない時期の差が巨大です。サイ トは前回のクリスマスを耐え切ることができず、MySQL データベースではそれ 以上の規模は無理なようでした。

Oracle への移行はすでにおこなわれ、DBA チームがすでに配置されていまし た。ソフトウェアの再設計をしている時間はなかったため、クリスマスまでに、 パフォーマンスを改善させることはなんでも実行しなくてはなりませんでした。


Apache::PerlRun による救済

Apache::PerlRun は CGI から mod_perl への移行をスムーズにするた めのモジュールです。CGI環境をシミュレートし、mod_perl でコードを書 くことによるメリットの一部(すべてではない)を提供します。このモジュール を使ってデータベースの永続コネクションを Apache::DBI によって実現し、 クリスマスまでに mod_perl と Oracle への基本的な移行をクリスマスま でにすませることができました。そして、クリスマスラッシュに備えて新しい ハードウェアの準備もできました。

トラフィックのピークは8週間つづきました。そのほとんどの間、必死になっ ていろんなものを修正し、神経質になりながらなにか異常がおこらないかを待っ ていました。ですが、わたしたちはなんとかやりとげました。その間、以下の ような統計を得ることができました。

Media Metrix によれば、eToys は eBay, Amazon についで3番目にトラフィッ クの多い eコマースサイトでした。


新たなアーキテクチャの計画

2000年にむけて、再設計をしなければいけないのは明白でした。現状のシステ ムではすでに限界に達しており、先延ばしにしていた困難な問題にとりくむ必 要がありました。

新たなシステムの目標には、オフラインでのページ生成をやめることも含まれ ていました。旧システムでは各製品や製品カテゴリごとのHTMLページをバッチ ジョブで生成し、スタティックなファイルに吐きだしていました。このやり方 は、製品データベースが小規模であれば、スタティックファイルの方がパフォー マンスがよいため、効率的だったでしょう。しかし、その頃サイトに子供用ブッ クストアを追加しており、製品データベースの規模はすごい勢いで大きくなり、 すべてのページを生成するのに必要とされる時間が無視できなくっていました。 よって、顧客がほんとうに興味をもってクリックしたときにページを生成し、 かつ安定したパフォーマンスを維持するような戦略が必要でした。

データベーススキーマの作りなおしや、コードをモジュラー化して、お互いの コードに踏み込むことなく、開発作業をチーム間で共有できるようにする要望 もありました。新たなコードベースは、追加され続ける機能要望にも対応でき るような柔軟性も備えなければならなかったのです。

チームの全員が Perl のオブジェクト指向に豊富な経験を持っていたわけでは なかったため、Randal Schwartz や Damian Conway を招いてトレーニングセッ ションをおこなってもらいました。コーディング標準をいくつか作成し、デザ イン設計をおこない、システムを構築しました。


2000年のクリスマスを切り抜ける

キャパシティの予想では、ピークトラフィックの数字は、前年の3倍でした。 その数字でテストし、結果は大体以下のようになりました。

ソフトウェアは切りぬけることができました。ルータの1個はいかれてしまい ましたが。この年もまた、シーズンでは 3番目にトラフィックに多いe-コマー スサイトとしてレイティングされました。


アーキテクチャ

このシステムのマシン戦略はとてもありふれたものです。安価なIntel ベース のサーバ群、そしてそのフロントにロードバランサを並べ、データベースサー バにコストをかけます。

多くの商用パッケージ同様、フロントエンドの Web サーバ(プロキシサーバと 呼びます)のシステムと、動的にコンテンツを生成するアプリケーションサー バは分離しました。プロキシサーバもアプリケーションサーバもf5 Networks の専用ハードウェアによってロードバランスします。それぞれのシステムの詳 細は以下で説明します。

プロキシとアプリケーションサーバには、mod_perl では典型的なプラット フォームである、Linux を撰択しました。Linux ではリモート管理が容易なた め、クラスタリングのアプローチが可能になりました。Linux はセキュリティ や自動ビルドの機能などにより、新たにサーバを追加するのが容易でした。

データベースサーバは IMB の NUMA-Q マシンで、DYNIX/ptx を走らせてい ます。


プロキシサーバ

プロキシサーバでは、mod_perl を組み込まずに、小さなバイナリの Apache を走らせています。この Apache には、いくつかのApache 標準モジュー ルと、セッションの Cookie を発行するカスタムバージョンの mod_session をインストールしてあります。プロセスサイズが小さいため、 1台のマシンで 400 個程度のApache プロセスを走らせることが可能です。こ れらのサーバでは、画像ファイルへのリクエストは自身で制御し、ページへの リクエストはアプリケーションサーバに転送します。アプリケーションサーバ とは通常のHTTPリクエストによって通信し、アプリサーバから、しかるべきヘッ ダが返ってきた際には、ページアウトプットをキャッシュします。キャッシュ されたページは共有された Network Appliance filer 上の NFS パーティショ ンに保存されます。キャッシュからページを返すのはスタティックファイルと 同様、とても 高速です。

このようにリバースプロキシをセットアップするのは、mod_perl を利用す る際に、通常推奨されるアプローチです。こうすれば、軽量のプロキシサーバ のプロセスによってコンテンツをクライアント(遅いコネクションの可能性が ある)に返すため、リソースを食う mod_perl を解放して、次のリクエスト に移ることができるためです。この設定がどのように役に立つかについての情 報は、mod_perl デベロッパーズガイド http://perl.apache.org/guide/ を見てください。


アプリケーションサーバ

アプリケーションサーバでは、mod_perl、そしてそれ以外にもいくつかの プロセスを走らせています。Berkeley DB を用いて Perl オブジェクトの ローカルキャッシュを持っています。Web アプリケーションはここで動作し、 HTML テンプレートファイルのような共有リソースは NetApp filer の NFS に マウントされています。このセットアップが相当重いため、これらのマシンは デュアルCPU に 1GB の RAM という豪勢なものになっています。


検索サーバ

さらにもう1つのサーバ群、検索専用のサーバがあります。検索が全体のトラ フィックの大きな割合を占めていたため、それ専用のリソースを用意し、アプ リケーションやデータベースの負荷を減らす価値がありました。

これらのサーバで動作させるソフトウェアは、社内で C++ を利用して開発し た、マルチスレッドのデーモンプロセスです。アプリケーションサーバは、 Perl モジュールを利用して検索サーバと通信します。検索デーモンは検索条 件のセットを受けとり、条件にマッチする製品のオブジェクトIDリストをソー トして返します。アプリケーションサーバは表示する製品のデータをデータベー スから引っ張ってきます。検索サーバは HTML やWeb インターフェースについ てはなにも関知しません。

このように、検索サーバで ID を検索し、次にオブジェクトデータを取得する アプローチは、パフォーマンスが良くないように見えますが、実際にはオブジェ クトデータはデータベースではなく、アプリケーション側のキャッシュから取 得されます。この設計では、データベースと検索サーバ間の重複データを最小 限に抑え、インデックスの再構築を容易にかつ高速におこなうことができます。 また、データベースからプロダクトオブジェクトを取得する Perl コードは、 どのように検索したかには関係ないため、そのまま再利用することができます。

検索デーモンは、標準的な逆引き単語リストのアプローチを使用しています。 インデックスは定期的に Oracle のデータから構築されます。もし Perl です べて実装するソリューションが良ければ、こうしたアプローチを実装したモジュー ルはCPAN にいくつかあります。Search::InvertedIndexDBIx::FullTextSearch などです。われわれが独自にこれを実装したのは、 この部分のパフォーマンス要件がシビアで、返り値のIDをソートする規則がか なり複雑であったためです。


ロードバランスとフェイルオーバー

クラスタ間でのロードバランスと、そのうちの1台かそれ以上のノードがダウ ンしていた場合のフォールトトレランスを実現するのには苦労しました。プロ キシサーバはランダム選択アルゴリズムによってバランスされています。ユー ザはリクエスト毎に異なるサーバに辿りつきます。これらのサーバはステート 情報をなにも保持していないため、目標は単に負荷を均一に分散させることに なります。

アプリケーションサーバは ``sticky'' なロードバランスをおこなっています。 つまり、一度あるユーザが特定のアプリケーションサーバにリクエストした場 合、そのユーザのセッション中の後続のリクエストはすべて同一のアプリサー バに転送されます。f5 ハードウェアは、ブラウザの cookie を利用してこれ を実現しています。

ロードバランサーは定期的にサービスのチェックをおこない、チェックに失敗 したサーバはローテーションから外します。サーバがこけた場合、そのマシン に割り当てられていたユーザはすべて別のマシンに移動されます。

アプリサーバが落ちた際にデータのロスが決して起こらないことを保証するた めに、データの更新はすべてデータベースに蓄積されます。結果として、ショッ ピングカートの中身のようなユーザデータは、アプリサーバのハードウェアの 悲劇的な障害のような場合でも、保護されます。こうしたことは、大規模な e コマースサイトでは必須です。

データベースは別のフェイルオーバシステムを持っていますが、ここでは立ち 入りません。ベンダーによって推奨されている標準的な方法にしたがっている のみです。


コード構造

コードは、もとは SmallTalk からはじまり、現在はよく Web アプリケーショ ンに適用される、Model-View-Controller パターンによって構造化されていま す。MVC パターンは、アプリケーションの責任を3つの異なるレイヤーに分割 する方法です。

Model レイヤーのクラスは、製品やユーザのような、ビジネス概念とデータを 表現します。これらには API がありますが、エンドユーザ向けのインターフェー スはありません。HTTP や HTML には関知せず、cron ジョブのような 非Web アプリケーションからでも利用可能です。データベースやその他のデータソー スと通信し、自身の永続管理もします。

Controller レイヤーは Web のリクエストを Model レイヤー上の適当なアク ションに変換します。パラメータのパーシング、入力チェック、Model オブジェ クトの取得、メソッド呼び出しなどを処理します。その後、適当な View を決 定し、結果のHTMLをユーザに送信します。

View オブジェクトは実際はHTMLテンプレートです。Controller は Model オ ブジェクトのデータを View に転送し、View が Web ページを生成します。こ れらは Template Toolkit という、Perl で書かれた強力なテンプレートシス テムで実装されています。テンプレートは基本的な条件文とループがあり、ロ ジックのフォーマットを表現するのに十分な機能を備えています。テンプレー トにアプリケーションの制御フローが埋め込まれることはありません。


キャッシング

パフォーマンス戦略のコアは、複数階層化されたキャッシュシステムです。ア プリケーションサーバでは、データオブジェクトは、ローカルディスク上の共 有メモリにキャッシュされています。アプリケーション側で、データオブジェ クトがどれだけの間、データベースと同期しないでよいかを指定し、その間の アクセスは高速なキャッシュによって処理されます。この種のキャッシュコン トロールは ``time-to-live'' として知られています。ローカルキャッシュは Berkeley DB データベースを用いて実装されています。オブジェクトは CPAN の標準モジュール Storable でシリアライズされます。

データオブジェクトは、高い粒度のエクスパイアを実現するために、必要に応 じて分割されます。たとえば、製品の在庫は他の製品データに比べて頻繁にアッ プデートされます。製品データを分割することによって、在庫だけは短いエク スパイア期間でデータベースと同期させ、その他の製品データのエクスパイア は長いままにしておくことができます。

アプリケーションサーバのオブジェクトキャッシュは IP マルチキャストプロ トコルと C でかかれたカスタムデーモンによって製品データを共有していま す。ある製品が1つのサーバのキャッシュに配置されると、そのデータは他の サーバのキャッシュに複製されます。このテクニックは製品データへのアクセ スの局所性が高いため、よい結果をもたらしました。2000 年のクリスマスシー ズンの間、キャッシュは 99% のヒットを記録し、それによってデータベース の負荷は大きく減りました。

データオブジェクトのキャッシュに加え、製品の詳細ページのような、ユーザ に依存しないページ全体もキャッシュ可能です。アプリケーションはページで 使われるデータオブジェクトのエクスパイアを最小に設定し、プロキシサーバ にページエクスパイア期間を、標準の Expires ヘッダで指示します。プロ キシサーバは NFS 上に、生成されたページをキャッシュします。このように してキャッシュされたページは、スタティックなページと同様のパフォーマン スを得ることができます。

緊急の修正ができるように、われわれは mod_proxy にフックを追加して、 指定したURLのキャッシュコピーの削除ができるようにしました。これによっ て、まちがった情報をすぐに修正して反映させることができました。

mod_proxy のキャッシュのもう1つの利点は、If-Modified-Since リク エストの自動処理にあります。mod_proxy にその機能があるため、これを われわれ自身で実装する必要はありませんでした。


セッショントラッキング

ユーザは HTTP cookie によって、セッションIDが割り当てられます。この処 理はプロキシサーバ上の、カスタマイズされた mod_session によっておこ なわれます。プロキシサーバで処理することによって、キャッシュされたペー ジにアクセスしているユーザもセッションIDが割り当てられることが保証され ます。セッションIDは、サーバサイドに保存されるデータへのキーに過ぎませ ん。ユーザセッションはアプリケーションサーバに割り当てられ、そのサーバ が利用できなくなるまで利用し続けます。これは ``sticky'' なロードバランシ ングと呼ばれます。セッションデータやその他のユーザが操作したデータ -- ショッピングカートの中身など -- はオブジェクトキャッシュとデータベース の双方に書き込まれます。二重に書き込むことにより、パフォーマンスペナル ティは若干生じますが、後続のリクエストでデータベースを見ない分、高速に リードアクセスを実現できます。サーバがダウンして、ユーザが別のサーバに 割り当てられた場合でも、単にデータベースからもう一度取得しなおせばよい のです。


セキュリティ

大規模な eコマースサイトはあらゆるアタックの絶好のターゲットとなります。 こうしたシステムを設計する際には、攻撃されることを想定し、マシンレベル と同時に、アプリケーションレベルでもキュリティを意識して構築する必要が あります。

一番のルールは``クライアントを信用するな!'' です。ユーザ固有のデータをク ライアントに送出する際には、複数レベルの暗号化で、保護されます。SSL は、 重要なデータのやりとりを、ネットワークトラフィックのスヌーピングから保 護します。``セッションハイジャック''(他のユーザのセッションにアクセスす るために、セッションIDを改ざんすること)を防ぐために、セッション cookie に Message Authentication Code (MAC) を含めています。これは CPAN の Digest::SHA1 モジュールを利用して、こちらのサーバ内でしかわからない seed フレーズを使って生成されます。MAC アルゴリズムによってセッション cookie の ID をチェックすれば、データが何者かによって改ざんされたもの でないことが証明されます。

状態を保持する情報を HTML フォームやURLに配置する必要があり、かつそれ をユーザに明らかにしたくないシチュエーションでは、CPAN の Crypt:: モジュールを使って、暗号化と復号化をします。Crypt::CBC モジュールか らスタートするとよいでしょう。

単純な過負荷アタックから防御するために、ユーザが大規模なリクエストをサー バに送出して、サービス停止させようとした場合には、アプリケーションサー バへのアクセスがスロットルシステムによってコントロールされます。このコー ドは Randal Schwartz による Stonehenge::Throttle モジュールをベース にしています。それぞれのユーザへのアクセスはNFS上の小さなログにトラッ キングされます。プログラムは、1ユーザの一定期間内のリクエスト数の上限 を指定します。

MAC の使用や、暗号化、過負荷防御など、Web セキュリティに関する情報は、 O'Reilly の CGI Programming with Perl, 2nd EditionWriting Apache Modules with Perl and C を見てみることをお勧めします。


例外(Exception)処理

このシステムを計画した際、実装する言語として Java を利用することを検討 しました。Perl でいくことに決定しましたが、Java の見事な例外ハンドリン グ機能はなくては困るものでした。ラッキーなことに、CPAN の Graham Barr の Error モジュールによって、同様の機能を Perl でも実現できます。

Perl でもすでに、ランタイムのエラーをトラップして、例外オブジェクトを 投げることはサポートしていますが、Error モジュールはそれに加えて、いく つかの見事な syntactic sugar を提供しています。次のコードサンプルは、 このモジュールを使った典型的なものです。

    try {
        do_some_stuff();
    } catch My::Exception with {
        my $E = shift;
        handle_exception($E);
    };

このモジュールで、独自の例外クラスを作成し、特定のタイプの例外をトラッ プすることができます。

これによる大きなメリットの1つは、DBI との連係です。DBIRaiseError フラグをたてて、例外をトラップしたい場所に try ブロック を利用すると、Error モジュールによって、DBI のエラーが Error オブジェクトに変換されます。

    try {
        $sth->execute();
    } catch Error with {
        # roll back and recover
        $dbh->rollback();
        # etc.
    };

このコードでは、エラーによって、データベースのトランザクションをロール バックしなくてはならないことを示しています。実際には、ほとんどの DBI のエラーは、予想外のことがデータベースに起こって、現在の操作が 継続できないことを意味しています。これらの例外はトップレベルの、すうべ てのリクエストを囲む try{} ブロックに伝播させることができます。ここ でエラーがキャッチされた場合には、スタックトレースをロギングし、ユーザ には親切なエラーページを送信します。


テンプレート

HTML とアプリケーションデータをはめこむロジックは、ともにテンプレート に保持されています。CPAN モジュールの Template Toolkit を利用してい ます。アプリケーションによって渡された Perl のデータ構造にアクセスする ための、シンプルかつ強力なシンタックスを提供しています。ループや条件制 御文などの基本的なものに加え、モジュール化のサポートが強力で、インクルー ドやマクロなど、テンプレートのメンテナンスを単純化し、冗長さをなくすこ とができます。

Template Toolkit はこのプロジェクトには有益でした。わたしたちのHTML コーダーは、すぐにそれを理解して、Perl コーダーのヘルプを借りることな く、テンプレート作業のほとんどすべてをおこなうことができました。それぞ れのテンプレートに、どのデータが渡されるかのドキュメンテーションを提供 して、残りはすべてやってくれました。いままでに、プロジェクトマネージャ に、要求された変更をHTMLチームだけで、あなたのヘルプを借りることなく処 理できると伝える悦びを味わったことがないのなら、あなたはほんとうに損を しています!

Template Toolkit はテンプレートを Perl バイトコードにコンパイルし、 メモリにキャッシュして、効率を改善します。テンプレートファイルがディス ク上で更新されると、それを検知して再コンパイルされます。これは Mason や Apache::Registry のような mod_perl のシステムの動作によく似て います。

テンプレートのサーチパスを変更することによって、サイトの特定の部分をテ ンプレート化して、あるエリアに関してはカスタマイズされたルック&フィー ルを提供することを可能にしました。たとえば、ブックストアサイトセクショ ンのヘッダーテンプレートは、ビデオゲームストアとは違うものにすることが できます。同じデータも、サイトの場所によって、違う外観で提供することも でき、コンテンツの co-branfing を可能にしています。

Template Toolkit での基本的なループがどのようになるかのサンプルです:

    [% FOREACH item = cart.items %]
    name: [% item.name %]
    price: [% item.price %]
    [% END %]


Controller サンプル

どのようにして Model-View-Controller パターンがコードの中で利用されて いるか、シンプルな Hello World サンプルを通して見てみましょう。 Controller コードからはじめます。

    package ESF::Control::Hello;
    use strict;
    use ESF::Control;
    @ESF::Control::Hello::ISA = qw(ESF::Control);
    use ESF::Util;
    sub handler {
        ### セットアップ作業をいくつか
        my $class = shift;
        my $apr = ESF::Util->get_request();

        ### model をインスタンス化する
        my $name = $apr->param('name');

        # Model::Hello オブジェクトの生成
        my $hello = ESF::Model::Hello->new(NAME => $name);

        ### view を送出
        my $view_data{'hello'} = $hello->view();

        # process_template() メソッドは
        # ESF::Control ベースクラスから継承
        $class->process_template(
                TEMPLATE => 'hello.html',
                DATA     => \%view_data);
    }

これでわかることに加えて、ESF::Control ベースクラスの興味深い詳細を もうすこし紹介します。すべてのリクエストは ESF::Control-&gt;run() メソッドによって、最初にディスパッチされ、全体を try{} ブロックで囲 んで、適当な handler() メソッドを呼び出します。ベースクラスでは process_template() メソッドも提供しており、Template Toolkit を走 らせて、適切な HTTPヘッダとともに結果を送出します。Controller が指定す れば、ヘッダには Last-ModifiedExpires をプロキシサーバのキャッ シュ制御のために含めることができます。

では、対応する Model コードを見てみましょう。

    package ESF::Model::Hello;
    use strict;
    sub new {
        my $class = shift;
        my %args = @_;
        my $self = bless {}, $class;
        $self{'name'} = $args{'NAME'} || 'World';
        return $self;
    }

    sub view {
        # オブジェクト自身が view に対して動作する
        return shift;
    }

これは、シンプルな Model オブジェクトです。ほとんどの Model オブジェク トはデータベースやキャッシュのやりとりを持っているでしょう。ID を受け 取って、適切なオブジェクト状態をデータベースからロードする load() メソッドも含まれるでしょう。アプリケーションによって更新される Model オブジェクトは save() メソッドも実装しているでしょう。

Perl の柔軟な OO スタイルのおかげで、データベースからオブジェクトをロー ドする際に、new() を呼ばなくてもよいことに注目して下さい。load()new() は、ともに異なる環境で利用されるコンストラクタになり、とも に bless されたリファレンスを返します。

典型的な load() メソッドは、データベースアクセスに加えてキャッシュ 管理を処理します。典型的な load() メソッドの擬似コードはこのように なります:

    sub load {
        my $class = shift;
        my %args = @_;
        my $id = $args{'ID'};
        $self = _fetch_from_cache($id) ||
                _fetch_from_database($id);
        return $self;
    }

save メソッドは同様のアプローチを逆にした形で利用し、先にキャッシュ、 次にデータベースに保存します。

Model クラスで最後に注目するのは view() メソッドについてです。この メソッドは、テンプレートで扱いやすいようにデータを入れ換えたり、分離さ れたデータ構造をつくったりする機会をオブジェクトにあたえるために存在し ます。これによって、テンプレートコーダーから、複雑な実装を隠すことがで きます。たとえば、製品の在庫データを別々のキャッシュエクスパイア時間に なるように分離したのを思いだして下さい。製品 Model オブジェクトは、実 際には、裏にあるいくつかの実装オブジェクトの Facade になっていますが、 view() メソッドによって、テンプレートで使われるデータを統一処理して います。

Hello World サンプルの最後に、View をレンダリングするテンプレートが必 要です。これで十分でしょう:

    <HTML>
    <TITLE>Hello, My Oyster</TITLE>
    <BODY>
        [% PROCESS header.html %]
        Hello [% hello.name %]!
        [% PROCESS footer.html %]
    </BODY>
    </HTML>


パフォーマンスチューニング

Perl コードは mod_perl 環境ではとても高速に動作するので、パフォーマ ンスのボトルネックは通常、データベースになります。われわれは、 DBD::Oracle のパフォーマンスを改善するために、ドキュメントにかかれ ているトリックをすべて適用しました。変数バインドや prepare_cached(), Apache::DBIRowCache バッファサイズの調 整をおこないました。

一番効果があるのは、まずデータベース接続を避けることです。これをキャッ シングすることによって、パフォーマンスはかなり改善しました。Berkeley DB キャッシュから製品データを取得することによって、データベースから取 得するのに比べ、10倍程度高速になりました。製品ページをプロキシキャッシュ から吐きだすことにより、キャッシュデータからアプリケーションサーバでペー ジ生成するのに比べ、10倍程度高速になりました。明らかに、このサイトはキャッ シングなしでは負荷には耐え切れなかったでしょう。

データオブジェクトの分割も大きな効果がありました。製品データのいくつか のサブセットを定義し、ロードとキャッシュを独立におこなえるようにしまし た。アプリケーションが製品データを必要とするとき、どのサブセットが必要 とされているかを指定して、不要なデータのローディングを避けることができ ました。

さらに標準的なパフォーマンス技法として採用したのは、不必要なオブジェク ト生成を避けることです。Template オブジェクトは最初に利用される時に 生成され、Apache プロセスのライフタイムの間、キャッシュされます。検索 サーバへのソケット接続は Apache::DBI のデータベース接続と同様にキャッ シュされます。リクエストスコープ内で頻繁に利用される、データベースハン ドルやセッションオブジェクトは、mod_perl$r-&gt;pnotes() に リクエストの間、キャッシュしておきました。


ワナ: ネストした例外

Error モジュールのような新しいテクノロジーに挑戦する時は、いくつか 注意しないといけないことがあります。実行されるたびに毎回メモリリークを 引き起こすようなコード構造があることにきづきました。ネストした try{} を含んでいて、このような感じです:

    my $foo;
    try {
        # some stuff...
        try {
            $foo++;
            # more stuff...
        } catch Error with {
            # handle error
        };

    } catch Error with {
        # handle other error
    };

これがリークするのは Graham Barr のせいではありません。trycatch のキーワードが無名サブルーチンを使って実装されているための副 産物にすぎないのです。このコードは次のものと同等です。

    my $foo;
    $subref1 = sub {
        $subref2 = sub {
            $foo++;
        };
    };

このネストしたサブルーチンは $foo へのクロージャを生成し、実行され る度に変数のコピーをつくります。こういったシチュエーションは、一度気を つけなくてはいけないとわかれば、簡単に回避することができます。


Berkeley DB

われわれのアーキテクチャで良い効果を産んだものの1つに、Berkeley DB があげられます。多くの人が Berkeley DB の高度な機能にくわしくないので、 ここで簡単に概要を説明しましょう。

DB_File モジュールは Perl ディストリビューションに標準で含まれてい ます。しかし、このモジュールは Berkeley DB のバージョン 1.85 のイン ターフェースしかサポートしておらず、それ以後のリリースの興味深い機能は 含まれていません。これを利用するには、CPAN で入手可能な BerkeleyDB.pm モジュールが必要です。このモジュールのビルドはトリッ キーですが、わかりやすい説明書がついています。

あたらしいバージョンの Berkeley DBmod_perl 環境でのパフォー マンスに貢献するたくさんの機能を提供しています。第一に、データベースファ イルはプログラムの先頭で一度オープンしたら、オープンしっぱなしにでき、 リクエストごとに開いたり閉じたりする必要がありません。Berkeley DB は共有メモリバッファを利用して、データベースを利用する総てのプロセスで のデータアクセス速度を向上させます。同時アクセスはデータベースによるロッ クとともに直接サポートされています。これは、自分でロックを操作する必要 がある DB_File に比べて大きなメリットとなります。ロックはデータベー スレベルか、複数の同時書き込みをサポートするメモリページレベルかを選択 できます。ロールバック可能なトランザクションもサポートされています。

これはうますぎる話のようですが、マイナス材料もあります。ドキュメントは やや不足ぎみで、複雑なことをやろうとすると、C API を参照する必要がある かも知れません。

さらに重大な問題は、データベースの破損です。Berkeley DB を利用して いる Apache プロセスが hard kill や segfault で死ぬと、データベースが 破損する可能性があります。データベースが破損すると、後続のオープン処理 がハングしてしまうことがあります。Sleepycat Software (Berkeley DB の商用サポートを提供している会社) によれば、これはトランザクションモー ドでも発生する可能性があるとのことです。彼らは現在この問題を修正するた めに作業しています。われわれの場合、キャッシュにストアされたデータは操 作に必須のものではなかったため、アプリケーションサーバのリスタート時に キャッシュをすべてクリアしていました。

もう1つはデッドロックです。ページレベルのロックオプションを利用すると、 デッドロック処理をする必要がでてきます。ディストリビューションには、デッ ドロックを監視して修正するデーモンが含まれていますし、C API を使って自 分で処理することもできます。

いろいろやってみた結果としては、データベースレベルのロックをお勧めしま す。こちらはずっとシンプルで、問題を解決してくれました。このロックモー ドで、パフォーマンスが大幅に落ちるということはありませんでした。唯一、 データベースレベルの排他ロックを利用する際に注意することは、データベー スを長い間拘束するオペレーションです。われわれは、この問題を回避するた めに、いくつかのオペレーションを複数の書き込みに分割しました。

もしあなたのチームによい C プログラマがいるなら、われわれと同様に、別 のアプローチを取ってみようと思うでしょう。Berkeley DB を操作するデー モンプロセスを書いて、UNIX ソケットを通してクライアント/サーバスタイル で動作させるのです。これによってシグナルをつかまえたり、安全にシャット ダウンをおこなうことが可能になります。この方法なら、デッドロックをハン ドルするコードも独自に書くことができます。


役に立つツール

もし Perl でシリアスな開発をおこなおうと思うのなら、利用可能な開発ツー ルのいくつかに慣れ親しむ時間をとったほうがよいでしょう。とくにデバッガ は救世主となりえますし、mod_perl でも動作可能です。Devel::DProf というプロファイラがあり、これrもまた mod_perl で動作します。アプリ ケーションのパフォーマンスチューニングをするなら、まずそこから始めるべ きでしょう。

システム全体を、各自のワークステーションで動作させることができると、か なり便利だであることを認識しました。みなが自分のマシンで開発することが でき、変更は CVS をつかって統一管理するのです。

オブジェクトのモデリングやデザインには、オープンソースの DiaRational Rose を使いました。どちらも、UML での作業をサポートしてお り、作業スペースのクラス図を生成するのに便利です。


これを宿題に

われわれがこのプロジェクトを始めてから、こうした感じのアーキテクチャを サポートした開発フレームワークがいくつかでてきました。これらを使った直 接の経験はないのですが、似たようなデザインを持っており、MVC アプローチ をするなら、きっと便利であることでしょう。

Apache::PageKit は CPAN から入手可能な mod_perlモジュールで、Web アプリケーションに関する、基本的な MVC 構造を提供します。View の作成に は、HTML::Template を使っています。

OpenInteract は最近リリースされた、Perl で書かれた Web アプリケーショ ンフレームワークで、SPOPS という Persistence レイヤーとともに動作し ます。ともに CPAN から入手可能です。

Extropia の Application Toolkit は Web アプリを構築するための Perl の綜合セットです。素晴しいドキュメントがついていて、既存の CPAN モジュー ルをうまく利用しています。http://www.extropia.com/ から入手できます。

すぐに利用可能なキャッシュモジュールが欲しければ、 http://sourceforge.net/ にある Perl-cache プロジェクトを見てみて下さい。 人気のある File::Cache モジュールの次世代版です。

Java もまた、は多くの選択枝を持っています。Jakarta プロジェクトの Struts フレームワークは、オープンソースで、良い選択でしょう。他にも、 いくつものベンダーが、こうしたデザインの商用プロダクトを出しています。 トップであらそっているのは、ATG Dynamo, BEA WebLogicIBM WebSphere でしょう。


オープンソースのサクセスストーリー

オープンソースのソフトウェアとコミュニティを構築することによって、上級 の Web サイトを最小のコストと努力で作ることができます。われわれが最後 に到達したシステムは、巨大なトラフィックに耐えうるスケーラビリティを持っ ています。日常使っているハードウェアで動作し、必要になれば強化すること ができます。そしておそらく最も良い点は、われわれデベロッパーに多くの勉 強の機会を与え、そしてより大きな開発コミュニティの一部になることができ るという点でした。

われわれは、仕事の中からいくつものオープンソースプロジェクトにパッチを contribute して、メーリングリストでヘルプを提供してきました。この機会 に、ここで言及したプロジェクトに貢献しているオープンソースデベロッパー のみなさんに感謝したいとおもいます。同時に eToys で一所懸命働いている Web デベロッパーのみなさんにも感謝しなくてはなりません。ストアは閉まっ たとしても、それをつくった才能は生き続けます。

この記事に質問等があれば、以下の e-mail アドレスまでコンタクトして下さい。

Bill Hilf - bill@hilfworks.com

Perrin Harkins - perrin@elem.com

Perl.com Compilation Copyright © 1998-2000 O'Reilly & Associates, Inc.