# Сессия #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 обрывал соединение. ## Рабочий рецепт ```python # _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 — строка как есть ) ``` ```sql -- schema.py original_bytes BYTEA, -- было NOT NULL → теперь nullable original_b64 TEXT, -- новое: base64 строка ``` ```javascript // Клиент: FileReader.readAsDataURL → base64 → XHR JSON POST /upload xhr.send(JSON.stringify({ filename: file.name, data: b64, // base64 строка cid: contractId })); ``` ## Пул соединений (db.py) ```python ThreadedConnectionPool( minconn=2, maxconn=5, keepalives=1, keepalives_idle=30, keepalives_interval=10, keepalives_count=3, connect_timeout=10, ) # Проверка живости: SELECT 1 при каждом getconn # Переподключение при OperationalError/InterfaceError ``` ## Как дебажили 1. `debug_log` таблица — 12 шагов лога в `_upload_json` 2. curl тесты: 10×150KB — случайные HTTP 000 3. `/test` endpoint: прямой SQL INSERT — работал, но только с малыми телами 4. Исключение FormData → переход на JSON 5. Исключение `base64.decode()` → TEXT как есть 6. `ALTER TABLE ... DROP NOT NULL` — последний фикс ## Выученные уроки 1. **Managed-платформа ≠ свой сервер** — лимиты невидимы и неконтролируемы 2. **TEXT INSERT быстрее BYTEA INSERT** — не гнать decode при загрузке 3. **NOT NULL constraint** — проверять схему при добавлении колонок 4. **debug_log в БД** — единственный способ увидеть что происходит внутри 5. **Версия в шаблоне** — перед КАЖДЫМ коммитом 6. **Пул соединений с keepalive** — обязательно для managed PostgreSQL