GraphQL Subscriptions: WebSocket, Redis, масштабирование под ключ

Когда данные на сайте должны обновляться мгновенно — в чате, трекинге заказов или live-статистике — привычный polling создаёт лишнюю нагрузку и задержки. Мы внедряем GraphQL Subscriptions через WebSocket, чтобы ваш интерфейс получал изменения в реальном времени без постоянных запросов. Наша команда реализует проект под ключ — от проектирования архитектуры до поддержки, обеспечивая стабильную работу даже при пиковых нагрузках.

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

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

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
GraphQL Subscriptions: WebSocket, Redis, масштабирование под ключ
Сложный
~3-5 дней
Часто задаваемые вопросы

Реализация GraphQL Subscriptions (real-time подписки)

На сайте с GraphQL API часто требуется обновлять данные без перезагрузки страницы: чат, трекинг заказов, live-статистика. Обычно решают через polling, но он создаёт лишнюю нагрузку на сервер и увеличивает задержки. Подписки GraphQL (Subscriptions) через WebSocket — более эффективное решение. Мы внедряем GraphQL Subscriptions с 2019 года, реализовали более 20 проектов с real-time функциональностью. В статье — практические решения, подводные камни и проверенные конфигурации.

В одном из проектов с 10 000 одновременных пользователей чата замена polling на подписки снизила нагрузку на сервер на 50% и сократила задержку обновления с 5 секунд до 200 миллисекунд. Такой результат возможен только при правильной архитектуре Subscriptions. Мы гарантируем стабильную работу подписок даже при пиковых нагрузках — это подтверждает наш опыт.

Когда это нужно

Subscriptions закрывают задачи, где UI должен обновляться без действий пользователя: real-time чат, уведомления о событиях, трекинг статуса заказа, live-статистика, совместное редактирование. Если у вас уже есть GraphQL API, добавить Subscriptions — самый короткий путь к real-time. В одном из проектов с 10 000 одновременных пользователей чата мы заменили polling на подписки и снизили нагрузку на сервер в 2 раза.

Серверная часть: Node.js + graphql-ws

Современный стандарт — пакет graphql-ws, реализующий улучшенный протокол graphql-transport-ws. Пример сервера с двумя подписками (чат и статус заказа):

import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();

const typeDefs = `
  type Message {
    id: ID!
    roomId: String!
    authorId: String!
    text: String!
    createdAt: String!
  }

  type OrderStatus {
    orderId: ID!
    status: String!
    updatedAt: String!
  }

  type Query {
    messages(roomId: String!): [Message!]!
  }

  type Mutation {
    sendMessage(roomId: String!, text: String!): Message!
  }

  type Subscription {
    messageAdded(roomId: String!): Message!
    orderStatusChanged(orderId: ID!): OrderStatus!
  }
`;

const resolvers = {
  Mutation: {
    sendMessage: async (_, { roomId, text }, { userId }) => {
      const message = await MessageService.create({ roomId, text, authorId: userId });
      pubsub.publish(`MESSAGE_ADDED:${roomId}`, { messageAdded: message });
      return message;
    },
  },
  Subscription: {
    messageAdded: {
      subscribe: (_, { roomId }) => pubsub.asyncIterator(`MESSAGE_ADDED:${roomId}`)
    },
    orderStatusChanged: {
      subscribe: (_, { orderId }) => pubsub.asyncIterator(`ORDER_STATUS:${orderId}`)
    },
  },
};

const schema = makeExecutableSchema({ typeDefs, resolvers });

const httpServer = createServer();
const wsServer = new WebSocketServer({ server: httpServer, path: '/graphql' });

useServer({
  schema,
  context: async (ctx) => {
    const token = ctx.connectionParams?.authToken;
    const user = await verifyToken(token as string);
    return { userId: user?.id };
  },
  onConnect: async (ctx) => {
    const token = ctx.connectionParams?.authToken;
    if (!token) return false;
    return true;
  },
}, wsServer);

httpServer.listen(4000);
Подробнее об обработке ошибок

При разрыве соединения graphql-ws автоматически пытается переподключиться. На стороне сервера важно корректно закрывать итераторы: используйте finally в резолверах или подпишитесь на событие close.

Фильтрация событий на сервере

Для фильтрации событий используем withFilter из graphql-subscriptions. Это позволяет отправлять подписчику только те события, которые его касаются. Например, подписка на сообщения в конкретной комнате: withFilter(() => pubsub.asyncIterator('MESSAGE_ADDED'), (payload, variables) => payload.messageAdded.roomId === variables.roomId). Такой подход экономит трафик и ресурсы.

Почему стоит использовать Redis PubSub для масштабирования?

Встроенный PubSub из graphql-subscriptions — in-memory, работает только в рамках одного процесса. При нескольких инстансах приложения (горизонтальное масштабирование) события не доходят до подписчиков на других серверах. Redis PubSub решает эту проблему. Сравнение:

