docs: finalize 64kb ingress investigation
This commit is contained in:
@@ -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` — обновление лимитов в документации
|
||||
|
||||
Reference in New Issue
Block a user