Files
contracts/History/upload-analysis-v40.md

4.0 KiB
Raw Permalink Blame History

Запрос на анализ: XHR onerror («Сеть») при POST /upload с телом > 100KB

Дата: 2026-06-17 Версия: v1.40 ТОЛЬКО АНАЛИЗ. КОД НЕ ПРАВИТЬ.


Симптом

Браузер отправляет XHR POST на /upload с JSON-телом ~200KB (base64 файла). Прогресс 100% (данные ушли). Затем xhr.onerror → «Сеть». Сервер иногда успевает обработать (логи подтверждают), иногда нет.

Прямой curl с такими же данными — иногда HTTP 200, иногда ReadTimeout.

Что ПРОБОВАЛИ (40 версий)

v Подход Результат
1.0-1.13 FormData multipart POST ✗ Сеть
1.14-1.20 Чанки 50KB, пул БД, keepalive
1.21-1.25 JSON/base64, TEXT вместо BYTEA частично ✓
1.26 original_bytes DROP NOT NULL + original_b64 TEXT ✓ РАБОТАЛО (!)
1.27-1.36 camelot, tabula, libreoffice для PDF регресс загрузки
1.37 Логи в памяти
1.38-1.39 Redis очередь (RPUSH → 200 сразу)
1.40 Сырой regex вместо get_json()

Текущий код (самое быстрое что смогли)

# app.py — НЕТ JSON-парсинга, НЕТ base64.decode(), НЕТ DB INSERT
def _upload_json_impl(self):
    raw = request.get_data(as_text=True)
    import re
    m = re.search(r'"data"\s*:\s*"([^"]*)"', raw)
    b64 = m.group(1)
    m = re.search(r'"filename"\s*:\s*"([^"]*)"', raw)
    filename = m.group(1) if m else ""
    m = re.search(r'"cid"\s*:\s*"([^"]*)"', raw)
    cid = m.group(1) if m else None

    # Контракт — синхронно (быстро)
    if not cid:
        conn = db.connect()
        cur = conn.cursor()
        cur.execute("INSERT INTO contracts ... RETURNING id")
        cid = cur.fetchone()[0]
        conn.commit()
        db.put_conn(conn)

    # Файл — в Redis (мгновенно, без DB)
    redis_client.push({"filename": filename, "b64": b64, "mime": mime, "cid": str(cid)})
    return jsonify({"contract_id": str(cid)})
# db.py — пул с keepalive
ThreadedConnectionPool(minconn=2, maxconn=10, keepalives=1, connect_timeout=10)
// upload.html — XHR без ретраев
var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.timeout = 30000;
xhr.onerror = function() { reject(new Error('Сеть')); };
xhr.send(JSON.stringify({filename: file.name, data: b64, cid: cid || null}));

Инфраструктура

Браузер → ddos-guard → nginx/Ingress → gunicorn → Flask
                                              ↓
                                    Redis (RPUSH) → воркер (BRPOP → DB)
  • Платформа: pythonk8s.services.ngcloud.ru (managed)
  • Redis: redisk8s....svc.cluster.local
  • PostgreSQL: postgresqlk8s-master...svc.cluster.local
  • Пул БД: 2-10 соединений, keepalive

Тесты curl

100KB #1: HTTP:000
100KB #2: HTTP:000
100KB #3: HTTP:200
100KB #4: HTTP:000
100KB #5: HTTP:000

10KB: всегда HTTP 200

Вопросы

  1. Почему request.get_data() для 200KB тела иногда убивает соединение ещё до ответа? Это на уровне gunicorn/Flask/Werkzeug?
  2. Может ли nginx/Ingress иметь client_max_body_size или proxy_request_buffering который режет тело?
  3. Почему v1.26 работал, а v1.40 с тем же /upload (без изменений в логике) — нет?
  4. Может ли проблема быть в HTTP/2 vs HTTP/1.1? curl с --http2 иногда помогает на этом хостинге.
  5. Redis внутри кластера — может ли redis.Redis() блокироваться на DNS-резолве .svc.cluster.local?