paint-brush
Caché etiquetado DIYpor@ayacaste
195 lecturas Nova historia

Caché etiquetado DIY

por Anton Musatov7m2024/12/09
Read on Terminal Reader

Demasiado longo; Ler

Os desenvolvedores adoitan bromear con que a programación ten dous desafíos principais: nomear variables e invalidar a caché. Esta broma non está lonxe da realidade: xestionar cachés, especialmente a súa invalidación, pode converterse en realidade nunha tarefa seria. Neste artigo, explicarei como implementar facilmente a funcionalidade de caché etiquetada baseada nun servizo de caché existente. 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?
featured image - Caché etiquetado DIY
Anton Musatov HackerNoon profile picture


Os desenvolvedores adoitan bromear dicindo que a programación ten dous desafíos principais:

  • nomear variables
  • invalidación da caché


Esta broma non está lonxe da realidade: xestionar cachés, especialmente a súa invalidación, pode converterse en realidade nunha tarefa seria. Neste artigo, explicarei como implementar facilmente a funcionalidade de caché etiquetada baseada nun servizo de caché existente.


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.