Docs: подробная запись сессии 2026-08-17 (loadtest, анализ 64KB contracts-flask)

This commit is contained in:
naeel
2026-08-17 17:21:19 +04:00
parent 25f163ea5e
commit c0916d5642
+158
View File
@@ -0,0 +1,158 @@
# Сессия 2026-08-17 — LoadTest (проверка загрузки больших файлов)
_Файл: подробная запись содержимого чата. Дата: 2026-08-17._
_Сервис: `loadtest.pythonk8s.dev.nubes.ru` (платформа Nubes, pythonk8s)._
_Цель: проверить, доходят ли файлы (в т.ч. большие) до бэкенда или нет._
---
## 1. Задача (из чата)
1. Создан пустой git-репозиторий `https://gitea.services.ngcloud.ru/sqs/loadtest.git`.
2. Склонировать его в `/home/naeel/nubes/contracts/loadtest`.
3. Сделать код по `howto-flask-nubes.md` для деплоя на Flask-платформу Nubes.
4. Функционал: меню выбора одного файла из локали + загрузка в память Flask.
5. Индикация прогресса загрузки — `%` и таймер (позже таймер признан неважным).
6. **Главное** — однозначный результат: файл загрузился ИЛИ нет, максимум статистики.
7. Код — простейший, с комментариями.
## 2. Инстанс платформы (из параметров)
- `instanceUid`: `bb9ed30b-1794-44ff-81a2-6ff2731200c9`
- `connectionUrl`: `https://loadtest.pythonk8s.dev.nubes.ru/`
- `resourceRealm`: `iot-naeel`
- `appConfiguration` изначально: `gitPath=https://github.com/Foxyhhd/Baldurs-Gate-test.git`, `version=3.12`, `healthPath=""`
- `clusterConfiguration`: cpu 500, memory 1024, replicas 1
- Мониторинг: Grafana kubernetes-pods по namespace `bb9ed30b-…`
**Для деплоя нужно сменить `gitPath` на `https://gitea.services.ngcloud.ru/sqs/loadtest.git`.**
## 3. Что сделано
### 3.1 Клонирование
- `git clone https://gitea.services.ngcloud.ru/sqs/loadtest.git .` — успешно, ветка `master`, коммитов не было.
### 3.2 Структура (обязательна по howto-flask-nubes — без `site/` платформа не запустится)
```
loadtest/
├── .gitignore # __pycache__, *.pyc
├── requirements.txt # Flask>=3.0, gunicorn>=21.2, requests>=2.31
└── site/
├── app.py # точка входа (python site/app.py)
├── static/style.css
└── templates/index.html
```
- НЕТ `site/__init__.py` (конфликт со stdlib `site.py`).
- В `app.py` обязателен `app.run(host="0.0.0.0", port=5000)`.
### 3.3 Код
**`site/app.py`**:
- `GET /` — страница; `GET /health``ok`.
- `POST /upload` — принимает multipart-файл в поле `file`, читает **в память** (`f.read()`), на диск не пишет.
- `MAX_CONTENT_LENGTH` **не задан** — цель проверить реальные лимиты платформы/прокси, а не Flask.
- Ответ — максимум статистики: `file` (name, mime, size_bytes, size_mb), `server` (read_ms, total_ms, speed_mb_s), `md5`, `meta` (server_start, remote_addr, content_length_header, content_type_header, user_agent).
**`site/templates/index.html`**:
- Выбор одного файла + кнопка «Загрузить».
- Загрузка через **`XMLHttpRequest`** + `xhr.upload.onprogress` — реальный `%` и скорость МБ/с (`fetch` не даёт upload progress).
- Минимальный таймер (секунды), timeout XHR = 10 мин.
- Исход всегда однозначный: ✅ «ФАЙЛ ЗАГРУЖЕН» / ❌ «НЕ ЗАГРУЖЕН (HTTP …)» / «СЕТЕВАЯ ОШИБКА» / «ТАЙМАУТ (10 мин)».
- При ошибке показывается HTTP-статус и текст ответа (в т.ч. не-JSON: 413, HTML ошибок прокси).
- Статистика: клиент (имя, размер, время старта, длительность, средняя скорость) + сервер (принято байт, чтение мс, обработка мс, скорость, MD5, заголовки, remote addr, время сервера, User-Agent).
### 3.4 Локальный тест (curl, 127.0.0.1:5000)
| Файл | Результат | Примечание |
|---|---|---|
| 1 МБ | ok | read 4.86 мс |
| 10 МБ | ok | read 10.61 мс |
| 100 МБ | ok | total 0.78 с, ~133 МБ/с |
- `GET /`, `/health`, `/static/style.css` — 200.
### 3.5 Коммит и пуш
- 2 коммита (второй — убрал `__pycache__/*.pyc` из индекса, добавил `.gitignore`).
- Пуш в gitea: ветка `master` создана, `origin/master` отслеживается.
- Файлы в репо: `.gitignore`, `requirements.txt`, `site/app.py`, `site/static/style.css`, `site/templates/index.html`.
## 4. Анализ: скорость «клиент» vs «сервер»
Пример из реального теста (VirtualBox-7.2.8-173730-Win.exe, 177 853 032 байт = 169.614 МБ):
```
-- КЛИЕНТ --
Длительность: 20.84 с
Средняя скорость (клиент): 8.14 МБ/с
-- СЕРВЕР --
Чтение в память: 816.43 мс
Обработка всего: 3308.25 мс
Скорость (сервер): 207.75 МБ/с
```
- **Скорость клиента** (8.14 МБ/с) — браузер: размер ÷ полное время (нажатие → ответ). Реальная скорость передачи по сети клиент → прокси → сервер, со всеми задержками. **Главный показатель для диагностики** загрузки.
- **Скорость сервера** (207.75 МБ/с) — `app.py`: размер ÷ `f.read()`. Это локальное чтение уже дошедших байт из памяти, сеть ни при чём. Всегда высокая.
- Признак проблемы загрузки: клиентская скорость низкая/нулевая или обрыв до 100%; серверная при этом «красивая».
## 5. Ключевая находка: «64KB» в contracts-flask — это СИМПТОМ, не лимит
### 5.1 Факты из History (проект contracts)
- `History/llm-analysis/sonnet-upload-from-scratch.md` — гипотеза «POST > 64KB → Ingress рвёт TCP (HTTP 000), 64KB — жёсткий предел платформы». **Гипотеза.**
- `History/sessions/session-08-upload-saga.md` — реальные корневые причины:
1. `original_bytes BYTEA NOT NULL` → PostgreSQL 500 при вставке `original_b64`.
2. `base64.b64decode()` + BYTEA INSERT > 30 с → таймаут Ingress → обрыв соединения.
3. `session-07`: до 7 отдельных DB-подключений на сборку чанков → медленно.
- `History/sessions/session-10-lucee-pipeline.md`**ddos-guard рвёт HTTP/2-стримы на POST > ~50KB** (HTTP/2 PROTOCOL_ERROR) на платформе `services.ngcloud.ru`.
- `History/features/upload-analysis-v40.md` — даже Redis-очередь (RPUSH → 200 сразу, v1.38-1.39) падала ✗; curl 100KB → случайные HTTP 000.
**Вывод:** «64KB» — порог, после которого обработка на сервере не успевала до таймаута прокси, + ddos-guard рвал крупные POST на `services`. Это не объективный лимит размера тела.
### 5.2 Порядок HTTP-запроса (важно для понимания)
1. Браузер передаёт тело (сеть). Обработка ещё не идёт.
2. Тело полностью дошло до сервера.
3. Вызывается обработчик (декодирование, INSERT, парсинг).
4. Сервер отвечает.
«Параллельности» обработки с передачей нет. Проблема — **медленный ответ после приёма тела** (`proxy_read_timeout` на Ingress): файл дошёл, а соединение оборвано до ответа → «Сеть»/HTTP 000.
### 5.3 Почему loadtest грузит без проблем
- Бэкенд тривиальный: `f.read()` → MD5 → JSON. Никакой БД/Redis/декодирования/парсинга → ответ мгновенный → прокси не успевает оборвать.
- Плюс это dev-платформа (`pythonk8s.dev.nubes.ru`), где нет ddos-guard-обрыва крупных POST, как на `services`.
## 6. Текущее состояние contracts-flask `/upload` (диагноз)
`contracts-flask/site/routes/upload_bp.py`, `/upload` — всё синхронно до ответа:
1. `data = f.read()`
2. `content_hash = hashlib.sha256(data)…`
3. `documents.insert(..., original_bytes=base64.b64encode(data).decode(), ...)` — base64 ВСЕГО файла → БД
4. `result = parse_file(f.filename, data)`**синхронный парсинг**
5. `documents.set_parsed(...)` / `set_error(...)`
6. `return jsonify(...)`
Прочее:
- `site/config.py`: `MAX_CONTENT_LENGTH = 200 * 1024 * 1024` (200 МБ) — не причина.
- `site/app.py`: `app.run(..., threaded=True)`, БД — SQLite (thread-local).
- Фронт `site/static/files.js:uploadFile``fetch` (реального `%` нет, только таймер), v2.0.3.
## 7. Предложенные правки contracts-flask (по приоритету, НЕ ВНЕСЕНЫ)
1. **Вынести парсинг из `/upload`** (главное): `/upload` = принять → сохранить → мгновенный `ok`; `parse_file` — в фоновый `threading.Thread` (паттерн есть в `pipeline_bp.py`) или отдельный endpoint `/parse`.
2. **Убрать `base64.b64encode(data)`** при вставке — хранить сырые байты (BLOB/BYTEA) или не хранить исходник.
3. **Клиент: `XMLHttpRequest` + `xhr.upload.onprogress`** — реальный `%` + явный статус «дошёл/не дошёл».
4. `MAX_CONTENT_LENGTH` 200 МБ — оставить; ограничения ddos-guard/Ingress на `services` — вне кода.
## 8. Открытые вопросы / следующий шаг
- В UI loadtest нужно показать размер выбранного файла с разбивкой по 3 цифры (например `177 853 032`) — **отложено**, «при следующей правке».
- Правки в `contracts-flask` (раздел 7) — **ожидают команды «делай»** и уточнения объёма (всё 4 пункта или только п.1).
## 9. Полезные команды
```bash
# локальный запуск
cd /home/naeel/nubes/contracts/loadtest && python3 site/app.py
# тест загрузки
curl -s --max-time 180 -F "file=@/tmp/lt_100mb.bin" http://127.0.0.1:5000/upload
# логи пода на платформе
kubectl -n bb9ed30b-1794-44ff-81a2-6ff2731200c9 logs deploy/pythonk8s --tail=50
```