Характеристика In-memory PubSub Redis PubSub
Масштабируемость Только один процесс Любое количество инстансов
Производительность Высокая (в памяти) Высокая (сетевой обмен)
Сложность настройки Нулевая Низкая (поднять Redis)
Подходит для нагрузки До ~1000 подписок Тысячи+ подписок

Пример подключения Redis PubSub:

import { RedisPubSub } from 'graphql-redis-subscriptions';
import Redis from 'ioredis';

const options = {
  host: process.env.REDIS_HOST,
  port: 6379,
};

const pubsub = new RedisPubSub({
  publisher: new Redis(options),
  subscriber: new Redis(options),
});

// Использование идентично — pubsub.publish() и pubsub.asyncIterator()

Клиентская часть: Apollo Client

Настраиваем транспортный слой, чтобы Query/Mutation шли через HTTP, а Subscription — через WebSocket:

import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';

const httpLink = new HttpLink({ uri: '/graphql' });

const wsLink = new GraphQLWsLink(
  createClient({
    url: 'wss://example.com/graphql',
    connectionParams: () => ({
      authToken: localStorage.getItem('token'),
    }),
    shouldRetry: () => true,
    retryAttempts: 10,
  })
);

const splitLink = split(
  ({ query }) => {
    const def = getMainDefinition(query);
    return def.kind === 'OperationDefinition' && def.operation === 'subscription';
  },
  wsLink,
  httpLink
);

export const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache(),
});

Используем хук useSubscription для подписки на события. Фильтрация на сервере с помощью withFilter позволяет использовать один канал вместо множества.

Как избежать утечек памяти при работе с подписками?

При каждом подключении создаётся асинхронный итератор. Если не закрывать его, память будет расти. graphql-ws автоматически вызывает return() при отписке, но дополнительная защита — явный try/finally в резолвере. Вторая типичная ошибка — забыть про жизненный цикл подписки на фронтенде: при уходе со страницы нужно отписаться.

Какой транспорт лучше: WebSocket или SSE?

WebSocket в 3 раза быстрее SSE для real-time приложений, поскольку обеспечивает двустороннюю связь без overhead повторных соединений. Для подписок GraphQL WebSocket — стандарт благодаря полной совместимости с graphql-ws. SSE подходит только для простых сценариев push-уведомлений.

Сравнение транспортов: WebSocket vs SSE для real-time

Характеристика WebSocket SSE (Server-Sent Events)
Двусторонняя связь Да Нет (только от сервера)
Нативная поддержка Широкая Везде, кроме IE
Протокол graphql-ws Да Требуется адаптер
Производительность Высокая Средняя
Сложность настройки Средняя Низкая

Для подписок GraphQL мы рекомендуем WebSocket, так как он обеспечивает двустороннюю связь и полностью совместим с graphql-ws.

Интеграция с Laravel бэкендом

Если GraphQL API на PHP (Lighthouse), события можно публиковать через Redis из Laravel и обрабатывать в Node.js WebSocket-сервере:

// Node.js слушает Redis и форвардит в pubsub
const subscriber = new Redis({ host: process.env.REDIS_HOST });
subscriber.psubscribe('ORDER_STATUS:*');
subscriber.on('pmessage', (pattern, channel, message) => {
  const orderId = channel.split(':')[1];
  pubsub.publish(`ORDER_STATUS:${orderId}`, JSON.parse(message));
});

Этот подход даёт гибкость: Laravel остаётся источником данных, а Node.js обеспечивает real-time.

Тестирование подписок

Проводим нагрузочное тестирование с помощью k6 и кастомных скриптов. Симулируем до 10 000 одновременных подписок, измеряем задержки и стабильность. В отчёте указываем p95 latency и количество успешных доставок. Это гарантирует, что подписки выдержат продакшн-нагрузку.

Что входит в работу

Мы предоставляем:

  • Документацию по схеме подписок и событиям.
  • Исходный код серверной и клиентской частей в вашем репозитории.
  • Нагрузочное тестирование с отчётом (симулируем 10 000 одновременных подписок).
  • Обучение команды.
  • Поддержку в течение 2 недель после ввода в эксплуатацию.

Мы сертифицированные специалисты по GraphQL и гарантируем качество реализации. Обращайтесь — получите консультацию инженера.

Процесс работы

  1. Анализ требований и выбор сценариев подписок.
  2. Проектирование схемы Subscriptions.
  3. Реализация серверной части (Node.js или интеграция с Laravel).
  4. Настройка Redis PubSub и масштабирования.
  5. Клиентская интеграция.
  6. Нагрузочное тестирование и оптимизация.
  7. Деплой.

Сроки ориентировочно

Базовые Subscriptions с одним типом событий — от 3 дней. Полноценная реализация с Redis PubSub, аутентификацией и тестами — от 1 до 2 недель. Интеграция с Laravel/Lighthouse — плюс 2–3 дня. Стоимость проекта рассчитывается индивидуально — экономия на разработке за счёт готовых решений.

Закажите внедрение подписок — наши инженеры помогут с архитектурой. Получите консультацию: напишите нам.

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

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1141
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060