test: stress_http --file (реальные файлы); History: 100KB прямой POST — 83% таймаутов (шлюз рвёт >64KB), 30KB реальный — 200/200 ok

This commit is contained in:
“Naeel”
2026-08-26 20:35:48 +03:00
parent 6fcbbddbdb
commit 08e8e3afec
2 changed files with 36 additions and 4 deletions
@@ -0,0 +1,22 @@
# Стресс прод: 100KB прямой POST — 83% таймаутов (2026-08-26)
## Прогон
`stress_http.py --url https://contractor.pythonk8s.dev.nubes.ru --n 300 --threads 20 --size 100000`
(файл `load.docx`, невалидный, 100KB)
Результат: `ok=51, err=249, timeouts=249, elapsed=398.5s, rps=0.8`.
## Находка
- **100KB прямой POST `/upload` через managed-шлюз → 83% обрывов** (таймауты ~30с).
- Подтверждает известное ограничение платформы: **шлюз рвёт тело >~64KB**
(история 2026-08-18, ingress-аннотация / client_max_body_size).
- Это НЕ деградация приложения: под почти не грузился (CPU 15m, MEM 66Mi) — обрывает внешний шлюз.
- Именно поэтому и внедрён **VM-буфер** (PUT на ВМ WebDAV → egress GET): прямой большой
POST через шлюз для прода непригоден.
## Вывод
- Стресс `/upload` валиден только файлами **< 64KB** (реальные документы 17–36KB).
- Большие файлы — только через VM-буфер (`/api/upload_refs`), не прямым POST.
+14 -4
View File
@@ -24,11 +24,21 @@ def main():
ap.add_argument("--url", default="http://127.0.0.1:5000") ap.add_argument("--url", default="http://127.0.0.1:5000")
ap.add_argument("--n", type=int, default=1000, help="число запросов") ap.add_argument("--n", type=int, default=1000, help="число запросов")
ap.add_argument("--threads", type=int, default=32, help="параллельных потоков") ap.add_argument("--threads", type=int, default=32, help="параллельных потоков")
ap.add_argument("--size", type=int, default=100000, help="размер файла в байтах") ap.add_argument("--size", type=int, default=100000, help="размер файла в байтах (если не задан --file)")
ap.add_argument("--file", default=None, help="путь к реальному файлу (заменяет сгенерированный)")
ap.add_argument("--health-only", action="store_true", help="только /health, без загрузки файлов") ap.add_argument("--health-only", action="store_true", help="только /health, без загрузки файлов")
args = ap.parse_args() args = ap.parse_args()
payload = b"x" * args.size if args.file:
import os as _os
fname = _os.path.basename(args.file)
with open(args.file, "rb") as _f:
payload = _f.read()
real_size = len(payload)
else:
fname = "load.docx"
payload = b"x" * args.size
real_size = args.size
ok = 0 ok = 0
err = 0 err = 0
slow = 0 slow = 0
@@ -43,7 +53,7 @@ def main():
else: else:
r = c.post( r = c.post(
f"{args.url}/upload", f"{args.url}/upload",
files={"files": ("load.docx", payload, "application/octet-stream")}, files={"files": (fname, payload, "application/octet-stream")},
) )
if r.status_code == 200: if r.status_code == 200:
ok += 1 ok += 1
@@ -60,7 +70,7 @@ def main():
elapsed = time.time() - t0 elapsed = time.time() - t0
print(f"url={args.url}") print(f"url={args.url}")
print(f"n={args.n} threads={args.threads} size={args.size}B health_only={args.health_only}") print(f"n={args.n} threads={args.threads} size={real_size}B file={args.file or '(gen)'} health_only={args.health_only}")
print(f"ok={ok} err={err} timeouts={slow} elapsed={elapsed:.1f}s rps={args.n / elapsed:.1f}") print(f"ok={ok} err={err} timeouts={slow} elapsed={elapsed:.1f}s rps={args.n / elapsed:.1f}")