Files
contracts/History/chunk-analysis-request.md
T

5.9 KiB
Raw Blame History

Запрос на анализ: чанковая загрузка падает с «Сеть»

Дата: 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)

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)

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. Предложить решение (без правки кода)