分散キャッシュによるパフォーマンスの向上
分散キャッシュとは
キャッシュとは、将来のリクエストにすばやく応答するためにデータを保存しておくコンポーネントのことです。データをメモリ内に保存することにより、スループットが向上し、よく使用されるアプリケーションデータへのレイテンシの低いアクセスが実現します。
キャッシュを活用すると、永続的なデータストアへのレイテンシの高いデータアクセスを避けることにより、アプリケーションの応答性を大幅に向上できます。
アプリケーションユーザーが共有する共通データが多い場合は、キャッシュが非常に有効です。たとえば、各ユーザーがそのユーザーまたはリクエストに固有のデータを取得することが多い場合は、キャッシュのメリットはそれほどありません。キャッシュのメリットが特に大きい例として、製品カタログがあります。製品カタログのデータはそれほど頻繁に変更されず、すべての顧客が同じデータを参照するからです。
ローカルキャッシュを使用してクエリやアクションの結果を保存するOutSystemsアプリケーションでは、各フロントエンドがそれぞれのプロセスメモリをキャッシュストレージとして使用し、キャッシュデータを保存します。この場合、各フロントエンドはそれぞれのキャッシュストレージ内にあるキャッシュデータのみにアクセスします。このため、その他のすべてのサーバーはデータストアに別途アクセスし、同じデータを再度取得する必要があります。
一方、分散キャッシュでは、他のインフラリソース上にキャッシュデータを保存して同期された状態で管理し、すべてのフロントエンドが同じキャッシュデータをリモートで透過的に利用できるようにします。たとえば、フロントエンドでは、キャッシュデータの保存に使用するインフラリソースやそのインフラのトポロジーはわかりません。
これらの情報は、分散キャッシュの同期メカニズムとルーティングメカニズムの中にあります。このように、フロントエンドで必要な情報はリモートエンドポイントと分散キャッシュに接続するためのソケットポートのみです。
分散キャッシュの作成パターン
キャッシュからデータを取得するには、先にデータがキャッシュに保存されている必要があります。キャッシュにデータを格納するにはいくつかの戦略があります。
オンデマンド/キャッシュアサイド
アプリケーションは、キャッシュからデータを取得しようとして「失敗」した(キャッシュにデータがない)場合、キャッシュにデータを保存して次回利用できるようにします。
アプリケーションは次に同じデータの取得を試行するとき、キャッシュ内でそのデータを検索します。データベース内で変更されたキャッシュデータの取得を避けるため、アプリケーションは、アクション内の特定のロジックを使用してデータストアを変更している間、キャッシュデータを無効化します。
また、キャッシュエントリを無効化するのではなく、アクションロジックの一部として、古くなったキャッシュデータを新しいデータに更新することもできます。CRUDアクションを公開しているエンティティのセット(読み取り専用)に対して継続的なクエリが行われる場合は、公開されたCRUDオペレーション内にキャッシュアクセス(読み取りおよび書き込み)を実装し、それらのエンティティのコンシューマに対して透過的にしたほうがよい場合があります。
この戦略は、本来ならセッションに保存されるデータや複数のWebリクエストでビジネスロジックのサポートとして使用されるデータなど、一時的なデータをキャッシュする必要がある場合に適しており、これらのオペレーションで必要なデータを提供する際のセッションやデータストアのリソースのサイズを削減できます。
このパターンの実装方法
以下のアクションフローは、この戦略に関係する手順を示しています。
-
適切なキャッシュのキーとスコープを使用して、現在キャッシュ内にアイテムが保持されているかどうかを特定します。
-
アイテムが現在キャッシュ内ない場合、データストアからアイテムを読み込みます。
-
アイテムのコピーをキャッシュに保存します。
バックグラウンドデータプッシュ
タイマーバックグラウンドアクションは、一定のスケジュールでデータを分散キャッシュにプッシュします。すべてのコンシューマアプリケーションは同じデータをキャッシュからプルします。コンシューマアプリケーションはデータの更新は行いません。
このアプローチは、常に最新データである必要はないレイテンシの高いデータソースまたはオペレーションにおいて非常に効果的です。
この戦略のアプリケーションシナリオとして、外部システムとの連携が考えられます。
-
同じレコードが継続的に読み取りおよび更新されており、データストアの待機ロックの可能性を軽減する場合。
-
ソースデータが一定のスケジュール(一般的な間隔)で更新される場合。
-
キャッシュへプッシュするデータが大量にある場合。
この戦略は、キャッシュ更新の役割をビジネスロジックアクションからバックグラウンドタイマーに移動させるため、有効な場合があります。
このパターンの実装方法
以下のアクションフローは、この戦略に関係する手順を示しています。
-
タイマーバックグラウンドアクションが実行されると、キャッシュ内で更新が必要なデータがデータストアから読み込まれます。
-
同じタイマーバックグラウンドアクションで、キャッシュエントリがデータの最新バージョンに更新されます。
-
フロントエンドがキャッシュからデータを読み込みます。
アプリケーションが取得するデータが少し古いものでも問題ない場合は、構成可能な有効期限パラメータ(キャッシュエントリごと)を利用して、許容されるキャッシュエントリの古さの限度を設定します。ベストプラクティスとして、この限度をキャッシュエントリの作成時または更新時に設定します。
分散キャッシュパターンの実装時期
インフラに追加されるフロントエンドが増えると、分散キャッシュのメリットがますます大きくなります。これらのシナリオでは、永続的なデータストアのスループットの限界やレイテンシによる遅延が全体的なアプリケーションパフォーマンスでより顕著になったり、他のサーバーにルーティングされるロードバランサリクエストによってローカルキャッシュで問題が生じたりします。このため、一般的には次のような事実や要件を理由に分散キャッシュが支持されています。
-
同一または異なるインフラにある異なるサーバー、アプリケーション、モジュール、コンポーネント間での状態の共有。
-
アクション、画面、Webサービス内での同じデータに対する継続的なクエリ。特にデータが数秒ごとなど頻繁に変更される場合。
-
ユーザーデータのセッションからの移動(パフォーマンス上の理由)。
-
一時的なデータ(非常に短時間で作成および削除されるデータ)の保存。
-
大量のデータ(数百メガバイト)のキャッシュ。
-
キャッシュに使用されるサーバーリソースの全体的な制御(キャッシュのキーおよびメトリック、メモリ、CPU):
- ローカルキャッシュに使用されるフロントエンドサーバーリソースの軽減。
- 実行時キャッシュへの外部アクセスの許可(サービス運用チーム、OutSystems開発インフラ以外の外部アプリケーションなど)。
- キャッシュの「ウォームアップ」および検証のための外部メソッドの提供。
上記の理由に加え、ローカルキャッシュが目的に適さない場合を理解することが重要です。
-
ローカルキャッシュデータは異なるフロントエンド間では同期されません。つまり、異なるフロントエンドサーバーでは、ソースデータのコピーが異なっている場合や、表示されるソースデータの状態が異なっている場合があり、ソースデータの同期されたコピーを必要とするユースケースのシナリオには適さない可能性があります。
-
追加するフロントエンドサーバーの数が増えると、ローカルキャッシュのメリットが小さくなります。
- ローカルキャッシュにはバージョン管理の機能がありません。バージョン管理は、各キャッシュエントリに更新の都度バージョンを割り当て、最終読み取り後にキャッシュエントリが変更されたかどうかの検証を可能にするメカニズムです。これは、キャッシュデータの楽観的ロックを実装するときに役立ちます。
分散キャッシュへのアクセスを実装する
dmCache Forgeコンポーネントを使用すると、これらのキャッシュ戦略を実装し、AWS ElasticCache、Memcached、Redis、Couchbaseなどの様々な分散キャッシュテクノロジーをサポートできます。
このモジュールは分散キャッシュの実装に使用されるインフラとプロトコルを抽象化したものであり、CacheServerという名前のサイトプロパティを提供します。このサイトプロパティは、分散キャッシュのエンドポイント、タイプ、プロトコルを指定する特定のタイプのURLです。
このモジュールは、エンティティを読み取り専用として分離または公開したり、エンティティのCRUDオペレーションやバックグラウンドジョブを実装したりするコアビジネスモジュールで使用します。dmCacheはエンドユーザーUIモジュールで直接使用しないでください。
パブリックアクション
dmCacheモジュールは、キャッシュエントリの管理で使用する以下のパブリックアクションを提供します。
-
SetValue
-
SetListOfRecords
-
GetValue
-
GetListOfRecords
-
FlushAll
-
RemoveCacheEntry
SetValue、SetListOfRecords、GetValue、GetListOfRecords、RemoveCacheEntryの各アクションには、CacheKeyパラメータおよびCacheScopeパラメータがあります。
パラメータ
CacheKey
同じCacheScope内または「バケット」内でキャッシュエントリを識別する名前です。すべてのキャッシュエントリ名は一意である必要があります。
CacheScope
内部で使用され、キャッシュエントリごとに階層キーを作成し、キャッシュストア内で衝突が発生することを避けるために使用されます。
CacheScopeは以下のようになります。
-
Request - 同じCacheScopeに対してGetValueを呼び出すときに現在のリクエストに設定された値のみを使用できます。つまり、リクエストIDを使用してキャッシュエントリのキーが作成されます。
-
Session - 同じCacheScopeに対してGetValueを呼び出すときに現在のセッションに設定された値のみを使用できます。つまり、セッションIDを使用してキャッシュエントリのキーが作成されます。
-
Application - 同じCacheScopeに対してGetValueを呼び出すときに現在のアプリケーションに設定された値のみを使用できます。つまり、アプリケーションIDを使用してキャッシュエントリのキーの一部が識別されます。
-
Global - キャッシュキーはグローバルです。つまり、キャッシュエントリに使用されるキーの作成に他の要素は使用されず、GetおよびSetキャッシュ値のパラメータとして使用されたキャッシュキー名のみが使用されます。これは、外部システムにより設定されたキャッシュエントリにアクセスする場合や、OutSystemsプラットフォーム以外の他のソリューションと状態を共有する場合に有効です。
Forgeコンポーネント
分散キャッシュコンポーネントの一般的な実装については以下をご覧ください。