# 2026-07-11 — WS stall: root cause (Sonnet) + план flow control ## Контекст WebSocket-загрузка файлов через ingress (Flask + flask-sock + Werkzeug dev server). Файл 3.7 MB, чанки 64 KB. Первые ~90% загружаются мгновенно, затем пауза ~51 сек. ## Диагноз (Sonnet) **Первопричина: TCP receive buffer saturation на поде.** ### Механика ``` Браузер: ws.send() × 58 без пауз, без проверки bufferedAmount ↓ nginx: пробрасывает всё в под (proxy-request-buffering: off) ↓ Ядро пода: TCP recv buffer автотюнится до ~6 MB ↓ При ~3.3 MB (90%) буфер ЗАПОЛНЕН → TCP zero window ↓ nginx: send() → EAGAIN → перестаёт читать от браузера ↓ Werkzeug: читает 1 фрейм (64 KB) → окно приоткрывается → nginx пихает 1-2 фрейма → окно схлопывается → ЦИКЛ: stop-and-go oscillation ↓ Оставшиеся ~400 KB (6 чанков) дрейнуются ~8 KB/s = 51 сек ``` ### Почему HTTP POST не виснет? HTTP POST тестировался на nginx:alpine (C-сервер) — вычитывает со скоростью линии, буфер не заполняется. На Flask/Werkzeug HTTP POST падает в 30-50% случаев — та же проблема, но проявляется через Connection: close → новый TCP → conntrack timeout. ### Почему внутри кластера (прямой WS на pod:5000) быстро? Нет nginx-прослойки. Браузер → напрямую под, TCP buffer тот же, но: - Меньше промежуточных буферов - Нет дополнительной задержки на nginx event loop ## План: `bufferedAmount` flow control **Меняется:** `app.js`, функция `uploadFileWS` ### Суть Вместо прямого `ws.send(data)` — проверка `ws.bufferedAmount`: ```javascript function sendOrWait(data) { if (ws.bufferedAmount > 256 * 1024) { setTimeout(function () { sendOrWait(data); }, 10); return; } ws.send(data); offset += CHUNK; if (offset < f.size) { readNext(); } else { ws.send('DONE'); } } ``` `reader.onload` становится однострочным: `sendOrWait(e.target.result)`. Порог 256 KB выбран так, чтобы: - Не заполнять TCP buffer пода (>6 MB max) - Оставлять запас для nginx внутренних буферов - Не создавать излишних задержек ### Версия `app.py`: `VERSION = '1.0.15'` → `'1.0.16'` ### Scope - Только client-side (app.js) - app.py — без изменений в логике - CHUNK (64 KB), порог прогресса (10%), таймаут (300 сек) — без изменений ### Ожидаемый результат - Буфер не переполняется → нет stop-and-go oscillation - Прогресс-бар обновляется плавно на всём протяжении загрузки - Файл 3.7 MB загружается без 51-секундной паузы - Скорость определяется скоростью чтения сервером (Werkzeug), а не TCP-буфером