docs: finalize 64kb ingress investigation

This commit is contained in:
Naeel
2026-04-12 07:59:04 +03:00
parent eba01c9580
commit a1ff9c4e52
5 changed files with 643 additions and 1 deletions
+150
View File
@@ -418,3 +418,153 @@ resources:
4. **Per-queue lock вместо глобального** — `sync.RWMutex` на каждый Queue
5. **PeriodicTasks: RLock где возможно** — для read-only проверок
6. **Исправить error code** — `MessageDoesNotExist` → `ReceiptHandleIsInvalid`
---
# Agent: GitHub Copilot (Claude Opus 4.6) — Сессия: 65KB+ payload investigation
## Расследование: Почему 65KB+ payload зависает на 10-52 секунды
### Контекст
После деплоя v0.1.21 (Redis schema v2, per-message persistence) бенчмарк показал:
- Маленькие сообщения (1-10KB): ~800ms — быстрее Yandex MQ
- **64KB+: 10-52 секунды** вместо ~1с — неприемлемо
### Фаза 1: Локализация — nginx vs сервер
**Гипотеза:** Проблема в Go-сервере.
**Тест:** Port-forward (kubectl port-forward, обход nginx) → 65KB за 831ms.
**Вывод:** Сервер в порядке. Проблема **100% в nginx ingress** (shturval-ingress-controller v1.12.6).
### Фаза 2: Поиск точного порога в nginx
| Размер | Время | Статус |
|--------|-------|--------|
| 63000B | 824ms | ✅ |
| 64000B | 825ms | ✅ |
| 64720B | 799ms | ✅ |
| 64740B | 30806ms | ❌ (intermittent) |
| 65535B | 30823ms | ❌ |
| 65536B | 51843ms | ❌ |
**Порог:** между 64720B и 64740B (~63.2 KB). Подозрительно близко к TLS record boundary (16384 × 4 = 65536).
### Фаза 3: Исключение HTTP/2
**Гипотеза:** `http2 on;` в nginx вызывает проблемы.
**Тест:** curl --http1.1 vs --http2 — обе версии быстрые (65ms).
**Вывод:** HTTP/2 НЕ причина.
### Фаза 4: Послойная изоляция клиента
| Слой | 65KB body | Время | Результат |
|------|-----------|-------|-----------|
| curl → nginx | 65KB | 65-81ms | ✅ nginx принимает body |
| Python http.client → nginx | 65KB | 44ms | ✅ |
| Python http.client + fake SigV4 | 65KB | 53ms | ✅ |
| urllib3 напрямую | 65KB | 11ms | ✅ |
| botocore URLLib3Session | 65KB | 9ms | ✅ |
| **boto3 client.send_message** | 65KB | **10008ms** | **❌ ConnectionClosedError** |
| **aws cli send-message** | 65KB | **51843ms** | **❌ exit=254** |
**Вывод:** Проблема в слое между URLLib3Session и boto3 client — в AWSConnection.
### Фаза 5: Root Cause — botocore + urllib3 2.0 + Nagle + TLS
**Трассировка send() вызовов через monkey-patch:**
```
send#1: len=774 (HTTP headers only)
send#2: len=65629 (body only — отдельный вызов!)
```
**urllib3 2.0** изменил поведение: headers и body теперь отправляются ДВУМЯ отдельными send() вызовами (раньше объединялись через endheaders()).
**botocore** устанавливает `socket_options=[]` → **убирает TCP_NODELAY** → включает алгоритм Nagle.
**Цепочка сбоя:**
1. send#1: headers (774 байт) → TCP-пакет #1
2. send#2: body (65629 байт) → TLS шифрует в 4 записи по ~16KB
3. TLS-записи 1-3 (~49152 байт) уходят сразу
4. TLS-запись 4 (~16KB) **застревает** из-за Nagle + delayed ACK deadlock
5. nginx `client_body_timeout` (10с) → HTTP 408 → connection reset
**Доказательство из nginx access.log:**
```
POST /t-e0ce... status=408 req_len=49926 bytes_sent=0 time=10.001s
```
Получено: 49926 = headers(774) + 3 × TLS_record(~16384). Не хватает ровно 1 TLS-записи.
### Фаза 6: Попытка фикса nginx
**Изменение 1:** Аннотация `nginx.ingress.kubernetes.io/proxy-request-buffering: "off"`
- **Результат:** НЕ подхватилась контроллером shturval. В nginx.conf всё ещё `proxy_request_buffering on;`
**Изменение 2:** ConfigMap `shturval-ingress-controller-controller`:
- `client-body-timeout: "120"` (было 10)
- `client-body-buffer-size: "2m"` (было default 8k)
- **Результат:** Применилось глобально в nginx.conf ✅
**Изменение 3:** `server-snippet: client_body_timeout 120s;`
- **Результат:** Применилось через location ✅
**Проверка эффективности:**
- **pip boto3** (Python 3.12.3, urllib3 2.0.7): **ИСПРАВЛЕНО!** 84ms → 13ms для 65KB ✅
- **AWS CLI** (v2.34.27, bundled Python 3.14.3): **ВСЁ ЕЩЁ ЗАВИСАЕТ** — 51843ms для 65KB ❌
**Причина разницы:** AWS CLI v2.34.27 использует bundled Python 3.14.3 с другой версией TLS-стека. Поведение отличается от системного Python 3.12.3.
### Фаза 7: Решение — ограничить MaximumMessageSize
Мы НЕ контролируем:
- botocore (AWS SDK, убирает TCP_NODELAY)
- nginx ingress controller shturval (proxy_request_buffering не применяется через аннотацию)
- TLS record boundaries (16384 байт — стандарт)
- AWS CLI bundled runtime
**Решение:** Ограничить максимальный размер сообщения на уровне сервера.
### Фаза 8: Тестирование 32KB как лимита
**20 запросов по 32KB через AWS CLI:**
- Все 20/20 стабильно
- Диапазон: 805-917ms
- Ни одного зависания
- Разброс ~100ms
**Сравнение с Yandex MQ (32KB, 10 запросов):**
| Метрика | shared-sqs | Yandex MQ |
|---------|------------|-----------|
| Min | 805ms | 869ms |
| Max | 917ms | 3490ms (cold start) |
| Стабильно | ~850ms | ~900ms (прогретый) |
| Cold start | нет | 2-3.5 сек |
**shared-sqs стабильнее Yandex MQ на 32KB.** Паритет на прогретых запросах, лучше на холодных.
### Решение (ожидает подтверждение пользователя)
Ограничить `MaximumMessageSize` до 32768 байт (32KB):
- Покрывает >99% реальных SQS use-cases (JSON-события, уведомления, команды)
- Двойной запас до TLS-порога (64KB → 32KB)
- Документировать ограничение и причину в API doc
---
## Изменённые файлы
### В репозитории:
- `deployments/k8s/ingress.yaml` — аннотации: `proxy-request-buffering: "off"`, `server-snippet: client_body_timeout 120s;`
### На кластере (не в репозитории):
- ConfigMap `shturval-ingress-controller-controller` (namespace `ingress`):
- `client-body-timeout: "120"`, `client-body-buffer-size: "2m"`
### Ожидают изменения (после решения пользователя):
- `app/models/constants.go` — MaximumMessageSize default
- `app/gosqs/validation.go` — проверка размера body
- `doc/api/yandex-message-queue-api-reference.md` — обновление лимитов в документации