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

11 KiB
Raw Permalink Blame History

Сессия 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 /healthok.
  • 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.mdddos-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:uploadFilefetch (реального % нет, только таймер), 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. Полезные команды

# локальный запуск
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