diff --git a/History/sonnet-upload-from-scratch.md b/History/sonnet-upload-from-scratch.md new file mode 100644 index 0000000..ed49b47 --- /dev/null +++ b/History/sonnet-upload-from-scratch.md @@ -0,0 +1,37 @@ +# Запрос: загрузка файлов с нуля + +**Задача:** надёжная загрузка docx/pdf/zip (до 50MB) через веб-интерфейс. + +## Ресурсы + +- **Flask** (managed pythonk8s.services.ngcloud.ru, деплой = git push) +- **PostgreSQL 17.6** (внутренний кластер `postgresqlk8s-master...svc.cluster.local`) +- **Redis** (внутренний `redisk8s...svc.cluster.local`, admin/aLITloRefJEiCPqUc2xB) +- **БД:** пул psycopg2 (2-10, keepalive), таблицы есть (`documents`, `contracts`, `supplements`) +- **Парсеры:** python-docx (DOCX), pdfplumber (PDF), zipfile (ZIP), textify.to_text() +- **LLM:** aillm.ru (gpt-oss-120b), HTTP/2 +- **Клиент:** браузер, XHR POST, JSON body + +## Что НЕ работает (43 версии) + +- POST с телом > 64KB → nginx/Ingress рвёт TCP (HTTP 000, ReadTimeout) +- 10-50KB: 100% надёжно +- FormData, JSON/base64, чанки, Redis, retry — ничего не помогло +- 64KB — жёсткий предел платформы + +## Что нужно + +1. **Архитектура загрузки** — гарантированно обходящая лимит 64KB на тело запроса +2. **Прогресс на клиенте** — интерактивно (имя файла, проценты, таймер) +3. **Парсинг после загрузки** — извлечение параграфов, таблиц, стилей +4. **ZIP** — показывать содержимое, парсить файлы внутри + +## Вопросы + +1. Как обойти 64KB лимит тела запроса? +2. Чанки — на клиенте или сервере? +3. Redis — нужен или нет для этой задачи? +4. Как гарантировать целостность файла при чанковой загрузке? +5. Какая структура БД оптимальна для хранения результатов парсинга? + +**Ответь кратко: архитектура (3-5 пунктов), потом отвечу на вопросы.** diff --git a/History/upload-analysis-v41.md b/History/upload-analysis-v41.md new file mode 100644 index 0000000..1ca87ab --- /dev/null +++ b/History/upload-analysis-v41.md @@ -0,0 +1,76 @@ +# Запрос на анализ: XHR onerror («Сеть») при POST /upload с телом > 100KB + +**Дата:** 2026-06-17 +**Версия:** v1.41 +**ТОЛЬКО АНАЛИЗ. КОД НЕ ПРАВИТЬ.** + +--- + +## Что поменялось в понимании + +После просмотра текущего кода причина выглядит уже не как `request.get_data()` сама по себе. + +Ветка `/upload` сейчас делает так: + +1. читает всё тело через `request.get_data(as_text=True)` +2. выдёргивает `filename`, `data`, `cid` regex-ом +3. если `cid` пустой, синхронно создаёт контракт в PostgreSQL +4. затем синхронно вызывает `redis_client.push(...)` +5. только после этого возвращает `jsonify({"contract_id": ...})` + +Самое подозрительное место — именно синхронный Redis push в запросе, а не чтение тела. Если Redis/DNS/сеть внутри кластера тормозит или подвисает, клиент уже видит `xhr.onerror` как «Сеть», хотя тело запроса успело уйти целиком. + +--- + +## Текущий код + +```python +# app.py +raw = request.get_data(as_text=True) +... +redis_client.push({"filename": filename, "b64": b64, "mime": mime, "cid": str(cid)}) +return jsonify({"contract_id": str(cid)}) +``` + +```python +# redis_client.py +_r = redis.Redis( + host=os.getenv("REDIS_HOST", "redisk8s....svc.cluster.local"), + port=int(os.getenv("REDIS_PORT", "6379")), + password=os.getenv("REDIS_PASS", "..."), + decode_responses=True, + socket_connect_timeout=5, +) + +def push(task: dict): + _get().rpush(UPLOAD_QUEUE, json.dumps(task, ensure_ascii=False)) +``` + +--- + +## Локальная гипотеза + +`request.get_data()` не ломает соединение на 200KB. Запрос уже дошёл. + +Соединение рвётся или уходит в таймаут на этапе синхронного `rpush()` в Redis, либо на первом обращении к Redis-клиенту, если DNS/соединение внутри кластера нестабильны. Это объясняет, почему: + +- маленькие тела проходят чаще; +- сервер иногда успевает обработать и записать лог; +- `xhr.upload` показывает 100%, а затем приходит `onerror`; +- `curl` даёт плавающий `HTTP:000` / `ReadTimeout`. + +--- + +## Что надо проверить первым + +Самый дешёвый дискриминирующий тест — замерить время именно вокруг `redis_client.push()` и временно обойти его для `/upload`. + +Если ответ сразу стабилизируется, проблема подтверждается. + +Если нет, следующая точка проверки — внешний таймаут/обрыв на ingress или gunicorn, но по текущему коду Redis выглядит ближе всего к критическому узлу. + +--- + +## Вывод + +В v1.40 сместили фокус с парсинга тела на более поздний синхронный шаг. Сам `get_data()` теперь выглядит не как корень проблемы, а как просто первая стадия запроса. Корневой кандидат — блокирующий `redis_client.push()` в обработчике `/upload`.