伝統的3層アーキテクチャでのスケールアウト
現代のシステムでは、ノード1台(本稿では、Akkaクラスターが立脚している分散システムの用語に合わせ、1台のコンピュータやサーバーを指します)で構成するシステムは稀で、負荷分散や耐障害性のため複数のノードで構成することが一般的です。
例えばアクターを用いない伝統的な3層アーキテクチャで、かつアプリケーション層に内部状態が存在しない設計の場合は、ノードの数を増やして負荷分散を行う、スケールアウトが非常に容易です。下図のようにロードバランサーが各ノードにリクエストを割り振る時、ラウンド・ロビンなど単純なアルゴリズムで負荷を分散できます。
この構成は単純でわかりやすいのですが、書き込みはおろか読み出しも含め、全てのリクエストがデータベースにアクセスする難点があります。これではデータベースに負荷が集中し、パフォーマンスが悪くなってしまいます。
これを回避するため、ノードのメモリ上のキャッシュを利用する方法もありますが、途端に設計が難しくなってきます。キャッシュ更新のタイミングが不適切だと、データベースはすでに更新されているのに、いくつかのノードではいつまでも古いキャッシュが残ってしまいます。反対に、頻繁なキャッシュの更新はデータベースアクセスを増やし、パフォーマンスを悪化させます。また各ノードは別々の状態のキャッシュを持つので、スケールアウトの規模が大きくなると、キャッシュヒット率がさがり、思ったようなパフォーマンス向上は望めません。
このような各ノードでのキャッシュ状態の差異をなくすため、キャッシュ用のミドルウェアを利用すると、さらに設計の難度は高くなります。Redisやmemcachedなどのキーバリューストアがこういった用途にはよく使われ、たしかにリクエストの特性によっては、特定の種類のデータベースを使うよりパフォーマンス向上が見込めます。しかしデータベースとキーバリューストアの2箇所に別れたデータと整合性を保ちつつ、アプリケーション層のデータモデルを保守しやすく保つのは至難の業です。
大規模システムのためのアクターを用いたスケールアウト
このように、伝統的な3層アーキテクチャではキャッシュを持つ設計は難しいことがお分かりになったと思います。データベースやキーバリューストアの都合に左右されないアプリケーション設計を行うためには、これまでの連載で紹介してきたように、アクターが有効です。
アプリケーション層でアクターを使えば、永続化層と疎結合になり、保守しやすいデータモデルを構築できます。さらに、アクター同士がメッセージを送り合うことで協調して動作するという特徴は、伝統的な3層アーキテクチャにはない設計の自由度を与えてくれます。特に、さまざまなクライアントから接続され、多種多様なデータを処理し、1つのイベントが複数のアクションを引き起こすような大規模システムでは、アクターによる設計が適しています。
一方で、アクターを用いたアプリケーションでは、スケールアウトするのが難しくなります。アクターが内部状態を包有しているので、伝統的3層アーキテクチャでメモリ上のキャッシュを用いるのと同じような難しさがあります。何らかの方法でアクターのメリットを享受しつつ、スケールアウトの問題を避けることはできないでしょうか?
Akkaクラスターは、この問題を解決し、アクターを複数台のノードにまたがるクラスター上で動かすための機能です。このクラスターと、今までの連載で紹介してきたアクター、イベント・ソーシング、CQRSと組み合わせて使うことで、パズルの最後のピースがはまるように、最大限の価値を発揮できます。特に、イベント・ソーシングによって得られる耐障害性をノードをまたいで拡張できるため、両者の相性は抜群です。これらの機能から得られるメリットは、大規模な分散システムを構築する上で大きな武器になるでしょう。
前回の記事でイベント・ソーシングとCQRSの実装を持ったツールキットはAkka以外にほとんど見当たらない、という話をしましたが、アクターをクラスター上で稼働させるツールキットもAkka以外には多くありません。大規模分散システムを構築する可能性がある、あるいは将来そういった領域に進みたいなら、ぜひ本記事を入り口にAkkaクラスターを学んでみてください。