WebSocket-підписки в Solana дають змогу отримувати оновлення в реальному часі без постійного опитування RPC-вузла через HTTP. Це критичний інструмент для індексації, моніторингу транзакцій, трекінгу стану акаунтів та синхронізації стану між сервісами. Нижче — практичний розбір механіки підписок, керування з'єднаннями та оптимізації для production-середовищ.

Типи підписок: account, logs, signature, slot

Solana RPC через WebSocket пропонує чотири основні типи підписок. Кожен вирішує своє завдання й має специфічну структуру відповідей.

accountSubscribe

Підписка на зміну стану конкретного акаунта. Коли будь-яка транзакція змінює дані (data) або lamports акаунта, клієнт отримує повідомлення з повним оновленим станом.

Формат запиту:

  • methodaccountSubscribe
  • params — масив із двох елементів: публічний ключ акаунта (рядок base58) та об'єкт конфігурації (encoding: base64, base58, jsonParsed; опціонально commitment та dataSlice)

Що повертається: об'єкт result містить context (slot) та value (дані акаунта, lamports, owner, executable, rentEpoch). При використанні jsonParsed для токен-акаунтів SPL дані розпарсяться у зручну структуру з полями amount, decimals, mint.

Типове застосування: відстеження балансу користувача, моніторинг стану PDA-акаунтів програми, трекінг зміни конфігурації.

Обмеження: якщо акаунт змінюється дуже часто (наприклад, hot wallet з великою кількістю транзакцій), потік повідомлень може бути інтенсивним. У таких випадках варто розглянути агрегацію на стороні клієнта або використання dataSlice для отримання лише потрібного фрагменту даних.

logsSubscribe

Підписка на логи транзакцій. Дозволяє фільтрувати за конкретним акаунтом (програмою) або отримувати всі логи в слоті.

Формат запиту:

  • methodlogsSubscribe
  • params — масив із фільтром ("all" для всіх логів або рядок із публічним ключем програми) та об'єктом конфігурації (commitment)

Що повертається: об'єкт з signature, err (null якщо транзакція успішна, інакше — об'єкт помилки), logs (масив рядків логу з підстановленими аргументами) та slot.

Типове застосування: індексація подій програми (парсинг логів Anchor за префіксом), виявлення невдалих транзакцій для аналітики, моніторинг активності конкретного смарт-контракту.

Важливий нюанс: фільтр за акаунтом у logsSubscribe означає «транзакції, що торкаються цього акаунта», а не «логи, що генерує ця програма». Якщо транзакція викликає кілька програм, ви отримаєте всі логи цієї транзакції, а не лише ті, що належать фільтрованій програмі. Фільтрація за вмістом логу виконується на стороні клієнта.

signatureSubscribe

Підписка на підтвердження конкретної транзакції за її підписом.

Формат запиту:

  • methodsignatureSubscribe
  • params — масив із підписом (рядок base58) та об'єктом конфігурації (опціонально commitment та enableReceivedNotification)

Що повертається: за замовчуванням — повідомлення лише при фіналізації (залежно від вказаного commitment). Якщо enableReceivedNotification встановлено в true, клієнт отримає додаткове повідомлення, коли транзакцію вперше побачено в пулі (до підтвердження).

Типове застосування: очікування підтвердження відправленої транзакції замість polling через getSignatureStatuses. Це зменшує навантаження на RPC і дає швидший зворотний зв'язок.

Обмеження: підписка автоматично скасовується після отримання фінального результату або після таймауту (якщо транзакція не потрапила в блок протягом деякого часу). Не підходить для довготривалого моніторингу.

slotSubscribe

Підписка на оновлення слотів — найлегший тип підписки, що дозволяє відстежувати прогрес ланцюга.

Формат запиту:

  • methodslotSubscribe
  • params — порожній масив

Що повертається: просто число — номер поточного слота. Додатково можуть надходити повідомлення типів slotStatus (processed, confirmed, finalized) залежно від реалізації RPC-провайдера.

