paint-brush
DIY-getagde cachedoor@ayacaste
588 lezingen
588 lezingen

DIY-getagde cache

door Anton Musatov7m2024/12/09
Read on Terminal Reader

Te lang; Lezen

Ontwikkelaars maken vaak grapjes dat programmeren twee grote uitdagingen kent: het benoemen van variabelen en cache-invalidatie. Deze grap is niet ver van de waarheid: het beheren van caches, met name hun invalidatie, kan inderdaad een serieuze taak worden. In dit artikel leg ik uit hoe je eenvoudig tagged cache-functionaliteit implementeert op basis van een bestaande cacheservice. Stel je voor dat we een systeem hebben waarin gebruikers artikelen toevoegen. Voor elke gebruiker tonen we statistieken over hun artikelen in hun persoonlijke dashboard: het aantal artikelen, gemiddeld aantal woorden, publicatiefrequentie, enz. Om het systeem te versnellen, cachen we deze gegevens. Voor elk rapport wordt een unieke cachesleutel gemaakt. De vraag rijst: hoe kunnen we dergelijke caches ongeldig maken wanneer gegevens veranderen?
featured image - DIY-getagde cache
Anton Musatov HackerNoon profile picture


Ontwikkelaars grappen vaak dat programmeren twee grote uitdagingen kent:

  • variabelen benoemen
  • cache-invalidatie


Deze grap is niet ver van de waarheid: het beheren van caches, met name het ongeldig verklaren ervan, kan inderdaad een serieuze taak worden. In dit artikel leg ik uit hoe je eenvoudig tagged cache-functionaliteit implementeert op basis van een bestaande caching-service.


Stel je voor dat we een systeem hebben waar gebruikers artikelen toevoegen. Voor elke gebruiker tonen we statistieken over hun artikelen in hun persoonlijke dashboard: het aantal artikelen, gemiddeld aantal woorden, publicatiefrequentie, etc. Om het systeem te versnellen, cachen we deze gegevens. Voor elk rapport wordt een unieke cachesleutel gemaakt.


De vraag rijst: hoe kunnen we zulke caches ongeldig maken als data verandert? Eén aanpak is om de cache handmatig te wissen voor elke gebeurtenis, bijvoorbeeld wanneer er een nieuw artikel wordt toegevoegd:

 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, ]) } }


Deze methode werkt, maar wordt omslachtig als u met een groot aantal rapporten en sleutels werkt. Dit is waar getagde caching van pas komt. Met getagde caching kunnen gegevens niet alleen aan een sleutel worden gekoppeld, maar ook aan een reeks tags. Vervolgens kunnen alle records die aan een specifieke tag zijn gekoppeld, ongeldig worden verklaard, wat het proces aanzienlijk vereenvoudigt.


Een waarde naar de cache schrijven met tags:

 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' ] )


De cache ongeldig maken op basis van tags:

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


Hier vertegenwoordigt de tag 'user_article_cache_tag_' . $user->id wijzigingen in de artikelen van de gebruiker. Het kan worden gebruikt om caches ongeldig te maken die afhankelijk zijn van deze gegevens. Een specifiekere tag 'user_article_report_cache_tag_' . $user->id staat alleen toe dat de rapporten van de gebruiker worden gewist, terwijl een algemene tag 'user_article_report' rapportcaches voor alle gebruikers ongeldig maakt.


Als uw cachebibliotheek geen tagging ondersteunt, kunt u dit zelf implementeren. Het hoofdidee is om de huidige versiewaarden van tags op te slaan, en voor elke getagde waarde de tagversies op te slaan die actueel waren op het moment dat de waarde naar de cache werd geschreven. Wanneer u vervolgens een waarde uit de cache ophaalt, worden de huidige tagversies ook opgehaald en wordt hun geldigheid gecontroleerd door ze te vergelijken.


Een TaggedCache klasse maken

 class TaggedCache { private cacheService: cacheService }


Implementeren van de set -methode voor het schrijven naar de cache met tags. In deze methode moeten we de waarde naar de cache schrijven, en ook de huidige versies van de meegeleverde tags ophalen en deze opslaan die zijn gekoppeld aan de specifieke cachesleutel. Dit wordt bereikt door een extra sleutel te gebruiken met een voorvoegsel dat is toegevoegd aan de meegeleverde sleutel.

 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 }


De get -methode toevoegen om getagde waarden uit de cache op te halen. Hier halen we de waarde op met behulp van de sleutel, evenals de tagversies die aan die sleutel zijn gekoppeld. Vervolgens controleren we de geldigheid van de tags. Als een tag ongeldig is, wordt de waarde uit de cache verwijderd en wordt null geretourneerd. Als alle tags geldig zijn, wordt de gecachte waarde geretourneerd.

 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 } }


Implementatie van de updateTagsVersions -methode om tagversies bij te werken. Hier itereren we over alle opgegeven tags en werken we hun versies bij met bijvoorbeeld de huidige tijd als versie.

 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) } }


Deze aanpak is zowel handig als universeel. Getagde caching elimineert de noodzaak om handmatig alle sleutels voor ongeldigverklaring te specificeren, waardoor het proces wordt geautomatiseerd. Het vereist echter extra resources: het opslaan van tagversiegegevens en het controleren van hun geldigheid bij elke aanvraag.


Als uw caching service snel is en niet zwaar beperkt in omvang, zal deze aanpak de prestaties niet significant beïnvloeden. Om de belasting te minimaliseren, kunt u tagged caching combineren met lokale caching mechanismen.


Op deze manier vereenvoudigt tagged caching niet alleen het ongeldig verklaren, maar zorgt het er ook voor dat het werken met gegevens flexibeler en begrijpelijker wordt, vooral in complexe systemen met grote hoeveelheden onderling verbonden gegevens.