paint-brush
DIY タグ付きキャッシュ@ayacaste
259 測定値

DIY タグ付きキャッシュ

Anton Musatov7m2024/12/09
Read on Terminal Reader

長すぎる; 読むには

開発者はよく、プログラミングには変数の命名とキャッシュの無効化という 2 つの主な課題があると冗談を言います。 この冗談は真実からほど遠いものではありません。キャッシュの管理、特に無効化は、確かに深刻な作業になる可能性があります。この記事では、既存のキャッシュ サービスに基づいて、タグ付きキャッシュ機能を簡単に実装する方法を説明します。 ユーザーが記事を追加するシステムがあるとします。ユーザーごとに、記事の数、平均単語数、公開頻度などの統計情報を個人ダッシュボードに表示します。システムを高速化するために、このデータをキャッシュします。レポートごとに一意のキャッシュ キーが作成されます。 ここで疑問が生じます。データが変更されたときに、このようなキャッシュを無効にするにはどうすればよいのでしょうか。
featured image - DIY タグ付きキャッシュ
Anton Musatov HackerNoon profile picture


開発者はよく、プログラミングには主に 2 つの課題があると冗談を言います。

  • 変数の命名
  • キャッシュ無効化


このジョークは真実からほど遠いものではありません。キャッシュの管理、特にキャッシュの無効化は、確かに深刻な作業になる可能性があります。この記事では、既存のキャッシュ サービスに基づいて、タグ付きキャッシュ機能を簡単に実装する方法について説明します。


ユーザーが記事を追加するシステムがあるとします。各ユーザーの個人ダッシュボードに、記事の数、平均単語数、公開頻度などの統計情報が表示されます。システムを高速化するために、このデータをキャッシュします。レポートごとに一意のキャッシュ キーが作成されます。


ここで疑問が生じます。データが変更されたときに、このようなキャッシュを無効にするにはどうすればよいのでしょうか。1 つの方法は、新しい記事が追加されたときなど、イベントごとにキャッシュを手動でクリアすることです。

 class InvalidateArticleReportCacheOnArticleCreated { public function handle(event: ArticleCreatedEvent): void { this->cacheService->deleteMultiple([ 'user_article_report_count_' . event->userId, 'user_article_report_word_avg_' . event->userId, 'user_article_report_freq_avg_' . event->userId, ]) } }


この方法は機能しますが、多数のレポートやキーを扱う場合には面倒になります。ここで、タグ付きキャッシュが役立ちます。タグ付きキャッシュを使用すると、データをキーだけでなくタグの配列にも関連付けることができます。その後、特定のタグに関連付けられたすべてのレコードを無効にすることができ、プロセスが大幅に簡素化されます。


タグ付きの値をキャッシュに書き込みます。

 this->taggedCacheService->set( key: 'user_article_report_count_' . user->id, value: value, tagNames: [ 'user_article_cache_tag_' . user->id, 'user_article_report_cache_tag_' . user->id, 'user_article_report' ] )


タグによるキャッシュの無効化:

 class UpdateCacheTagsOnArticleCreated { public function handle(event: ArticleCreatedEvent): void { this->taggedCacheService->updateTagsVersions([ 'user_article_cache_tag_' . user->id, ]) } }


ここで、タグ'user_article_cache_tag_' . $user->idユーザーの記事の変更を表します。このデータに依存するキャッシュを無効にするために使用できます。より具体的なタグ'user_article_report_cache_tag_' . $user->id使用すると、ユーザーのレポートのみをクリアできますが、一般的なタグ'user_article_report'を使用すると、すべてのユーザーのレポート キャッシュが無効になります。


キャッシュ ライブラリがタグ付けをサポートしていない場合は、自分で実装できます。主なアイデアは、タグの現在のバージョン値を保存することと、タグ付けされた各値について、値がキャッシュに書き込まれた時点で最新だったタグ バージョンを保存することです。次に、キャッシュから値を取得するときに、現在のタグ バージョンも取得され、それらを比較して有効性が確認されます。


TaggedCacheクラスの作成

class TaggedCache { private cacheService: cacheService }


