Рефакторинг решения для кэширования, отказ от локального хранилища для кэша в памяти. Рефакторинг инвалидации кеша LFU и реализация LRU с нуля, предлагая пользователям гибкость и расширяемость групп итераций в будущем.
Введение
Obsidian — это первое решение Deno для кэширования GraphQL, обеспечивающее кэширование на стороне клиента для компонентов React с использованием собственного кэша LFU или LRU и кэширование на стороне сервера для маршрутизаторов Oak с использованием Redis.
Проблемы и решения
Мы решили сосредоточиться на построении и усовершенствовании клиентского решения для кэширования, предоставляемого Obsidian. Предыдущие итерации оставили нам несколько проблем с кэшированием на стороне клиента, в том числе:
BrowserCache, setState и localStorage
Проблема. Кэширование на стороне клиента требовало от пользователя обновления кеша вручную во внешнем интерфейсе с помощью setState. Это не идеально как потому, что кеш и операция кэширования должны быть скрыты от внешнего интерфейса, так и потому, что раскрытие кеша и функции setCache таким образом может представлять угрозу безопасности.
Пример:
import { useObsidian, BrowserCache } from 'https://deno.land/x/obsidian/clientMod.ts'; const MovieApp = () => { const { query, cache, setCache } = useObsidian(); const [movies, setMovies] = (React as any).useState(''); ... return ( <h1>{movies}</h1> <button onClick={() => { query(queryStr) .then(resp => setMovies(resp.data)) .then(resp => setCache(new BrowserCache(cache.storage))) }} >Get Movies</button> ); };
Решение. Рефакторинг функции запроса, чтобы пользователю больше не приходилось вручную устанавливать состояние с помощью нового объекта BrowserCache; вместо этого пользователь просто импортирует useObsidian и функцию запроса. Когда пользователь делает запрос, полученный ответ обновляет внешний компонент, а кэширование остается за кулисами без вмешательства пользователя. Таким образом, Cache, setCache и BrowserCache больше не доступны.
Проблема.Кэширование на стороне клиента выполняется с использованием localStorage; хотя это имеет преимущество сохранения между сеансами, localStorage обращается к жесткому диску и, следовательно, медленнее, чем решение в памяти.
Решение.Кэш LFU на стороне клиента уже был реализован, но он не использовался, поэтому мы провели рефакторинг оболочки Obsidian, чтобы заменить им BrowserCache. LFU использует объект Javascript в памяти, что позволяет избежать необходимости доступа к жесткому диску. Кроме того, мы провели рефакторинг и отладку кэша LFU на стороне клиента и позаботились об обработке крайних случаев, сделав его более стабильным.
Инвалидация кеша
Проблема. Аннулирование кеша — это процесс удаления данных из кеша, которые больше не действительны или неактуальны. Предыдущая стратегия аннулирования кеша работала некорректно в BrowserCache.
Решение.Убрано использование BrowserCache из ObsidianWrapper и вместо этого используется кэш LFU на стороне клиента, который мы подробнее обсудим в следующем разделе.
Проблема: LFU, алгоритм кэширования, основанный на частоте доступа, не является самым оптимальным алгоритмом кэширования для многих случаев использования; LRU также должен быть доступен в качестве выбора алгоритма кэширования.
Решение. Реализована функция кэширования LRU на стороне клиента, которая вместо этого кеширует на основе давности, о чем мы поговорим подробнее в следующем разделе.
Политики удаления кэша
Наименее часто используемые (LFU)
Мы использовали хэш частоты для отслеживания количества вызовов запроса, аннулируя наименее часто используемые запросы при достижении емкости. В нашей реализации мы использовали объект ROOT_QUERY, а также объект ROOT_MUTATION для отслеживания запросов и мутаций. Для дедупликации записей мы использовали реализованные функции нормализации и деструктуризации, чтобы разбить возвращенный ответ из нашего запроса GraphQL и сохранить его как запись в кеше. Кэш обновляется в соответствии с частотой доступа при достижении емкости.
Наименее недавно использовавшиеся (LRU)
Мы использовали связанный список для отслеживания давности вызова запроса, добавляя последний использовавшийся запрос в конец связанного списка и удаляя последний использовавшийся запрос из головы при достижении емкости. Подобно LFU, объекты ROOT_QUERY и ROOT_MUTATION отслеживают запросы и мутации, а функции нормализации и деструктуризации разбивают возвращаемый ответ из нашего запроса GraphQL. Кэш обновляется в соответствии с последним доступом при достижении емкости.
Некоторые дополнительные проблемы, с которыми мы столкнулись, включают:
Нормализация данных
Нормализация данных ускоряет доступ к объектам кэша; нормализуя и превращая ответы в хеш-строки с идентификаторами, мы можем хранить объекты кеша как свойства в кеше и иметь более производительное время поиска.
Разрушение
Деструктурируя ответ перед кэшированием, мы можем устранить некоторое дублирование, которое может возникнуть из-за похожих, но немного разных запросов. Мы также можем более эффективно строить наш ответ из наших кэшированных данных, увеличивая частоту попаданий в кэш.
Будущие улучшения
Путь к совершенствованию бесконечен. После завершения нашей итерации мы чувствуем, что есть несколько областей, которые будущие группы могут расширить и улучшить, в том числе:
- Внедрение более эффективных алгоритмов кэширования. LFU и LRU служат отличным фундаментальным строительным блоком для целого семейства более производительных алгоритмов кэширования, включая: псевдо-LRU, LFU с динамическим старением, ARC (кэш с адаптивной заменой), Hawkeye и т. д.
- Повышение общей производительности приложения за счет перехода с JavaScript/TypeScript на AssemblyScript. Существующие функции кэширования на стороне клиента написаны на Typescript; преобразование их в AssemblyScript должно быть довольно простым и, вероятно, значительно повысит производительность.
- Обновление и оптимизация инструмента разработчика для совместимости с новыми политиками кэширования на стороне клиента. Существующий инструмент разработчика устарел и работает некорректно; вероятно, стоит попытаться реорганизовать существующее решение или создать новый инструмент разработчика с нуля.
- Рефакторинг кэширования на стороне сервера, возможно, отделение от Redis для обеспечения расширяемости в будущем. Кэширование на стороне сервера в его нынешнем виде немного глючит и работает не полностью, как предполагалось. Он полностью зависит от Redis, и, по нашему мнению, имеет смысл отделить Redis от существующего кэширования на стороне сервера и создать собственные алгоритмы кэширования Obsidian, аналогичные алгоритмам на стороне клиента.
Другие статьи об Obsidian:
Обсидиан 1.0
Обсидиан 2.0
Обсидиан 3.0
Обсидиан 3.1
Обсидиан 3.2
Обсидиан 4.0
Обсидиан Техническое описание 4.0
Техническое описание Obsidian 6.0
Соавтор:
Кевин Хуанг | Гитхаб | ЛинкедИн
Алекс Лопес | Гитхаб | ЛинкедИн