Files
loadtest/History/session-2026-08-17-loadtest.md
T

159 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Сессия 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
```