paint-brush
DIY Tagged Cacheза@ayacaste
559 показання
559 показання

DIY Tagged Cache

за Anton Musatov7m2024/12/09
Read on Terminal Reader

Надто довго; Читати

Розробники часто жартують, що програмування має дві основні проблеми: іменування змінних і анулювання кешу. Цей жарт недалекий від істини: управління кешами, особливо їх недійсністю, справді може стати серйозним завданням. У цій статті я поясню, як легко реалізувати функцію тегованого кешу на основі існуючої служби кешування. Уявіть, що у нас є система, в якій користувачі додають статті. Для кожного користувача ми відображаємо статистику щодо його статей на особистій панелі: кількість статей, середню кількість слів, частоту публікацій тощо. Щоб прискорити роботу системи, ми кешуємо ці дані. Для кожного звіту створюється унікальний ключ кешу. Виникає питання: як зробити такі кеші недійсними при зміні даних?
featured image - DIY Tagged Cache
Anton Musatov HackerNoon profile picture


Розробники часто жартують, що програмування має дві основні проблеми:

  • іменування змінних
  • недійсність кешу


Цей жарт недалекий від істини: управління кешами, особливо їх недійсністю, справді може стати серйозним завданням. У цій статті я поясню, як легко реалізувати функцію тегованого кешу на основі існуючої служби кешування.


Уявіть, що у нас є система, в якій користувачі додають статті. Для кожного користувача ми відображаємо статистику щодо його статей на особистій панелі: кількість статей, середню кількість слів, частоту публікацій тощо. Щоб прискорити роботу системи, ми кешуємо ці дані. Для кожного звіту створюється унікальний ключ кешу.


Виникає питання: як зробити такі кеші недійсними при зміні даних? Один із підходів полягає в тому, щоб вручну очистити кеш для кожної події, наприклад, коли додається нова стаття:

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


Такий підхід і зручний, і універсальний. Кешування з тегами позбавляє від необхідності вручну вказувати всі ключі для визнання недійсними, автоматизуючи процес. Однак для цього потрібні додаткові ресурси: зберігання даних версії тегів і перевірка їх дійсності з кожним запитом.


Якщо ваша служба кешування є швидкою та не має значних обмежень у розмірі, цей підхід не вплине істотно на продуктивність. Щоб мінімізувати навантаження, ви можете поєднати кешування з тегами з механізмами локального кешування.


Таким чином, кешування з тегами не тільки спрощує визнання недійсними, але й робить роботу з даними більш гнучкою та зрозумілою, особливо в складних системах із великими обсягами взаємопов’язаних даних.

L O A D I N G
. . . comments & more!

About Author

Anton Musatov HackerNoon profile picture
Anton Musatov@ayacaste
Software Engineer

ПОВІСИТИ БИРКИ

ЦЯ СТАТТЯ БУЛА ПРЕДСТАВЛЕНА В...