Типове застосування: перевірка доступності та актуальності RPC-вузла, синхронізація локального стану індексатора з прогресом ланцюга, виявлення затримок (staleness) з'єднання.

Керування життєвим циклом підключення

WebSocket-з'єднання з Solana RPC — це довготривалий ресурс, який потребує явного керування. Неконтрольоване створення з'єднень або підписок призводить до перевищення лімітів провайдера та розривів.

Ініціалізація та ідентифікація підписок

Кожен виклик *Subscribe повертає subscription id — ціле число, що ідентифікує підписку в межах цього WebSocket-з'єднання. Цей id обов'язковий для скасування підписки через відповідний *Unsubscribe метод. Зберігайте мапу «що підписано → subscription id» у своєму додатку.

Передумови для стабільної роботи:

  • Використовуйте єдиний WebSocket-клієнт на сервіс (або пул з контрольованою кількістю), а не створюйте нове з'єднання на кожну підписку.
  • Реалізуйте чергу повідомлень, якщо відправка має відбуватися послідовно (багато WebSocket-бібліотек це роблять автоматично).
  • Фіксуйте час створення кожної підписки для діагностики «завислих» підписок.

Скасування підписок

Нескасовані підписки споживають ресурси як на стороні клієнта, так і на стороні RPC-вузла. Виклик accountUnsubscribe, logsUnsubscribe, signatureUnsubscribe або slotUnsubscribe з правильним subscription id повертає true у полі result при успіху.

Правило: якщо ваш сервіс зупиняє обробку певного акаунта чи програми, скасуйте підписку до того, як видалите відповідний об'єкт із внутрішнього стану. Інакше виникає розсинхронізація: RPC продовжує надсилати повідомлення, а обробник вже не існує.

Graceful shutdown

При зупинці сервісу (SIGTERM, SIGINT) необхідно:

  1. Припинити приймати нові повідомлення з черги.
  2. Відправити *Unsubscribe для всіх активних підписок.
  3. Дочекатися підтвердження або таймауту (наприклад, 2 секунди).
  4. Закрити WebSocket-з'єднання (код 1000 — нормальне закриття).

Це зменшує навантаження на RPC-провайдера та уникає «завислих» підписок, які провайдер може рахувати проти ваших лімітів ще деякий час.

Обробка розривів та перепідключень

WebSocket-з'єднання з Solana RPC неминуче розриваються: таймаути на проксі-серверах провайдера, перезапуски вузлів, мережеві аномалії. Production-сервіс має відновлювати роботу без втрати критичних даних.

Виявлення розриву

Розрив може бути виявлений трьома шляхами:

  • Подія close у WebSocket-клієнті — найнадійніший спосіб. Код закриття та причина допомагають діагностувати проблему (наприклад, 1008 — policy violation, часто означає перевищення лімітів підписок).
  • Подія error — сигналізує про проблеми на рівні протоколу, але не завжди означає повний розрив.
  • Відсутність ping/pong — якщо провайдер надсилає ping-фрейми, а pong не отримується, з'єднання фактично мертве, навіть якщо подія close ще не спрацювала. Реалізуйте власний таймаут (наприклад, 30 секунд без жодного фрейму).

Стратегія перепідключення

Використовуйте exponential backoff з джитером:

  • Перша спроба — негайно або через 1 секунду.
  • Кожна наступна — подвоєння попередньої затримки (2, 4, 8, 16 секунд).
  • Максимальна затримка — 60 секунд (не більше, інакше сервіс буде непридатним довго).
  • Додавайте випадковий джитер (±20% від затримки), щоб уникнути thundering herd при масовому розриві.

Критичний крок після перепідключення: всі підписки втрачаються при розриві з'єднання. Після встановлення нового WebSocket-з'єднання необхідно повторно відправити всі *Subscribe запити. Для цього зберігайте повний реєстр активних підписок (тип, параметри) незалежно від стану з'єднання.

Заповнення прогалин у даних

