feat: bufferedAmount flow control in WS upload, bump 1.0.16
Deploy loadtest / validate (push) Waiting to run

This commit is contained in:
2026-07-11 06:38:38 +04:00
parent a851bfb102
commit b509491c60
3 changed files with 102 additions and 3 deletions
@@ -0,0 +1,90 @@
# 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-буфером