Imaxina que temos un sistema onde os usuarios engaden artigos. Para cada usuario, amosamos estatísticas sobre os seus artigos no seu panel persoal: número de artigos, número medio de palabras, frecuencia de publicación, etc. Para axilizar o sistema, almacenamos estes datos na memoria caché. Créase unha clave de caché única para cada informe.
Xorde a pregunta: como invalidar tales cachés cando os datos cambian? Un enfoque é borrar manualmente a caché de cada evento, por exemplo, cando se engade un artigo novo:
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, ]) } }
Este método funciona pero vólvese complicado cando se trata cunha gran cantidade de informes e claves. Aquí é onde o caché etiquetado é útil. O almacenamento en caché etiquetado permite que os datos se asocien non só cunha clave senón tamén cunha matriz de etiquetas. Posteriormente, todos os rexistros asociados a unha etiqueta específica poden ser invalidados, simplificando significativamente o proceso.
Escribindo un valor na caché con etiquetas:
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' ] )
Invalidando a caché mediante etiquetas:
class UpdateCacheTagsOnArticleCreated { public function handle(event: ArticleCreatedEvent): void { this->taggedCacheService->updateTagsVersions([ 'user_article_cache_tag_' . user->id, ]) } }
Aquí, a etiqueta 'user_article_cache_tag_' . $user->id
representa cambios nos artigos do usuario. Pódese usar para invalidar calquera caché dependente destes datos. Unha etiqueta máis específica 'user_article_report_cache_tag_' . $user->id
só permite borrar os informes do usuario, mentres que unha etiqueta xeral 'user_article_report'
invalida as memorias caché de informes para todos os usuarios.
Se a súa biblioteca de caché non admite a etiquetaxe, pode implementala vostede mesmo. A idea principal é almacenar os valores da versión actual das etiquetas, así como para cada valor etiquetado, para almacenar as versións das etiquetas que estaban actuais no momento en que se escribiu o valor na caché. Despois, ao recuperar un valor da caché, tamén se recuperan as versións actuais das etiquetas e compróbase a súa validez comparándoas.
Creando unha clase TaggedCache
class TaggedCache { private cacheService: cacheService }
Implementación do método set
para escribir na caché con etiquetas. Neste método, necesitamos escribir o valor na caché, así como recuperar as versións actuais das etiquetas proporcionadas e gardalas asociadas á clave de caché específica. Isto conséguese usando unha chave adicional cun prefixo engadido á chave proporcionada.
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 }
Engadindo o método get
para recuperar os valores etiquetados da caché. Aquí, recuperamos o valor usando a clave, así como as versións de etiquetas asociadas a esa clave. Despois comprobamos a validez das etiquetas. Se algunha etiqueta non é válida, elimínase o valor da caché e devólvese null
. Se todas as etiquetas son válidas, devólvese o valor almacenado na caché.
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 } }
Implementación do método updateTagsVersions
para actualizar as versións das etiquetas. Aquí, iteramos todas as etiquetas proporcionadas e actualizamos as súas versións usando, por exemplo, a hora actual como versión.
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) } }
Este enfoque é conveniente e universal. O caché etiquetado elimina a necesidade de especificar manualmente todas as claves para a súa invalidación, automatizando o proceso. Non obstante, require recursos adicionais: almacenar os datos da versión da etiqueta e comprobar a súa validez con cada solicitude.
Se o teu servizo de almacenamento na caché é rápido e non ten un tamaño moi limitado, este enfoque non afectará significativamente o rendemento. Para minimizar a carga, pode combinar o caché etiquetado con mecanismos de caché locais.
Deste xeito, o almacenamento en caché etiquetado non só simplifica a invalidación, senón que tamén fai que o traballo con datos sexa máis flexible e comprensible, especialmente en sistemas complexos con grandes cantidades de datos interconectados.