Під час розриву ви пропускаєте оновлення. Стратегія заповнення залежить від типу підписки:

Тип підписки Стратегія відновлення
account Після перепідключення викличте HTTP-метод getAccountInfo для кожного підписаного акаунта. Порівняйте отриманий стан із останнім відомим — якщо відрізняється, обробіть як оновлення.
logs Збережіть останній оброблений slot. Після перепідключення викличте getBlocks або getBlockSignatures для пропущених слотів і обробіть пропущені транзакції через getTransaction. Для високонавантажених програм це може бути дорогим — оцініть, чи прийнятна втрата даних за час простою.
signature Якщо підписка була для очікування підтвердження, після перепідключення перевірте статус через getSignatureStatuses. Якщо статус уже відомий — обробіть, якщо ні — повторіть підписку.
slot Просто відновіть підписку. Пропущені слоти не мають практичного значення для більшості use cases.

Ризик: при тривалому розриві (хвилини та більше) заповнення прогалин для logsSubscribe може згенерувати великий обсяг HTTP-запитів. Встановіть межу: якщо розрив тривав більше N слотів, логуйте попередження та продовжуйте з поточного стану, а не намагайтеся ідеально відновити історію.

Оптимізація кількості одночасних підписок

Кожен RPC-провайдер встановлює ліміти на кількість одночасних WebSocket-підписок. Ці ліміти відрізняються залежно від тарифного плану та провайдера. Перевірте актуальні ліміти у вашого провайдера — це не універсальна константа.

Групування підписок за з'єднаннями

Замість одного з'єднання на все, організуйте пул з'єднань і розподіляйте підписки між ними. Базова стратегія:

  • Визначте максимальну кількість підписок на одне з'єднання (наприклад, 80% від ліміту провайдера, щоб залишити буфер для службових запитів).
  • При досягненні порогу — створюйте нове з'єднання.
  • При скасуванні підписок — якщо з'єднання стало майже порожнім, закривайте його після таймауту (наприклад, 30 секунд без підписок).

Зменшення кількості необхідних підписок

Перш ніж додавати нову підписку, перевірте, чи можна досягти тієї ж мети іншим шляхом:

  • Замість кількох accountSubscribe на токен-акаунти одного користувача — розгляньте підписку на логи програми токена (spl-token) з фільтром за відповідними акаунтами. Це одна підписка замість N.
  • Замість logsSubscribe на кожну програму окремо — якщо вам потрібні логи кількох програм у одному слоті, одна підписка з фільтром "all" з подальшою клієнтською фільтрацією може бути ефективнішою, якщо обсяг логів прийнятний.
  • Періодичний polling замість постійної підписки — для акаунтів, що змінюються рідко (наприклад, конфігураційні PDA), виклик getAccountInfo кожні 10–30 секунд може бути дешевшим за постійну WebSocket-підписку, особливо якщо таких акаунтів сотні.

Моніторинг споживання підписок

Ведіть метрики:

  • Кількість активних підписок за типом (account, logs, signature, slot).
  • Кількість активних WebSocket-з'єднань.
  • Кількість відхилених запитів на підписку (помилка з текстом на кшталт «too many subscriptions»).
  • Частота отриманих повідомлень за кожною підпискою (допомагає виявити аномально активні акаунти).

Якщо ви наближаєтесь до ліміту підписок, це сигнал для перегляду архітектури: можливо, частина навантаження має бути перенесена на індексацію через geyser plugin або на пакетне опитування через getProgramAccounts.

Коли WebSocket-підписки не підходять

Якщо вам потрібно відстежувати тисячі акаунтів або індексувати всі транзакції програми з повною надійністю, WebSocket-підписки через публічний або навіть dedicated RPC стануть вузьким місцем. У таких випадках розгляньте пряме підключення до вузла з geyser plugin для стрімінгу повних даних або використання спеціалізованих індексаторів. WebSocket-підписки найефективніші для точкового моніторингу обмеженого набору об'єктів у реальному часі.

Джерела