Коли WebSocket «пливе» в мобільних проксі: що відрізняється від звичайного інтернету
WebSocket виглядає ідеальним транспортом для realtime: один апґрейд з HTTP, далі двосторонній канал без постійних запитів/відповідей. Але щойно ви додаєте мобільні проксі (LTE/5G IP, CGNAT, часті зміни маршрутів), WebSocket починає поводитися як «довга сесія, яку постійно хтось намагається закрити».
Для архітектора важливо розуміти: проблема зазвичай не у протоколі WebSocket як такому, а в тому, що довгоживучі TCP-з’єднання проходять через кілька шарів мережевого обладнання з власними тайм-аута́ми та політиками. У reverse-proxy на кшталт NGINX є типовий тайм-аут читання 60 секунд без трафіку, і він закриє WebSocket, якщо канал «мовчить» занадто довго. Це прямо описано в офіційній документації NGINX, разом із рекомендацією або підняти тайм-аут, або надсилати ping кадри, щоб тримати з’єднання «живим».
Мобільні проксі для WebSocket: чому realtime стає нестабільним
Мобільні проксі для WebSocket використовують динамічні IP та працюють через CGNAT, що створює ризик розриву довгих TCP-з’єднань. Якщо не налаштувати heartbeat WebSocket, sticky-сесію проксі та правильні тайм-аути NGINX, realtime через mobile proxy буде нестабільним.
Чому WebSocket рветься через мобільні проксі: коротка відповідь
Якщо WebSocket рветься через мобільні проксі, причина зазвичай не в самому протоколі, а в idle timeout на проксі/NGINX/CDN, агресивних NAT-тайм-аутах (CGNAT), ротації IP або нестабільності мобільної мережі. Довгі TCP-з’єднання у LTE/5G середовищах потребують heartbeat, узгоджених тайм-аутів і reconnect-логіки.
Нижче — технічний розбір, як стабілізувати WebSocket через мобільні проксі та уникнути «тихих смертей» з’єднання.
Де починаються нестабільності: 7 найчастіших причин розривів
1) Тайм-аути бездіяльності в проксі, CDN, балансерах
Найпідступніший сценарій: WebSocket «працює», доки є обмін даними, але падає, коли користувач нічого не робить 1–2 хвилини. Причина — проміжний вузол вважає канал неактивним і закриває його. Наприклад, NGINX за замовчуванням може закрити проксійоване з’єднання, якщо сервер не передає дані 60 секунд, і це лікується proxy_read_timeout або heartbeat на рівні WebSocket.
Аналогічно, деякі CDN/edge-проксі мають жорсткі ліміти неактивності для WebSocket. У практиці це виглядає як «рівно N секунд і відвал». Для архітектури це означає: навіть якщо бекенд готовий тримати сесію годинами, середовище доставки може мати інший план.
2) Мобільні мережі: handover, радіопауза, зміна маршруту
У мобільному інтернеті клієнт може перемикатися між базовими станціями, 4G↔5G, Wi‑Fi↔LTE, потрапляти у «радіопаузу» (тимчасова втрата пакетів) або в зону слабкого сигналу. Для коротких HTTP-запитів це часто непомітно, але довге TCP-з’єднання чутливе: зростає RTT, з’являються ретрансляції, а інколи шлях просто обривається.
3) CGNAT і агресивні NAT-тайм-аути
Мобільні оператори часто використовують Carrier-Grade NAT. Це додає ще один станful-шар, який тримає таблиці з’єднань і чистить їх за своїми правилами. Якщо трафіку немає, NAT може прибрати запис — і ви отримаєте «тиху смерть» каналу (клієнт думає, що з’єднання є, але пакети вже не доходять). Саме тому keepalive/heartbeat — не «опція», а базова вимога у мобільних сценаріях.
4) Ротація IP у мобільних проксі та «липкість» сесії
Багато мобільних проксі орієнтовані на зміну IP. Це добре для скрейпінгу, але погано для WebSocket: якщо проксі вирішить змінити вихідний IP або маршрут під час активної сесії, TCP-з’єднання не «мігрує» — воно обривається. Тут ключове слово для архітектора — sticky: чи може провайдер забезпечити стабільний вихідний IP/канал на час сесії і як саме це гарантується (по порту, токену, часу, «сесійній» прив’язці).
5) HTTP/2, TLS-термінація і зайві «переклади» протоколів
WebSocket — це апґрейд HTTP/1.1. Коли по дорозі стоять компоненти, що люблять HTTP/2, TLS-термінацію та повторне встановлення з’єднань (edge, ingress, WAF), інколи з’являється неочевидна деградація: часті повторні handshakes, розриви при оновленні сертифікатів, обмеження на довгі потоки. Для архітектора важливо спроектувати шлях трафіку так, щоб WebSocket не проходив через «зайві» трансформації, а компонент, який робить Upgrade, реально підтримував його в режимі проксіювання.
6) Перевантаження та backpressure: канал живий, але додаток «не встигає»
Realtime-системи часто падають не тому, що мережа погана, а тому що клієнт/сервер не справляється з обсягом подій. У WebSocket немає «чарівної» гарантії доставки: якщо ви генеруєте події швидше, ніж приймач здатен їх обробити, починаються черги, зростає затримка, а потім і тайм-аути. У мобільних проксі це посилюється вищою латентністю та втратами пакетів.
7) «Тиха смерть» без правильного детекту: клієнт не знає, що канал мертвий
Типовий симптом: користувач «підключений», UI показує online, але події не приходять. Це наслідок того, що TCP може не відразу повідомити додатку про розрив. Правильний підхід — детектити живість на прикладному рівні (ping/pong) з чіткими інтервалами і тайм-аутом очікування відповіді. У популярних бібліотеках WebSocket keepalive описують як механізм, що одночасно тримає з’єднання активним і вимірює затримку через Ping→Pong.
Типові причини розриву WebSocket у мобільних проксі
| Причина | Симптом | Що робити |
|---|---|---|
| Idle timeout (NGINX/CDN) | Рівно 60–100 секунд і розрив | Збільшити proxy_read_timeout або додати heartbeat 20–30 сек |
| CGNAT | «Тиха смерть», UI online, але події не приходять | Ping/Pong + тайм-аут очікування відповіді |
| Ротація IP у проксі | Розрив під час активної сесії | Sticky-сесія + вимкнена ротація під час WebSocket |
| Backpressure | Зростання затримки перед обривом | Агрегація подій + адаптація частоти по RTT |
| HTTP/2 або TLS-термінація | Нестабільний Upgrade або випадкові disconnect | Перевірити підтримку WebSocket-проксіювання |
Як стабілізувати WebSocket через мобільні проксі
- Налаштувати heartbeat WebSocket (ping/pong кожні 20–30 секунд)
- Збільшити тайм-аути NGINX для WebSocket (proxy_read_timeout)
- Використовувати sticky-сесію проксі без ротації IP під час з’єднання
- Реалізувати reconnect-стратегію з backoff
- Моніторити RTT і backpressure у realtime proxy
Як проєктувати стабільний realtime через мобільні проксі: архітектурні принципи
Принцип 1. Вважайте WebSocket «крихким транспортом» і робіть reconnect штатною функцією
Надійність у WebSocket через мобільні проксі досягається не магією в мережі, а планом Б у додатку:
- Автоматичний reconnect з exponential backoff і джитером (щоб не «вбити» бекенд після масового відвалу).
- Відновлення стану: клієнт після reconnect має швидко догнати пропущені події (через last_event_id/offset або snapshot+diff).
- Ідемпотентність: якщо клієнт повторно надсилає команду (бо не знає, чи дійшло), бекенд не повинен виконати її двічі.
Це базова різниця між «демо-чатом» і продакшн realtime: у продакшні розрив — норма, а не виняток.
Принцип 2. Heartbeat на рівні WebSocket + узгоджені тайм-аути на всьому шляху
Heartbeat — це регулярні маленькі сигнали, які:
- не дають проксі/балансерам закрити «мовчазний» канал;
- дозволяють швидко виявити «тиху смерть» і запустити reconnect;
- дають телеметрію затримки (RTT) для адаптації частоти оновлень UI.
Практика: виберіть інтервал ping коротший за найменший тайм-аут бездіяльності у вашому ланцюжку (edge/CDN → ingress → reverse proxy → app). Наприклад, якщо десь є 60 секунд, heartbeat 20–30 секунд часто спрацьовує. Документація NGINX прямо каже: або збільшуйте proxy_read_timeout, або періодично шліть ping кадри.
Не плутайте heartbeat WebSocket із TCP keepalive. TCP keepalive — це низькорівневі проби ядра ОС і за замовчуванням можуть бути занадто рідкими (наприклад, години) та не проходити через деякі проксі так, як ви очікуєте.
Приклад конфігурації NGINX для стабільного WebSocket
Базова конфігурація для уникнення idle timeout виглядає так:
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
Офіційна документація NGINX підтверджує: якщо дані не передаються протягом proxy_read_timeout, з’єднання буде закрите, тому або збільшуйте тайм-аут, або використовуйте періодичні ping-кадри.
Принцип 3. Сесійна «липкість» для мобільних проксі
Якщо ви використовуєте мобільні проксі саме як проміжну інфраструктуру (а не як клієнтський мобільний інтернет), вам потрібні правила:
- Сесійні креденшали/токени на проксі-рівні, які гарантують незмінний вихідний канал хоча б X хвилин.
- Заборона ротації IP під час активних WebSocket-сесій або ротація лише між сесіями.
- Обмеження довжини сесії (наприклад, 10–30 хвилин) з плановим «м’яким» перепідключенням, щоб ви керували моментом зміни маршруту, а не проксі.
Принцип 4. Синхронізація подій: offsets, acknowledgements, snapshot+diff
Realtime через нестабільний канал потребує протоколу поверх WebSocket (навіть якщо ви не пишете його як окремий стандарт):
- Порядкові номери подій (sequence) або cursor/offset.
- Підтвердження (ack) для критичних повідомлень або хоча б «останній отриманий offset» у кожному клієнтському запиті.
- Snapshot (знімок стану) після reconnect + diff (далі потокові події). Це зменшує обсяг «догону».
Без цього ви отримуєте класичні баги: дублікати, пропуски, «стрибаючий» стан у UI, або зависання, коли клієнт чекає подію, що вже була втрачена.
Принцип 5. Деградація сервісу: коли WebSocket не найкращий вибір
Іноді найрозумніше — не тримати WebSocket «будь-якою ціною», а мати fallback:
- SSE (Server-Sent Events) для односпрямованих стрімів (сповіщення, стрічка подій) — простіше і часто стабільніше через проксі.
- Long polling як аварійний режим, коли середовище ріже Upgrade або дуже агресивні тайм-аути.
- Часткова realtime: не все має бути «миттєвим» — наприклад, курсори/typing індикатори можна оновлювати раз на 1–2 секунди з агрегацією.
Практичний чекліст для продакшн: що перевірити перед запуском
- Карта тайм-аутів по всьому шляху: CDN/edge, WAF, ingress, reverse proxy, app server, idle timeout у проксі-провайдера.
- Heartbeat: інтервал ping, тайм-аут pong, політика reconnect. Вимірюйте RTT і логікуйте причини закриття.
- Ліміти: максимальна кількість одночасних WebSocket-сесій, rate limiting на підключення, захист від «thundering herd» після відвалу.
- Стабільність мобільної проксі-сесії: sticky-правила, відсутність ротації IP під час сесії, контрольований «м’який» reconnect.
- Семантика подій: sequence/offset, replay, snapshot+diff, ідемпотентність команд.
- Спостережуваність: метрики reconnect rate, розподіл тривалості сесій, відсоток «тихих смертей», latency ping→pong.
Кейси: як підходи працюють у реальних продуктах
Кейс 1. Чат/підтримка: «відвалюється, коли користувач нічого не пише»
Симптом: чат обривається, якщо 1–2 хвилини немає повідомлень. Причина: idle timeout на одному з проксі-шарів. Рішення: heartbeat 25 секунд + узгоджені тайм-аути на ingress/NGINX. Паралельно — reconnect з відновленням по last_message_id. Більшість reverse-proxy мають дефолтні idle-тайм-аути, які потрібно узгодити з heartbeat.
Кейс 2. Дашборд з котируваннями: «краще менше, але стабільно»
Симптом: при високій частоті оновлень (десятки разів/сек) на мобільних каналах росте затримка, а потім обриви. Причина: backpressure + мобільна латентність. Рішення: агрегація подій (наприклад, 5–10 разів/сек), пріоритизація важливих оновлень, відмова від «дрібних» подій у піках. Heartbeat використовується ще й як індикатор погіршення мережі: якщо RTT виріс, система знижує частоту update.
Кейс 3. Колаборація (спільне редагування): «зникли правки після reconnect»
Симптом: після розриву частина змін не застосувалася або застосувалася двічі. Причина: немає чіткого cursor/ack протоколу. Рішення: послідовні номери операцій, підтвердження застосування та replay від останнього підтвердженого offset; snapshot документу після reconnect, потім diff. Це дозволяє пережити будь-який короткий відвал, навіть якщо мобільний проксі «переключився».
Тестування під мобільні проксі: як не обманути себе «стабільною» лабораторією
Найпоширеніша помилка — тестувати WebSocket лише у стабільному офісі й вважати, що в полі все буде так само. Для мобільних сценаріїв вам потрібні тести на:
- втрату мережі на 3–10 секунд і повернення;
- перемикання мереж (Wi‑Fi↔LTE) і стрибки RTT;
- тривалу бездіяльність (idle) і перевірку, чи не ріже проксі;
- масові reconnect (наприклад, після рестарту edge/ingress);
- упорядкування/дублювання повідомлень під час відновлення.
Підхід до тестування мобільних WebSocket-підключень часто включає перевірки стабільності, порядку повідомлень і правильного reconnect.
Як вибрати мобільні проксі під WebSocket: питання, які варто поставити провайдеру
Якщо мобільні проксі — частина вашої інфраструктури (наприклад, для доступу до зовнішніх realtime-джерел або як шар маршрутизації), під WebSocket їх потрібно оцінювати інакше, ніж «звичайні» HTTP-проксі.
- Чи є підтримка довгих сесій? Запитайте про максимальну тривалість TCP/WebSocket-сесії та причини примусових розривів.
- Як реалізована sticky-сесія? По чому відбувається прив’язка: логін/пароль, токен, порт, IP клієнта, cookie? Чи гарантується незмінність вихідного IP на час сесії?
- Політика ротації IP: чи можна вимкнути ротацію повністю; чи є «м’яка» ротація між сесіями; чи є примусова ротація через N хвилин.
- Обмеження на кількість одночасних конекшенів з однієї сесії/акаунту та ліміти на reconnection rate.
- Прозорість для WebSocket-фреймів: чи є інспекція/перепакування трафіку, чи кадри проходять транзитом.
- Географія та маршрутизація: чи можете ви вибирати країну/ASN; чи є різниця у стабільності між регіонами.
Навіть якщо провайдер каже «ми підтримуємо WebSocket», перевіряйте це практично тестом на idle та reconnect: саме там найчастіше «вилазять» приховані тайм-аути.
Спостережуваність і SLO: які метрики покажуть, що realtime «здоровий»
У мобільних проксі середовищах «просто пінгувати» недостатньо — потрібні метрики, які дозволяють відрізнити проблему мережі від проблеми додатка:
- Connection lifetime distribution: медіана/перцентилі тривалості WebSocket-сесій. Якщо є «стіна» на 60 або 100 секунд — це майже завжди idle timeout на одному з шарів.
- Reconnect rate (на користувача і загалом) та причини close-кодів: різкі піки — ознака деградації мережі або інциденту на edge.
- Ping→Pong RTT та його розподіл: зростання RTT часто передує масовим відвалам; це сигнал знизити частоту оновлень або перейти в «легший» режим UI.
- Delivery lag: різниця між часом генерації події та відображенням на клієнті. Важливо відстежувати не лише середнє, а й хвости (p95/p99).
- Missed/duplicated events: частка відновлень, де потрібен replay/snapshot. Якщо показник росте — протокол синхронізації подій недостатній.
З архітектурної точки зору, сформулюйте SLO не як «WebSocket ніколи не падає», а як «після розриву клієнт відновлює актуальний стан за X секунд у Y% сесій».
План «м’якого» перепідключення: керуйте моментом, коли сесія перезапускається
Оскільки в мобільних проксі можуть бути примусові ротації або непередбачувані розриви, корисно запровадити контрольований цикл:
- Клієнт кожні N хвилин ініціює м’який reconnect у момент низької активності (наприклад, між батчами подій).
- Перед відключенням клієнт надсилає свій останній підтверджений offset, а сервер готує швидкий snapshot/diff.
- Якщо reconnect не вдався — система повертається до backoff стратегії.
Такий підхід зменшує випадкові відвали «посеред важливої операції» і робить поведінку більш передбачуваною для користувача.
Безпека і сумісність: дрібниці, які часто ламають продакшн
- WSS всюди: якщо десь по дорозі є TLS-термінація, переконайтеся, що заголовки Upgrade не блокуються політиками безпеки.
- Обмеження на заголовки/кукі: деякі проксі ріжуть великі заголовки, а у корпоративних мережах можуть блокувати нестандартні патерни.
- Rate limiting на підключення: захистіть сервер від хвилі reconnect, але робіть це «розумно» (з урахуванням джитера), щоб не заблокувати легітимних користувачів після інциденту.
Пам’ятайте: WebSocket — це довга відкрита сесія, а значить, будь-який компонент на шляху має право її закрити. Ваша задача як архітектора — зробити так, щоб це не руйнувало продукт, а лише переводило його в режим відновлення.
Технічні джерела
- RFC 6455 — офіційний стандарт протоколу WebSocket (IETF).
- Офіційна документація NGINX щодо proxy_read_timeout і підтримки WebSocket.
Висновок
WebSocket + мобільні проксі — це не «погано», але це інша ліга вимог: потрібно планувати розриви, узгоджувати тайм-аути, вмикати heartbeat, керувати «липкістю» сесії та мати протокол відновлення стану. Якщо зробити це системно, realtime працює стабільно навіть у середовищі з CGNAT, високою латентністю та періодичними відвалами — і користувач бачить не «помилки з’єднання», а плавне відновлення.
FAQ: часті питання про WebSocket через мобільні проксі
Чому WebSocket рветься рівно через 60 секунд?
Найчастіше це idle timeout на NGINX або CDN. За замовчуванням proxy_read_timeout може становити 60 секунд без трафіку.
Чи може TCP keepalive замінити heartbeat?
Ні. TCP keepalive працює на рівні ОС і зазвичай має занадто великі інтервали. Для мобільних проксі потрібен heartbeat на рівні WebSocket (ping/pong).
Що краще через мобільні проксі — WebSocket чи SSE?
Для двостороннього realtime — WebSocket. Для односпрямованих подій (нотифікації, стрічки) SSE часто стабільніший через проксі та простіший в інфраструктурі.
Чи можна використовувати WebSocket через HTTP/2?
Класичний WebSocket працює через HTTP/1.1 Upgrade. HTTP/2 потребує окремої підтримки і не всі проксі коректно його проксійовують.