Реализация GraphQL Persisted Queries для оптимизации

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация GraphQL Persisted Queries для оптимизации
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Персистентные запросы GraphQL (Persisted Queries)

Persisted Queries — техника, при которой клиент отправляет хеш запроса вместо полного тела. Сервер кеширует запросы по хешу. Преимущества: меньше трафика (особенно на mobile), возможность GET-запросов для кеширования на CDN, защита от произвольных запросов в продакшен.

Как работает Automatic Persisted Queries (APQ)

Клиент          Сервер          CDN/Cache
  │                │                │
  │ POST {hash}    │                │
  │───────────────>│                │
  │ 404 Not Found  │                │
  │<───────────────│                │
  │                │                │
  │ POST {hash + query body}        │
  │───────────────>│                │
  │ {data}  [сохранить hash→query]  │
  │<───────────────│                │
  │                │                │
  │ GET ?hash=...  │                │
  │──────────────────────────────>  │
  │               {data} из кеша    │
  │<──────────────────────────────  │

Apollo Client: настройка APQ

import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client'
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries'
import { generatePersistedQueryIdsFromManifest } from '@apollo/persisted-query-lists'
import { sha256 } from 'crypto-hash'

// Подход 1: Automatic Persisted Queries (fallback при промахе кеша)
const persistedQueryLink = createPersistedQueryLink({
  sha256,
  useGETForHashedQueries: true  // GET запросы для CDN кеширования
})

const httpLink = createHttpLink({
  uri: 'https://api.example.com/graphql'
})

const client = new ApolloClient({
  link: persistedQueryLink.concat(httpLink),
  cache: new InMemoryCache()
})

Серверная поддержка APQ

// Apollo Server встроенная поддержка APQ
import { ApolloServer } from '@apollo/server'
import { createClient } from 'redis'
import { BaseRedisCache } from 'apollo-server-cache-redis'

const redis = createClient({ url: process.env.REDIS_URL })
await redis.connect()

const server = new ApolloServer({
  typeDefs,
  resolvers,

  // APQ cache: по умолчанию in-memory, в продакшен — Redis
  cache: new BaseRedisCache({
    client: redis,
    ttl: 86400  // 24 часа
  }),

  // Включить APQ (по умолчанию включено)
  persistedQueries: {
    ttl: 86400  // Время жизни в cache
  }
})

Registered Persisted Queries (более безопасный подход)

APQ позволяет выполнять любой запрос, зарегистрировав его. Registered PQ разрешает только заранее известные запросы:

# Генерация манифеста из клиентских операций (при сборке)
npx generate-persisted-query-manifest \
  --documents "src/**/*.graphql" \
  --output persisted-query-manifest.json
// persisted-query-manifest.json
{
  "format": "apollo-persisted-query-manifest",
  "version": 1,
  "operations": [
    {
      "id": "dc67510fb4289672bea757e862d6b00e...",
      "name": "GetPosts",
      "type": "query",
      "body": "query GetPosts($limit: Int) { posts(first: $limit) { ... } }"
    },
    {
      "id": "4a1250de93ad972168776be6ccd86fec...",
      "name": "CreatePost",
      "type": "mutation",
      "body": "mutation CreatePost($input: CreatePostInput!) { createPost(input: $input) { id } }"
    }
  ]
}
// Сервер: разрешать только зарегистрированные запросы
import manifest from './persisted-query-manifest.json'

const allowedQueries = new Map(
  manifest.operations.map(op => [op.id, op.body])
)

// Middleware до ApolloServer
app.use('/graphql', (req, res, next) => {
  // В продакшен — только persisted queries
  if (process.env.NODE_ENV === 'production') {
    const queryId = req.body?.extensions?.persistedQuery?.sha256Hash
                 || req.query?.extensions?.persistedQuery?.sha256Hash

    if (!queryId) {
      return res.status(400).json({
        errors: [{ message: 'Only persisted queries allowed in production' }]
      })
    }

    if (!allowedQueries.has(queryId)) {
      return res.status(400).json({
        errors: [{ message: `Unknown query ID: ${queryId}` }]
      })
    }

    // Подставить тело запроса из манифеста
    if (!req.body.query) {
      req.body.query = allowedQueries.get(queryId)
    }
  }

  next()
})

CDN кеширование через GET

// Apollo Client: GET для query, POST для mutation
const persistedQueryLink = createPersistedQueryLink({
  sha256,
  useGETForHashedQueries: true
})

// Результирующий URL:
// GET /graphql?operationName=GetPosts&variables={"limit":20}&extensions={"persistedQuery":{"version":1,"sha256Hash":"dc67510..."}}

Nginx конфигурация для кеширования GET-запросов:

proxy_cache_path /var/cache/nginx/graphql
  levels=1:2 keys_zone=graphql:10m max_size=100m
  inactive=1h use_temp_path=off;

location /graphql {
  # Кешировать только GET (persisted queries)
  if ($request_method = GET) {
    proxy_cache graphql;
    proxy_cache_key "$uri$is_args$args";
    proxy_cache_valid 200 5m;
    proxy_cache_use_stale error timeout updating;
    add_header X-Cache-Status $upstream_cache_status;
  }

  proxy_pass http://api_backend;
}

Клиент: предзагрузка манифеста

// При использовании Registered PQ в Apollo Client 3.8+
import { generatePersistedQueryIdsFromManifest } from '@apollo/persisted-query-lists'
import manifest from './persisted-query-manifest.json'

const generateLink = createPersistedQueryLink({
  generateQueryPreferredFallback: generatePersistedQueryIdsFromManifest({ manifest }),
  useGETForHashedQueries: true
})

Мониторинг попаданий в кеш

// Prometheus метрика: cache hit rate для persisted queries
const persistedQueryHits = new Counter({
  name: 'graphql_persisted_query_hits_total',
  help: 'Persisted query cache hits',
  labelNames: ['status']  // hit, miss, not_found
})

// В плагине ApolloServer
const plugin = {
  async requestDidStart() {
    return {
      async executionDidStart({ request }) {
        const pq = request.extensions?.persistedQuery
        if (pq) {
          const cached = await cache.get(`apq:${pq.sha256Hash}`)
          persistedQueryHits.labels(cached ? 'hit' : 'miss').inc()
        }
      }
    }
  }
}

Срок выполнения

Настройка Automatic Persisted Queries с Redis-кешем и CDN-кешированием через GET — 1–2 рабочих дня.