メモリイメージ
2011年8月31日
エンタープライズアプリケーションを開発する際、最初に問われる質問の1つが「データベースとどのようにやり取りするか」です。最近では、「リレーショナルデータベースを使うか、それともNoSQLデータベースを使うか」と少し異なる質問をすることがあります。しかし、もう1つ考慮すべき質問があります。「そもそもデータベースを使う必要があるのか?」
エンタープライズアプリケーションの定義的な特徴の1つは、長期データを保存する必要があるということであり、それは自然と人々をデータベースへと導きます。結局のところ、データの永続化はデータベースの主な役割の1つです。メモリイメージを使用することは、データベースを伴わない、永続化への別のルートです。
メモリイメージの重要な要素は、イベントソーシングを使用することです。これは、アプリケーションの状態へのすべての変更がイベントとしてキャプチャされ、永続的なストアに記録されることを意味します。さらに、これらのイベントを再生することで、アプリケーションの完全な状態を再構築できることを意味します。イベントがプライマリな永続化メカニズムになります。
イベントソーシングを使用するシステムの身近な例として、バージョン管理システムがあります。すべての変更はコミットとしてキャプチャされ、空のディレクトリにコミットを再生することでコードベースの現在の状態を再構築できます。実際には、すべてのイベントを再生するには時間がかかりすぎるため、システムはアプリケーション状態の定期的なスナップショットを保存します。その後、再構築には、最新のスナップショットをロードし、そのスナップショット以降のイベントを再生することが含まれます。
イベントソーシングには、過去の状態を再構築する機能など、多くの結果があります。しかし、メモリイメージにとって重要な特性は、アプリケーション状態を最新の永続的なストアに保持することを心配する必要がなくなることです。代わりに、アプリケーションの状態をメインメモリに保持できます。プロセスがクラッシュした場合でも、イベント(およびスナップショット)から再構築できます。
メモリイメージを使用すると、すべてがメモリ内で実行され、IOやデータベースシステムへのリモート呼び出しがないため、高いパフォーマンスが得られます。おそらくもっと重要なのは、データベースマッピングコードを削除したり、メモリ内の状態とデータベースの状態の同期について心配する必要がなくなることです。
それに反して、イベントを確実に保存および処理できることを保証する必要があります。また、スナップショットを保存およびロードするコードを記述し、サービス品質を維持するためにシステムを十分に迅速に復元する方法を理解する必要があります。データベースは、永続化だけでなくトランザクションの並行性も提供するため、並行性についてどのように対処するかを理解する必要があります。
もう1つの、かなり明らかな制限は、保持する必要のあるデータよりも多くのメモリが必要になることです。メモリサイズが着実に増加するにつれて、これは以前よりもはるかに制限が少なくなってきています。[1]
さまざまな種類のシステムがメモリイメージを利用できます。ここでは、私が遭遇した3つの例を紹介します。
最も最近のものはLMAXです。LMAXは高性能取引システムで、単一のJVMスレッドで1秒あたり600万件のトランザクションを処理します。ここでは、メモリイメージのパフォーマンス上の利点は明らかに大きな要因ですが、プログラミングモデルの簡略化も同様に重要であることがわかりました。すべてが単一のスレッドであるため、並行性について心配する必要はありません。可用性を高く保つために、メモリイメージの複数のコピーを実行しており、1つがダウンした場合は、トランザクションレートを非常に高く保ちながら、別のインスタンスに切り替えることができます。
数年前、私はEventPosterアーキテクチャを使用する2つのシステムについて書きました。このスタイルは、分析目的で多数のUIにメモリ内モデルへの読み取りアクセスを提供します。複数のUIは複数のスレッドを意味しますが、ライター(イベントプロセッサ)は1つだけであるため、並行性の問題が大幅に簡略化されます。
最も古い例は、名前の由来でもあります。それは、Smalltalk開発環境です。ほとんどの開発ツールは、必要に応じてコンパイルまたは解釈されるファイルシステムのテキストファイルに依存しています。Smalltalkは、すべてのソースコードとコンパイルされたメソッドをイメージ内に保持していました[2]。実行したすべてのコマンドは変更ログに保存されました。ほとんどの場合、イメージ(スナップショット)を保存しましたが、何か愚かなことをした場合、必要に応じて安定したベースから変更ログを再生できました。
これらの種類のアイデアの多くと同様に、何度も使用され、再発明されてきたアプローチですが[3]、主流にはなりませんでした。データベースに永続データを保持することは、引き続き一般的なアプローチです。
メモリイメージで耳にした問題の1つは、移行に関するものです。ソフトウェアシステムを構築する際は、変更をどのように処理するかを理解することが重要です。メモリイメージの場合、本質的なタスクは、イベントログからメモリイメージを再構築し続けることができるようにすることです。
ここでの落とし穴の1つは、イベントの構造を変更する場合に、イベントログのシリアル化構造で、進化を適切に処理しないものを使用することです。特定のイベントクラスを作成してシリアル化すると、後でイベントクラスの構造を変更した場合に、古いイベントの処理が難しくなる可能性があります。多くの場合、マップやリストなどの汎用データ構造でシリアル化するのが最適です。
また、イベントとモデル構造自体の間に適切な疎結合を維持することも重要です。イベントデータとモデルを内省する自動マッピングシステムを思いつくのは魅力的かもしれませんが、これはイベントとモデルを結合するため、モデルを移行して古いイベントを処理することが困難になります。
ある時点で、イベントログ自体を古い形式から新しい形式に移行する価値があるかもしれません。イベントログの移行は手間がかかることが多いですが、元のイベント構造から大きく進化した場合、オプションになる可能性があります。
長い間、メモリイメージの使用に対する大きな議論はサイズでしたが、現在ではほとんどのコモディティサーバーには、ディスクに慣れていたよりも多くのメモリがあります。結果として、ほとんどのワーキングセットをメモリ内に安全に保持できるようになりました。私たちは数年前にこれに気づきましたが、メモリイメージはまだ比較的まれです。現在、NoSQLムーブメントが人々に永続化のオプションを再考させているため、このパターンの急増が見られるかもしれません。
注釈
1: イベントにあるデータの一部だけが必要な場合は、メモリ使用量を減らすこともできます。必要に応じて、同じイベントを異なるデータサブセットに対して異なるメモリイメージシステムに送信できます。
2: ここでSmalltalkに対して過去形を使用しているのは、私が最後に使用してから長い時間が経っており、長年にわたって動作が変わった可能性があるためです。それはまだ存在しますが、残念ながらニッチな環境です。
3: また、このアプローチのオープンソース実装であるPrevaylerプロジェクトを覚えている人もいるかもしれません。これは数年前にJavaコミュニティで大きな話題になりましたが、それ以来はかなり静かになっています。そのコミュニティでは、このアプローチの一般的な用語としてシステムプレバレンスを使用しています。