Исправлено недостаточное и чрезмерное количество ответов из кеша за счет улучшения стратегии аннулирования кеша и добавления возможности обработки выборочных запросов. Инструмент разработчика перенесен с манифеста версии 2 на версию 3.

Введение
Obsidian — это первое решение Deno для кэширования GraphQL, обеспечивающее кэширование на стороне клиента для компонентов React с использованием кеша браузера и кэширование на стороне сервера для маршрутизаторов Oak с использованием кеша Redis.
Предыдущие версии Obsidian имели несколько недостатков, которые мы постарались исправить в этом обновлении. Некоторыми из этих недостатков были чрезмерный ответ, недостаточный ответ и устаревшее расширение инструмента разработчика.
Неполный ответ: если есть запрос на чтение из базы данных, за которым следует мутация, которая добавляет, тот же запрос на чтение будет отвечать из кэша с неполными данными.
Решение. Мы реализовали рабочую стратегию аннулирования кеша для добавления мутаций, удалив все затронутые запросы в кеше Redis. Это гарантирует, что Obsidian сделает вызов GraphQL к базе данных для этих запросов.
Чрезмерное количество ответов: если запрос кэшируется, а последующий запрос запрашивает меньшее количество полей из первого, процесс преобразования обманывает Obsidian, заставляя думать, что два ответа должны быть одинаковыми. Это связано с тем, что Obsidian не может отличить ссылку «~7~Movie», которая включает свойство «Год выпуска», и ссылку, которая его не включает. То, что он всегда кэшировал, - это тот, у которого больше полей.
Решение. Чтобы исправить чрезмерные ответы, Obsidian теперь более детально сохраняет запросы в кэше, позволяя выполнять выборочные запросы и возвращая только те данные, которые запросил пользователь.
Ниже приведен график, показывающий, как Obsidian 6.0 обрабатывает запросы и кэширует данные:

