diff --git a/History/chunk-analysis-request.md b/History/chunk-analysis-request.md new file mode 100644 index 0000000..80b8798 --- /dev/null +++ b/History/chunk-analysis-request.md @@ -0,0 +1,128 @@ +# Запрос на анализ: чанковая загрузка падает с «Сеть» + +**Дата:** 2026-06-17 +**Версия:** v1.17 +**СТРОГО: ТОЛЬКО АНАЛИЗ. КОД НЕ ПРАВИТЬ. Ответ — в этот же файл.** + +--- + +## Симптом + +Загрузка PDF 153 KB (3 чанка по 50 KB) через `POST /chunk`: +- Прогресс быстро доходит до 100% +- Затем ~10 секунд висит «Загрузка...» +- Итог: `✗ Сеть` (XHR onerror — обрыв соединения, нет HTTP-ответа) + +Пробовали: +- v1.15: чанки в памяти Python `_chunk_store = {}` → «Сеть» +- v1.17: чанки в PostgreSQL (таблица `chunks`) → ТО ЖЕ САМОЕ + +Значит дело НЕ в worker'ах/shared memory — с БД чанки общие для всех процессов. + +## Архитектура + +- **Платформа:** pythonk8s.services.ngcloud.ru (managed Flask-хостинг, деплой = git push) +- **Ingress:** nginx перед приложением, таймаут ~30 сек (известно по предыдущему опыту) +- **Приложение:** Flask (app.run), Python 3.12 +- **БД:** PostgreSQL 17.6, внутренний кластер + +## Клиентский код (upload.html) + +```javascript +async function uploadFileChunked(file, cid, onProgress) { + var CHUNK_SIZE = 50 * 1024; // 50 KB + var totalChunks = Math.ceil(file.size / CHUNK_SIZE); + var uploadId = file.name + '_' + Date.now() + '_' + Math.random(); + + for (var i = 0; i < totalChunks; i++) { + var chunk = file.slice(i * CHUNK_SIZE, Math.min((i+1) * CHUNK_SIZE, file.size)); + + var resp = await new Promise((resolve, reject) => { + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/chunk'); + xhr.timeout = 60000; + + xhr.onload = function() { + if (xhr.status === 200) { + var r = JSON.parse(xhr.responseText); + if (r.error) reject(new Error(r.error)); + else resolve(r); + } else reject(new Error('HTTP ' + xhr.status)); + }; + xhr.onerror = () => reject(new Error('Сеть')); + xhr.ontimeout = () => reject(new Error('Таймаут')); + + var fd = new FormData(); + fd.append('chunk', chunk); + fd.append('upload_id', uploadId); + fd.append('filename', file.name); + fd.append('chunk_index', i); + fd.append('total_chunks', totalChunks); + if (cid) fd.append('cid', cid); + xhr.send(fd); + }); + // resp = {contract_id: "..."} только на последнем чанке + // или {chunk: N, received: N, total: N} на промежуточных + } +} +``` + +Файл 153 KB → 3 чанка. Прогресс 33% → 67% → 100% (значит все 3 XHR завершились). Затем 10 сек паузы → «Сеть». + +## Серверный код (app.py) + +```python +def _chunk_upload(self): + upload_id = request.form.get("upload_id") + chunk_index = int(request.form.get("chunk_index", 0)) + total_chunks = int(request.form.get("total_chunks", 1)) + chunk_data = request.files.get("chunk").read() + + # Сохранить в БД + db.execute( + "INSERT INTO chunks (...) VALUES (...) ON CONFLICT DO NOTHING", + (upload_id, chunk_index, chunk_data, ...) + ) + + # Проверить: все чанки получены? + received = db.query("SELECT COUNT(*) FROM chunks WHERE upload_id=%s", ...) + + if received == total_chunks: + # Собрать из БД + rows = db.query("SELECT chunk_data FROM chunks WHERE upload_id=%s ORDER BY chunk_index", ...) + file_bytes = b"".join(...) + + # Сохранить как документ + _save_file_to_db(filename, file_bytes, mime, contract_id) + + # Очистить чанки + db.execute("DELETE FROM chunks WHERE upload_id=%s", ...) + + return jsonify({"contract_id": str(contract_id)}) + + return jsonify({"chunk": chunk_index, "received": received, "total": total_chunks}) +``` + +## Вопросы для анализа + +1. **Почему «Сеть» (XHR onerror), а не HTTP-ошибка?** Сервер не возвращает ответ — соединение обрывается на уровне TCP. Это не таймаут (был бы `ontimeout`) и не HTTP-ошибка (был бы `onload`). + +2. **Почему прогресс 100% но потом 10 сек паузы?** Все 3 чанка отправлены и получили ответ? Или третий чанк висит 10 сек и потом обрыв? + +3. **Может ли `ON CONFLICT DO NOTHING` + `SELECT COUNT(*)` дать гонку?** Два worker'а одновременно пишут чанки — COUNT может не увидеть только что вставленный? + +4. **Может ли сборка (`b"".join` + `_save_file_to_db`) быть медленной и вызывать таймаут Ingress?** 153 KB — вроде быстро, но если БД тормозит... + +5. **Может ли `client_max_body_size` на nginx резать чанки?** 50 KB — вроде мало. + +6. **Есть ли ограничение на количество запросов от одного клиента?** Rate limiting? + +7. **Что видят логи сервера?** Есть ли способ посмотреть? + +## Что нужно + +**ТОЛЬКО АНАЛИЗ. КОД НЕ ТРОГАТЬ.** + +1. Определить наиболее вероятную причину обрыва +2. Предложить способ диагностики (как узнать точно) +3. Предложить решение (без правки кода)