Рефакторинг решения для кэширования, отказ от локального хранилища для кэша в памяти. Рефакторинг инвалидации кеша 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. Кэш обновляется в соответствии с последним доступом при достижении емкости.

Некоторые дополнительные проблемы, с которыми мы столкнулись, включают:

Нормализация данных

Нормализация данных ускоряет доступ к объектам кэша; нормализуя и превращая ответы в хеш-строки с идентификаторами, мы можем хранить объекты кеша как свойства в кеше и иметь более производительное время поиска.

Разрушение

Деструктурируя ответ перед кэшированием, мы можем устранить некоторое дублирование, которое может возникнуть из-за похожих, но немного разных запросов. Мы также можем более эффективно строить наш ответ из наших кэшированных данных, увеличивая частоту попаданий в кэш.

Будущие улучшения

Путь к совершенствованию бесконечен. После завершения нашей итерации мы чувствуем, что есть несколько областей, которые будущие группы могут расширить и улучшить, в том числе:

  1. Внедрение более эффективных алгоритмов кэширования. LFU и LRU служат отличным фундаментальным строительным блоком для целого семейства более производительных алгоритмов кэширования, включая: псевдо-LRU, LFU с динамическим старением, ARC (кэш с адаптивной заменой), Hawkeye и т. д.
  2. Повышение общей производительности приложения за счет перехода с JavaScript/TypeScript на AssemblyScript. Существующие функции кэширования на стороне клиента написаны на Typescript; преобразование их в AssemblyScript должно быть довольно простым и, вероятно, значительно повысит производительность.
  3. Обновление и оптимизация инструмента разработчика для совместимости с новыми политиками кэширования на стороне клиента. Существующий инструмент разработчика устарел и работает некорректно; вероятно, стоит попытаться реорганизовать существующее решение или создать новый инструмент разработчика с нуля.
  4. Рефакторинг кэширования на стороне сервера, возможно, отделение от Redis для обеспечения расширяемости в будущем. Кэширование на стороне сервера в его нынешнем виде немного глючит и работает не полностью, как предполагалось. Он полностью зависит от Redis, и, по нашему мнению, имеет смысл отделить Redis от существующего кэширования на стороне сервера и создать собственные алгоритмы кэширования Obsidian, аналогичные алгоритмам на стороне клиента.

Другие статьи об Obsidian:

Обсидиан 1.0
Обсидиан 2.0
Обсидиан 3.0
Обсидиан 3.1
Обсидиан 3.2
Обсидиан 4.0
Обсидиан Техническое описание 4.0
Техническое описание Obsidian 6.0

Соавтор:

Кевин Хуанг | Гитхаб | ЛинкедИн

Алекс Лопес | Гитхаб | ЛинкедИн

Райан Ранджбаран | Гитхаб | ЛинкедИн

Мэтью Вайскер | Гитхаб | ЛинкедИн