Наконец, инструмент разработчика расширений Chrome был перенесен с версии 2 манифеста на версию 3.
Инвалидация кеша
Проблема:
При аннулировании кеша возникла проблема с недостаточным ответом. При добавлении мутаций затронутые запросы, которые были сохранены в кеше, не будут обновляться. Затем Obsidian не ответила из кеша с неполными данными.
Пример: Obsidian выполняет запрос GraphQL для установок с низким уровнем обслуживания. Этот ответ сохраняется в кэше Redis, а его ссылка сохраняется в виде следующей строки запроса:
“{\n plants(input: {maintenance: \”Low\”}) {\n id\n name\n size\n }\n}\n”: “\”{\\\”~Plant~1\\\”:{},\\\”~Plant~2\\\”:{},\\\”~Plant~4\\\”:{},\\\”~Plant~5\\\”:{},\\\”~Plant~6\\\”:{},\\\”~Plant~9\\\”:{}}\””
Если для установки с низким уровнем обслуживания произойдет добавление мутации, Obsidian обновит базу данных GraphQL и добавит индивидуальную ссылку в кеш:
Хотя добавленный завод был кэширован отдельно, исходный сохраненный запрос для растений с низким уровнем обслуживания остался без изменений:
“{\n plants(input: {maintenance: \”Low\”}) {\n id\n name\n size\n }\n}\n”: “\”{\\\”~Plant~1\\\”:{},\\\”~Plant~2\\\”:{},\\\”~Plant~4\\\”:{},\\\”~Plant~5\\\”:{},\\\”~Plant~6\\\”:{},\\\”~Plant~9\\\”:{}}\””
Решение:
Obsidian 5.0 уже мог определить, является ли входящий запрос мутацией добавления, но не мог сопоставить эту мутацию с затронутыми таблицами, которые он уже кэшировал. Чтобы решить эту проблему, мы добавили конфигурацию, которая сопоставляет типы добавления мутаций с таблицами, на которые они влияют.
Разработчик, внедряющий Obsidian, должен сконфигурировать собственную мутациюTableMap: объект с добавлением типов мутаций в качестве ключей и их затронутых таблиц в качестве значений.
const GraphQLRouter = await ObsidianRouter<ObsRouter>({
Router,
// context: () => console.log('hi, Cameron'),
typeDefs: types,
resolvers: resolvers,
redisPort: 6379,
useCache: true,
usePlayground: true,
// fields used to create the custom entries in the database
customIdentifier: ['__typename', 'id'],
mutationTableMap: { addPlant: 'plants' },
});
Последнее свойство внутри GraphQLRouter — это пример настроенной мутацииTableMap, которая работает для демонстрации растений Obsidian. Как только разработчик настроит свою собственную карту, обновленная стратегия аннулирования кеша Obsidian выявит затронутые таблицы и удалит любые ссылки на них после их соответствующих мутаций добавления. Вот обновленная логика в экспортированной функции cacheInvalidation:
//mutationTableMap, queryString, and normalizedMutation are parameters that will be passed into the cacheInvalidation function.
let normalizedData: object;
let cachedVal: any;
// Common case is that we get one mutation at a time. But it's possible to group multiple mutation queries into one. That's why the for loop is needed
for (const redisKey in normalizedMutation) {
normalizedData = normalizedMutation[redisKey];
cachedVal = await cache.cacheReadObject(redisKey);
//Logic to check for delete mutation is omitted for conciseness. If it's inferred that the mutation type is not delete…
else {
if (cachedVal === undefined) { // checks if add mutation
let ast = gql(queryString);
const mutationType =
ast.definitions[0].selectionSet.selections[0].name.value; // Extracts mutationType from query string
const staleRefs: Array<string> = mutationTableMap[mutationType]; // Grabs array of affected data tables from dev specified mutationTableMap
const rootQueryContents = await redisdb.hgetall('ROOT_QUERY'); // Creates array of all query keys and values in ROOT_QUERY from Redis
for (let j = 0; j < staleRefs.length; j++) { // Checks for all query keys that refer to the affected tables and deletes them from Redis
for (let i = 0; i < rootQueryContents.length; i += 2) {
if (
staleRefs[j] === rootQueryContents[i].slice(0, staleRefs[j].length)
) {
redisdb.hdel('ROOT_QUERY', rootQueryContents[i]);
}
}
}
}
await cache.cacheWriteObject(redisKey, normalizedData); // Adds or updates reference in redis cache
}
}
Объект normalizedMutation — это параметр, который уже будет передан в функцию в более читаемом формате. Обычно каждый объект имеет только одну мутацию, но использование цикла for… in будет учитывать несколько типов мутаций. Переменная cachedVal проверяет, существует ли кеш, прочитанный в redisKey(s) normalizedMutation, и неопределенное возвращаемое значение указывает на добавленную мутацию.
При работе с мутациями добавления Obsidian построит абстрактное синтаксическое дерево (let ast = gql(queryString)), используя соответствующую строку запроса, переданную в функцию. Будучи сильно внедренным объектом, AST предоставляет доступ к типу мутации (const mutationType). Используя мутациюTableMap, Obsidian теперь может циклически просматривать запросы в своем кеше, определять, какие из них затронуты мутацией, и удалять эти ссылки из кеша. После удаления Obsidian будет знать, что нужно ответить обновленными данными из базы данных GraphQL, прежде чем сохранять их в кеше.
Выборочный запрос
Проблема:
Предыдущие версии Obsidian сохраняли строки запросов GraphQL в кэш без изменений, например:
Запрос GraphQL для всех растений, помеченных как нетребовательные к обслуживанию, будет выглядеть так:
{
query
{
plants(input: {maintenance:low}
{
name
id
size
}
}
}
Ответ вернется как:
{
"data": {
"plants": [
{
"id": "1",
"name": "Whale Fin Sansevieria",
"size": "Medium"
},
{
"id": "2",
"name": "Bamboo Palm",
"size": "Large"
},
{
"id": "4",
"name": "Bromeliad Pineapple",
"size": "Medium"
},
{
"id": "5",
"name": "Jade Plant",
"size": "Small"
},
{
"id": "6",
"name": "Red Prayer Plant",
"size": "Small"
},
{
"id": "9",
"name": "Monstera",
"size": "Large"
}
]
}
}
Каждое растение будет сохранено как отдельная пара ключ/значение в Redis, на которую будет ссылаться кешированный запрос;
Запрос будет сохранен как пара ключ/значение Redis:
“{\n plants(input: {maintenance: \”Low\”}) {\n id\n name\n size\n }\n}\n”: “\”{\\\”~Plant~1\\\”:{},\\\”~Plant~2\\\”:{},\\\”~Plant~4\\\”:{},\\\”~Plant~5\\\”:{},\\\”~Plant~6\\\”:{},\\\”~Plant~9\\\”:{}}\””
Сохранение запроса в виде необработанной строки снижает вероятность попадания в кеш, поскольку запрос должен точно совпадать с кешированной строкой.
Запрос только поля «имя» со всех малообслуживаемых заводов приведет к промаху в кэше, потому что строка запроса не будет точно соответствовать тому, что сохранено, даже если вся необходимая информация кэшируется.
Решение:
Переформатирование способа сохранения запроса в кеше, а также разбиение запроса GraphQL на отдельные части позволяют более детально извлекать данные из кеша.
Пример:
Беря наш вышеприведенный пример, Obsidian 6.0 берет запрос GraphQL и извлекает соответствующий текст из запроса для сохранения в кэше путем обхода абстрактного синтаксического дерева. Таким образом, запрос на растения с пометкой «низкие эксплуатационные расходы» теперь сохраняется в Redis с парой ключ/значение:
“plants:maintenance:Low”: “\”{\\\”~Plant~1\\\”:{},\\\”~Plant~2\\\”:{},\\\”~Plant~4\\\”:{},\\\”~Plant~5\\\”:{},\\\”~Plant~6\\\”:{},\\\”~Plant~9\\\”:{}}\””
Обратите внимание на гораздо более удобочитаемую строку ключей, которая используется для попадания в кеш. Значение по-прежнему является ссылкой на каждое значение завода, сохраненное как собственный ключ в Redis.
Но есть еще один шаг, который теперь предотвращает чрезмерный ответ данных. Теперь запрос GraphQL, запрашивающий только «имя» всех малообслуживаемых растений, вернет это конкретное поле из кеша. Это достигается путем обхода абстрактного синтаксического дерева и извлечения полей, запрошенных в запросе GraphQL. Затем при доступе к кешу пользователю возвращаются только эти поля.
Исправления DevTool
Obsidian Developer Tool — это расширение для Chrome, предназначенное для приложений, использующих Obsidian. Короче говоря, это расширение дает пользователям возможность визуализировать показатели производительности запросов и кеша, отправлять динамические запросы и мутации, а также просматривать и удалять данные из кеша браузера.
При первоначальной настройке последняя версия Dev Tool загружала время отклика производительности для запросов, но не реагировала на какие-либо функции кэша и игровой площадки, что означает, что пользователь не сможет визуализировать какие-либо кэшированные данные или писать новые запросы GraphQL.
Чтобы исправить это, мы обновили версию манифеста с V2 до V3. Manifest V3 обеспечивает ключевые улучшения конфиденциальности, безопасности и производительности расширения в целом. V3 позволяет улучшить пользовательский контроль над разрешениями, информируя пользователей о том, что делают расширения, и позволяя им предоставлять разрешения во время выполнения и в контексте. С точки зрения производительности Dev Tool теперь может работать без сбоев, даже если на одном устройстве установлено множество расширений.
Были также недопустимые импорты модулей Bootstrap, которые необходимо было удалить, а также несколько экземпляров зависимостей CodeMirror, которые были разрешены, чтобы Dev Tool работал с полной функциональностью.
Идеи по улучшению
- Оптимизируйте выборочные запросы с помощью пользовательских запросов GraphQL для полей, отсутствующих в кеше.
В случае запроса, который запрашивает данные, которые частично хранятся в кеше, вместо полного вызова GraphQL для всех данных было бы оптимально создать собственный вызов базы данных только для тех данных, которые еще не сохранены. хранится в кэше. Затем Obsidian может объединить ответ из кеша с менее дорогим пользовательским вызовом и вернуть его пользователю, повысив производительность.
2. Возможно, будет возможно устранить необходимость в настройке MutationTableMap разработчиком.
Мы добавили мутациюTableMap, чтобы легко указать, какие таблицы данных затронуты каждой мутацией добавления, но, учитывая, что связь между каждой мутацией добавления и таблицами, к которым она добавляется, уже определена, вероятно, можно извлечь эти затронутые таблицы без жестких -кодирование объекта MutationTableMap.
3. Рефакторинг и оптимизация инструмента разработчика Obsidian
В настоящее время инструмент разработчика для Obsidian имеет некоторые компоненты, которые еще не полностью работают. Площадка в инструменте разработчика Obsidian не работает должным образом, несмотря на то, что конечная точка GraphQL работает. Также пока не реализован компонент Настройки, добавлена только иконка на инструменте разработчика.
Другие статьи об Obsidian:
Обсидиан 1.0
Обсидиан 2.0
Обсидиан 3.0
Обсидиан 3.1
Обсидиан 3.2
Обсидиан 4.0
Обсидиан 4.0 Техническое описание
Соавтор:
Дерек Окуно | Гитхаб | ЛинкедИн
Лиам Джонсон | Гитхаб | ЛинкедИн