タグを使用してキャッシュに書き込むためのsetメソッドを実装します。このメソッドでは、キャッシュに値を書き込むだけでなく、提供されたタグの現在のバージョンを取得して、特定のキャッシュ キーに関連付けて保存する必要があります。これは、提供されたキーにプレフィックスが追加された追加キーを使用することで実現されます。

 class TaggedCache { private cacheService: cacheService public function set( key: string, value: mixed, tagNames: string[], ttl: int ): bool { if (empty(tagNames)) { return false } tagVersions = this->getTagsVersions(tagNames) tagsCacheKey = this->getTagsCacheKey(key) return this->cacheService->setMultiple( [ key => value, tagsCacheKey => tagVersions, ], ttl ) } private function getTagsVersions(tagNames: string[]): array<string, string> { tagVersions = [] tagVersionKeys = [] foreach (tagNames as tagName) { tagVersionKeys[tagName] = this->getTagVersionKey(tagName) } if (empty(tagVersionKeys)) { return tagVersions } tagVersionsCache = this->cacheService->getMultiple(tagVersionKeys) foreach (tagVersionKeys as tagName => tagVersionKey) { if (empty(tagVersionsCache[tagVersionKey])) { tagVersionsCache[tagVersionKey] = this->updateTagVersion(tagName) } tagVersions[$tagName] = tagVersionsCache[tagVersionKey] } return tagVersions } private function getTagVersionKey(tagName: string): string { return 'tag_version_' . tagName } private function getTagsCacheKey(key: string): string { return 'cache_tags_tagskeys_' . key }


キャッシュからタグ付きの値を取得するためのgetメソッドを追加します。ここでは、キーとそのキーに関連付けられたタグ バージョンを使用して値を取得します。次に、タグの有効性をチェックします。いずれかのタグが無効な場合、値はキャッシュから削除され、 nullが返されます。すべてのタグが有効な場合は、キャッシュされた値が返されます。

 class TaggedCache { private cacheService: cacheService public function get(key: string): mixed { tagsCacheKey = this->getTagsCacheKey(key) values = this->cacheService->getMultiple([key, tagsCacheKey]) if (empty(values[key]) || empty(values[tagsCacheKey])) { return null } value = values[key] tagVersions = values[tagsCacheKey] if (! this->isTagVersionsValid(tagVersions)) { this->cacheService->deleteMultiple([key, tagsCacheKey]) return null } return value } private function isTagVersionsValid(tagVersions: array<string, string>): bool { tagNames = array_keys(tagVersions) actualTagVersions = this->getTagsVersions(tagNames) foreach (tagVersions as tagName => tagVersion) { if (empty(actualTagVersions[tagName])) { return false } if (actualTagVersions[tagName] !== tagVersion) { return false } } return true } }


タグのバージョンを更新するためのupdateTagsVersionsメソッドを実装します。ここでは、提供されたすべてのタグを反復処理し、たとえば現在の時刻をバージョンとして使用して、タグのバージョンを更新します。

 class TaggedCache { private cacheService: cacheService public function updateTagsVersions(tagNames: string[]): void { foreach (tagNames as tagName) { this->updateTagVersion(tagName) } } private function updateTagVersion(tagName: string): string { tagKey = this->getTagVersionKey(tagName) tagVersion = this->generateTagVersion() return this->cacheService->set(tagKey, tagVersion) ? tagVersion : '' } private function generateTagVersion(): string { return (string) hrtime(true) } }


このアプローチは便利で普遍的です。タグ付きキャッシュにより、無効化するすべてのキーを手動で指定する必要がなくなり、プロセスが自動化されます。ただし、タグ バージョン データを保存し、リクエストごとにその有効性を確認するという追加のリソースが必要になります。


キャッシュ サービスが高速で、サイズが厳しく制限されていない場合、このアプローチはパフォーマンスに大きな影響を与えません。負荷を最小限に抑えるには、タグ付きキャッシュとローカル キャッシュ メカニズムを組み合わせることができます。


このように、タグ付きキャッシュは無効化を簡素化するだけでなく、特に大量の相互接続されたデータを持つ複雑なシステムにおいて、データの操作をより柔軟かつわかりやすくします。