Imaginați-vă că avem un sistem în care utilizatorii adaugă articole. Pentru fiecare utilizator, afișăm statistici despre articolele sale în tabloul de bord personal: numărul de articole, numărul mediu de cuvinte, frecvența publicării etc. Pentru a accelera sistemul, memorăm aceste date în cache. Pentru fiecare raport este creată o cheie cache unică.
Apare întrebarea: cum să invalidăm astfel de cache atunci când datele se modifică? O abordare este să ștergeți manual memoria cache pentru fiecare eveniment, de exemplu, atunci când este adăugat un articol nou:
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, ]) } }
Această metodă funcționează, dar devine greoaie atunci când aveți de-a face cu un număr mare de rapoarte și chei. Aici este utilă stocarea în cache cu etichete. Memorarea în cache cu etichete permite ca datele să fie asociate nu numai cu o cheie, ci și cu o serie de etichete. Ulterior, toate înregistrările asociate cu o anumită etichetă pot fi invalidate, simplificând semnificativ procesul.
Scrierea unei valori în cache cu etichete:
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' ] )
Invalidarea memoriei cache prin etichete:
class UpdateCacheTagsOnArticleCreated { public function handle(event: ArticleCreatedEvent): void { this->taggedCacheService->updateTagsVersions([ 'user_article_cache_tag_' . user->id, ]) } }
Aici, eticheta 'user_article_cache_tag_' . $user->id
reprezintă modificări în articolele utilizatorului. Poate fi folosit pentru a invalida orice cache care depinde de aceste date. O etichetă mai specifică 'user_article_report_cache_tag_' . $user->id
permite ștergerea doar a rapoartelor utilizatorului, în timp ce o etichetă generală 'user_article_report'
invalidează memoria cache a rapoartelor pentru toți utilizatorii.
Dacă biblioteca dvs. de stocare în cache nu acceptă etichetarea, o puteți implementa singur. Ideea principală este de a stoca valorile versiunii curente ale etichetelor, precum și pentru fiecare valoare etichetată, pentru a stoca versiunile de etichete care erau curente în momentul în care valoarea a fost scrisă în cache. Apoi, la preluarea unei valori din cache, sunt preluate și versiunile de etichete curente, iar validitatea acestora este verificată prin compararea lor.
Crearea unei clase TaggedCache
class TaggedCache { private cacheService: cacheService }
Implementarea metodei set
pentru scrierea în cache cu etichete. În această metodă, trebuie să scriem valoarea în cache, precum și să recuperăm versiunile curente ale etichetelor furnizate și să le salvăm asociate cu cheia cache specifică. Acest lucru se realizează prin utilizarea unei chei suplimentare cu un prefix adăugat la cheia furnizată.
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 }
Adăugarea metodei get
pentru a prelua valorile etichetate din cache. Aici, recuperăm valoarea utilizând cheia, precum și versiunile de etichete asociate cu cheia respectivă. Apoi verificăm valabilitatea etichetelor. Dacă orice etichetă este invalidă, valoarea este ștearsă din cache și este returnat null
. Dacă toate etichetele sunt valide, se returnează valoarea din cache.
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 } }
Implementarea metodei updateTagsVersions
pentru a actualiza versiunile de etichete. Aici, repetăm toate etichetele furnizate și le actualizăm versiunile folosind, de exemplu, ora curentă ca versiune.
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) } }
Această abordare este atât convenabilă, cât și universală. Memorarea în cache cu etichete elimină necesitatea de a specifica manual toate cheile pentru invalidare, automatizând procesul. Cu toate acestea, necesită resurse suplimentare: stocarea datelor despre versiunea etichetei și verificarea validității acestora cu fiecare solicitare.
Dacă serviciul dvs. de stocare în cache este rapid și nu este foarte limitat în dimensiune, această abordare nu va afecta semnificativ performanța. Pentru a minimiza încărcarea, puteți combina memorarea în cache cu etichete cu mecanisme locale de stocare în cache.
În acest fel, memorarea în cache cu etichete nu numai că simplifică invalidarea, ci și face ca lucrul cu date să fie mai flexibil și mai ușor de înțeles, în special în sistemele complexe cu cantități mari de date interconectate.