4.0 KiB
4.0 KiB
Сессия #8 — Upload Saga: 26 версий до рабочей загрузки
Дата: 2026-06-17 Версия: v1.12 → v1.26
Проблема
Загрузка файлов > 50-100 KB на managed-платформе pythonk8s.services.ngcloud.ru случайно обрывалась с ✗ Сеть (XHR onerror, HTTP 000).
Хронология попыток
| v | Что пробовали | Результат |
|---|---|---|
| 1.12-1.13 | FormData/multipart POST | ✗ Сеть > 100KB |
| 1.14 | Promise fix (uploadFileXHR) | ✗ |
| 1.15-1.17 | Чанки 50KB FormData → БД | Только чанк 0, остальные не доходят |
| 1.18 | Одно DB-соединение вместо 7 | ✗ |
| 1.19 | debug_log таблица | ✗ |
| 1.20 | Пул соединений psycopg2 | ✗ |
| 1.21 | base64 JSON вместо FormData | ✗ |
| 1.22 | Пул: keepalive, reconnect | ✗ |
| 1.23 | Всегда одно соединение (убрали if/cid) | ✗ |
| 1.24-1.25 | base64 как TEXT (без decode) + логи | ✗ — original_bytes NOT NULL |
| 1.26 | original_bytes DROP NOT NULL |
✓ РАБОТАЕТ |
Корневые причины (две)
1. original_bytes BYTEA NOT NULL
Вставляли только original_b64 TEXT, а original_bytes требовал значения. PostgreSQL выдавал 500, но клиент не видел ответа из-за таймаута Ingress.
2. base64.b64decode() при загрузке
Декодирование 200KB base64 → 150KB байт → BYTEA INSERT занимало >30 сек. Ingress обрывал соединение.
Рабочий рецепт
# _upload_json (app.py)
# 1. Принимаем base64 как строку
# 2. INSERT TEXT (не BYTEA) — мгновенно
# 3. Без base64.decode()
# 4. Одно DB-соединение на весь запрос
cur.execute(
"INSERT INTO documents (filename, mime_type, original_b64, status) VALUES (%s,%s,%s,'uploaded')",
(filename, mime, b64), # b64 — строка как есть
)
-- schema.py
original_bytes BYTEA, -- было NOT NULL → теперь nullable
original_b64 TEXT, -- новое: base64 строка
// Клиент: FileReader.readAsDataURL → base64 → XHR JSON POST /upload
xhr.send(JSON.stringify({
filename: file.name,
data: b64, // base64 строка
cid: contractId
}));
Пул соединений (db.py)
ThreadedConnectionPool(
minconn=2, maxconn=5,
keepalives=1,
keepalives_idle=30,
keepalives_interval=10,
keepalives_count=3,
connect_timeout=10,
)
# Проверка живости: SELECT 1 при каждом getconn
# Переподключение при OperationalError/InterfaceError
Как дебажили
debug_logтаблица — 12 шагов лога в_upload_json- curl тесты: 10×150KB — случайные HTTP 000
/testendpoint: прямой SQL INSERT — работал, но только с малыми телами- Исключение FormData → переход на JSON
- Исключение
base64.decode()→ TEXT как есть ALTER TABLE ... DROP NOT NULL— последний фикс
Выученные уроки
- Managed-платформа ≠ свой сервер — лимиты невидимы и неконтролируемы
- TEXT INSERT быстрее BYTEA INSERT — не гнать decode при загрузке
- NOT NULL constraint — проверять схему при добавлении колонок
- debug_log в БД — единственный способ увидеть что происходит внутри
- Версия в шаблоне — перед КАЖДЫМ коммитом
- Пул соединений с keepalive — обязательно для managed PostgreSQL