Persisted queries (сохранённые запросы) — это механизм, при котором клиент отправляет серверу не полный текст GraphQL-запроса, а его уникальный идентификатор. Сервер хранит валидный запрос и его схему заранее. При получении ID сервер извлекает готовый запрос из кэша, проверяет права доступа и выполняет его. Это снижает нагрузку на сеть, ускоряет обработку и защищает API от инъекций через некорректные синтаксисы.
Что такое persisted queries и зачем они нужны в продакшене
Традиционный GraphQL-клиент формирует строку с операциями (например, `query { user(id: $id) { name } }`) и отправляет её. Сервер должен каждый раз парсить эту строку, проверять синтаксис и сопоставлять поля со схемой. При высокой частоте запросов это создаёт нагрузку на процессоры сервера.
С Persisted queries клиент отправляет только ID (например, `12345`). Сервер имеет таблицу или кэш, где хранится пара «ID — текст запроса». Если ID известен и зарегистрирован, сервер пропускает этап парсинга. Это экономит ресурсы и ускоряет ответ.
Безопасность: если злоумышленник попытается отправить запрос с вредоносным синтаксисом или некорректными полями, сервер отклонит его, так как этот ID не существует в реестре. Это предотвращает некоторые классы атак на GraphQL-эндпоинты.
Управление версиями: при изменении схемы базы данных (например, поле `email` стало обязательным) вы можете обновить сохранённый запрос на сервере. Клиент продолжит работать без изменений кода, пока использует старый ID, но получит ошибку валидации, если попытается выполнить несуществующий ID. Это позволяет контролировать миграции.
Ограничение трафика: мобильные приложения часто работают при плохом соединении. Передача короткого ID вместо 500-1000 символов текста запроса экономит мегабайты данных и снижает задержки.
Как настроить persisted queries на примере Apollo Client или React Query
Настройка требует изменений как на стороне сервера, так и в клиентском коде. Процесс состоит из трёх этапов: регистрация запросов, настройка клиента и обработка ошибок.
Этап 1: Регистрация на сервере. Вам нужно создать эндпоинт или админ-панель для регистрации новых операций. Например, вы можете использовать `PersistedQueryLayer` в Apollo Server или настроить таблицу в базе данных. Для каждого нового запроса генерируется уникальный ID (часто это хэш от текста запроса).
Этап 2: Настройка клиента. В Apollo Client используется пакет `@apollo/client/link/persisted-queries`. Вы подключаете линк, указывая URL эндпоинта регистрации (`persistedQueryEndpoint`). Клиент автоматически отправляет этот ID вместе с запросом.
Пример кода для Apollo Client:
«`javascript
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';
const persistedQueryLink = createPersistedQueryLink({
uri: 'https://api.example.com/graphql',
});
const client = new ApolloClient({
link: persistedQueryLink,
cache: new InMemoryCache(),
});
«`
В этом примере `uri` указывает на эндпоинт, который принимает ID и возвращает ответ. Если запрос не зарегистрирован, сервер должен вернуть ошибку 400 или 404.
Этап 3: Работа с ошибками. При первом запуске приложения клиент может не знать ID новых операций. Apollo Client автоматически отправляет полный текст запроса в эндпоинт регистрации. Сервер должен ответить JSON-объектом с полем `version` и текстом запроса, который клиент сохранит в кэш.
Если сервер не поддерживает автоматическую регистрацию, вам нужно вручную прописать все ID в коде клиента или использовать админ-панель для их добавления.
Критерии выбора: когда использовать persisted queries, а когда нет
Используйте persisted queries, если:
1. У вас есть фиксированный набор операций (например, список пользователей, товары, заказы). Эти запросы не меняются часто.
2. Вы работаете с мобильными приложениями или IoT-устройствами, где важен каждый байт трафика.
3. Вам нужна централизованная защита от инъекций и контроль версий запросов.
4. Сервер имеет высокую нагрузку, и оптимизация парсинга критична для производительности.
Не используйте persisted queries, если:
1. Клиентские запросы динамически формируются на основе пользовательского ввода (например, поиск по произвольным параметрам). В таких случаях ID будет меняться слишком часто, и механизм регистрации не сработает.
2. У вас очень маленькая команда разработки, и затраты времени на регистрацию каждого нового запроса превышают выгоду от экономии трафика.
3. Вы используете GraphQL только для прототипирования или внутренних инструментов, где скорость разработки важнее оптимизации.
Типичные ошибки при внедрении и способы их избежать
Ошибка 1: Попытка использовать persisted queries для динамических запросов. Если вы отправляете запрос с произвольными параметрами (например, `search(q: $q)`), сервер не сможет найти ID в кэше, так как каждый уникальный набор параметров требует отдельного ID.
Решение: разделяйте фиксированные операции и динамические. Для поиска используйте обычный GraphQL-запрос с переменными, а для получения списка товаров — persisted query.
Ошибка 2: Отсутствие эндпоинта регистрации. Если сервер не настроен на приём запросов с ID, клиент будет получать ошибку 400 или 404 при первом обращении.
Решение: настройте эндпоинт, который принимает JSON-объект `{ query: 'text', variables: {} }` и возвращает `{ version: 1, query: 'text' }`. Используйте стандарты Apollo Server для этого.
Ошибка 3: Игнорирование версий. Если вы обновите запрос на сервере, но не обновите версию в клиенте, возможны конфликты кэширования.
Решение: всегда указывайте `version` в ответе сервера и используйте его для обновления кэша клиента.
Ограничения технологии и сценарии, где она неэффективна
Persisted queries не подходят для экспериментальных функций или A/B-тестов, где вы быстро меняете структуру запросов. В таких случаях лучше использовать обычный GraphQL.
Если у вас есть сотни уникальных операций, ручная регистрация станет бюрократической нагрузкой. Автоматизируйте процесс через CI/CD пайплайны, которые генерируют ID при деплое нового кода.
Не полагайтесь только на persisted queries для безопасности. Они защищают от инъекций синтаксиса, но не заменяют авторизацию и валидацию данных на уровне бизнес-логики.
Вопросы и ответы
Можно ли использовать persisted queries с другими GraphQL-клиентами?
Да, механизм работает независимо от клиента. Главное, чтобы клиент поддерживал отправку ID вместо текста запроса. Примеры: Apollo Client, React Query (с кастомным линком), Android/iOS SDKs от Apollo.
Как генерировать ID для persisted queries?
ID обычно генерируется как хэш от текста запроса (например, SHA-256). Сервер может хранить хэш и текст в таблице. Клиент отправляет хэш, сервер проверяет его и возвращает ответ.
Что делать, если нужно изменить persisted query?
Вы можете создать новый ID для изменённого запроса и обновить клиентский код. Старый ID останется работать с старой версией запроса, пока вы не отключите его на сервере.
По теме
- Что такое лонгрид и как использовать его для SEO
- Как использовать email-маркетинг для продвижения сайта: полный гид для новичков и профессионалов
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';
const persistedQueryLink = createPersistedQueryLink({
uri: 'https://api.example.com/graphql',
});
const client = new ApolloClient({
link: persistedQueryLink,
cache: new InMemoryCache(),
});


