11 KiB
Сессия 2026-08-17 — LoadTest (проверка загрузки больших файлов)
Файл: подробная запись содержимого чата. Дата: 2026-08-17.
Сервис: loadtest.pythonk8s.dev.nubes.ru (платформа Nubes, pythonk8s).
Цель: проверить, доходят ли файлы (в т.ч. большие) до бэкенда или нет.
1. Задача (из чата)
- Создан пустой git-репозиторий
https://gitea.services.ngcloud.ru/sqs/loadtest.git. - Склонировать его в
/home/naeel/nubes/contracts/loadtest. - Сделать код по
howto-flask-nubes.mdдля деплоя на Flask-платформу Nubes. - Функционал: меню выбора одного файла из локали + загрузка в память Flask.
- Индикация прогресса загрузки —
%и таймер (позже таймер признан неважным). - Главное — однозначный результат: файл загрузился ИЛИ нет, максимум статистики.
- Код — простейший, с комментариями.
2. Инстанс платформы (из параметров)
instanceUid:bb9ed30b-1794-44ff-81a2-6ff2731200c9connectionUrl:https://loadtest.pythonk8s.dev.nubes.ru/resourceRealm:iot-naeelappConfigurationизначально: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(конфликт со stdlibsite.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— реальные корневые причины:original_bytes BYTEA NOT NULL→ PostgreSQL 500 при вставкеoriginal_b64.base64.b64decode()+ BYTEA INSERT > 30 с → таймаут Ingress → обрыв соединения.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-запроса (важно для понимания)
- Браузер передаёт тело (сеть). Обработка ещё не идёт.
- Тело полностью дошло до сервера.
- Вызывается обработчик (декодирование, INSERT, парсинг).
- Сервер отвечает.
«Параллельности» обработки с передачей нет. Проблема — медленный ответ после приёма тела (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 — всё синхронно до ответа:
data = f.read()content_hash = hashlib.sha256(data)…documents.insert(..., original_bytes=base64.b64encode(data).decode(), ...)— base64 ВСЕГО файла → БДresult = parse_file(f.filename, data)— синхронный парсингdocuments.set_parsed(...)/set_error(...)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 (по приоритету, НЕ ВНЕСЕНЫ)
- Вынести парсинг из
/upload(главное):/upload= принять → сохранить → мгновенныйok;parse_file— в фоновыйthreading.Thread(паттерн есть вpipeline_bp.py) или отдельный endpoint/parse. - Убрать
base64.b64encode(data)при вставке — хранить сырые байты (BLOB/BYTEA) или не хранить исходник. - Клиент:
XMLHttpRequest+xhr.upload.onprogress— реальный%+ явный статус «дошёл/не дошёл». MAX_CONTENT_LENGTH200 МБ — оставить; ограничения ddos-guard/Ingress наservices— вне кода.
8. Открытые вопросы / следующий шаг
- В UI loadtest нужно показать размер выбранного файла с разбивкой по 3 цифры (например
177 853 032) — отложено, «при следующей правке». - Правки в
contracts-flask(раздел 7) — ожидают команды «делай» и уточнения объёма (всё 4 пункта или только п.1).
9. Полезные команды
# локальный запуск
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