diff --git a/doc/decisions/message-size-limit-2026-04-11.md b/doc/decisions/message-size-limit-2026-04-11.md new file mode 100644 index 0000000..04d0aa8 --- /dev/null +++ b/doc/decisions/message-size-limit-2026-04-11.md @@ -0,0 +1,103 @@ +# Decision: Ограничение MaximumMessageSize — 2026-04-11 + +## Update — 2026-04-12 + +Первичное решение из этой записи было пересмотрено после дополнительного расследования ingress controller штурвала. + +### Новый финальный статус решения +- **не вводить** hard-limit 32KB в код shared-sqs; +- **оставить** протокольный максимум SQS без искусственного server-side урезания; +- **документировать** 32KB как безопасный практический размер для AWS CLI/botocore при текущем ingress path. + +### Почему решение изменено +На момент первоначальной записи было уже понятно, что 32KB работает стабильно, но не было окончательно доказано, где именно ломается путь 64KB+. + +Дополнительное расследование 2026-04-12 показало: +1. В кластере для `qu.kube5s.ru` существует только один ingress. +2. Объект ingress у shared-sqs содержит `proxy-request-buffering: "off"`. +3. Платформенный ingress controller штурвала рендерит для этого host `proxy_request_buffering on;` несмотря на annotation override. +4. Upstream ingress-nginx такую аннотацию официально поддерживает, значит это platform-specific limitation/bug, а не ограничение shared-sqs. + +Следовательно, code-level лимит 32KB был бы не корневым исправлением, а маскировкой внешней инфраструктурной проблемы внутри приложения. + +## Контекст + +При тестировании shared-sqs v0.1.21 обнаружено, что сообщения размером 65KB+ зависают на 10-52 секунды при отправке через AWS CLI / boto3. + +## Проблема + +### Root Cause (подтверждённый) + +**Цепочка сбоя:** urllib3 2.0 + botocore + TLS + Nagle's algorithm + nginx. + +1. **urllib3 2.0** изменил API: headers и body отправляются двумя отдельными `send()` вызовами (раньше одним через `endheaders()`) +2. **botocore** устанавливает `socket_options=[]`, что убирает `TCP_NODELAY` → включает алгоритм Nagle +3. Body (65629 байт) шифруется TLS в 4 записи по ~16KB +4. Первые 3 записи (49152 байт) отправляются сразу +5. Последняя 4-я запись (~16KB) **застревает** из-за Nagle + delayed ACK deadlock +6. nginx `client_body_timeout` срабатывает → HTTP 408 → connection reset + +### Доказательство + +nginx access.log при сбое: +``` +POST status=408 req_len=49926 bytes_sent=0 time=10.001s +``` +49926 = headers(774) + 3 × TLS_record(~16384) — ровно на 1 TLS-запись меньше чем нужно. + +### Что мы НЕ контролируем + +- botocore (AWS SDK) — убирает TCP_NODELAY, мы не можем повлиять +- urllib3 2.0 — split headers/body, это багфикс а не баг +- nginx ingress controller (shturval) — аннотация `proxy-request-buffering: "off"` не применяется +- AWS CLI bundled runtime (Python 3.14.3 с другим TLS-стеком) + +### Что мы пробовали + +1. **proxy-request-buffering: off** — аннотация не подхватывается shturval controller ❌ +2. **client_body_timeout: 120s** — помогло для pip boto3, но НЕ для AWS CLI ❌ +3. **client_body_buffer_size: 2m** — не помогло для AWS CLI ❌ +4. **TCP_NODELAY patch** — помогает частично (1-й запрос fails, остальные OK) — не production решение ❌ + +## Решение + +Первоначальная идея: ограничить `MaximumMessageSize` до **32768 байт (32 KB)**. + +Финальное решение после дополнительного расследования: **не вводить hard-limit в коде**, а использовать 32KB как **операционную рекомендацию**. + +### Обоснование + +1. **32KB стабильно:** 20 из 20 запросов через AWS CLI = 805-917ms, ни одного зависания +2. **Двойной запас:** от порога сбоя (64720B) до лимита (32768B) — двойной запас +3. **Покрывает use-cases:** >99% SQS-сообщений — JSON, уведомления, команды (<10KB) +4. **Паритет с Yandex MQ:** на 32KB shared-sqs стабильнее (нет cold start penalty) +5. **Честный лимит:** лучше явный лимит чем молчаливые зависания на 52 секунды + +### Бенчмарк 32KB + +**shared-sqs (20 запросов):** +- Min: 805ms, Max: 917ms, Avg: ~860ms +- 0 зависаний из 20 + +**Yandex MQ (10 запросов):** +- Min: 869ms, Max: 3490ms (cold start), Avg: ~920ms (прогретый) +- Cold start: 2-3.5 секунды + +### Альтернативы (рассмотренные и отвергнутые) + +| Вариант | Почему нет | +|---------|------------| +| 256KB (как AWS SQS) | Зависает на 52 секунды, не работает | +| 64KB (ближе к порогу) | Впритык к границе, рискованно — 720 байт запас | +| Фиксить nginx controller | Мы не контролируем shturval, нет исходников | +| Monkey-patch botocore | Не production, каждое обновление AWS CLI сломает | +| Отдельный endpoint без nginx | Over-engineering для MVP | + +## Статус + +✅ Решение принято: **не менять код shared-sqs** ради этого кейса. + +## Практический вывод + +1. Для повседневного использования через AWS CLI/botocore ориентироваться на payload до 32KB. +2. Если в будущем потребуется надёжная поддержка 64KB+ для этих клиентов, исправление нужно делать в platform ingress layer, а не в shared-sqs. diff --git a/doc/errors/65kb-payload-timeout-2026-04-11.md b/doc/errors/65kb-payload-timeout-2026-04-11.md new file mode 100644 index 0000000..59f01d0 --- /dev/null +++ b/doc/errors/65kb-payload-timeout-2026-04-11.md @@ -0,0 +1,112 @@ +# Error Log: 65KB+ Payload Timeout — 2026-04-11 + +## Update — 2026-04-12 + +После дополнительного расследования в кластере инцидент переклассифицирован. + +### Финальный статус +- shared-sqs backend **не является** первичной причиной зависания; +- проблема локализована на стороне **platform ingress path**; +- конкретно: ingress controller штурвала **не применяет** `nginx.ingress.kubernetes.io/proxy-request-buffering: "off"`, хотя upstream ingress-nginx эту аннотацию поддерживает; +- часть других аннотаций при этом применяется корректно, значит речь не о полном игноре ingress-ресурса, а о selective handling/bug/platform override. + +### Дополнительные подтверждения +1. Для host `qu.kube5s.ru` существует только один ingress — `shared-sqs-ingress`, значит это не конфликт нескольких ingress-объектов. +2. В объекте ingress аннотация `proxy-request-buffering: "off"` присутствует. +3. В итоговом `nginx.conf` для `qu.kube5s.ru` остаётся `proxy_request_buffering on;`. +4. В шаблоне контроллера `/etc/nginx/template/nginx.tmpl` директива не захардкожена; там используется значение `location.Proxy.RequestBuffering`, то есть проблема происходит до фазы рендеринга финального location config. +5. `server-snippet`/`configuration-snippet` нельзя считать рабочим обходным путём без platform-level изменений, так как snippet-аннотации контролируются `allow-snippet-annotations`, а по умолчанию этот режим отключён. + +### Операционный вывод +Это **известное ограничение инфраструктуры**, а не дефект бизнес-логики shared-sqs. + +### Итоговое решение +- hard-limit 32KB в код shared-sqs **не внедряется**; +- 32KB остаётся **практической рекомендацией** для AWS CLI/botocore-клиентов в текущей инфраструктуре; +- приоритет дальнейших работ по этой теме переносится с shared-sqs на platform ingress controller. + +## Симптом + +SendMessage с телом ≥64740 байт зависает на 10-52 секунды и возвращает ошибку (ConnectionClosedError / exit=254). + +**Воспроизведение:** +```bash +# Генерим payload 65KB +python3 -c "print('A'*65536)" > /tmp/big.txt + +# Отправляем через AWS CLI — зависает на ~52 секунды +aws --endpoint-url https://qu.kube5s.ru sqs send-message \ + --queue-url "https://qu.kube5s.ru/TENANT_ID/QUEUE_NAME" \ + --message-body "file:///tmp/big.txt" +``` + +## Диагностика + +### 1. Сервер — OK +Port-forward (обход nginx): 65KB за 831ms. Сервер не виноват. + +### 2. nginx ingress — частично виноват +`client_body_timeout: 10s` — nginx закрывает соединение если тело не пришло за 10 секунд. + +### 3. Root Cause — botocore + urllib3 2.0 + TLS + Nagle + +**urllib3 2.0** посылает headers и body двумя отдельными `send()`: +``` +send#1: len=774 (headers) +send#2: len=65629 (body) +``` + +**botocore** ставит `socket_options=[]` → убирает `TCP_NODELAY` → Nagle ON. + +**Body** шифруется TLS в 4 записи по ~16KB. Первые 3 уходят, 4-я **застревает** из-за Nagle + delayed ACK deadlock. + +**nginx access.log:** +``` +POST status=408 req_len=49926 bytes_sent=0 time=10.001s +``` +Получено 49926 байт = headers(774) + 3 × TLS(~16384). Не хватает ровно 1 TLS-записи. + +### 4. Порог + +| Размер | Время | Статус | +|--------|-------|--------| +| 64000B | 825ms | ✅ | +| 64720B | 799ms | ✅ | +| 64740B | 30806ms | ❌ intermittent | +| 65536B | 51843ms | ❌ всегда | + +### 5. Версии ПО + +| Компонент | Версия | +|-----------|--------| +| nginx ingress | shturval-ingress-controller v1.12.6 | +| AWS CLI | v2.34.27 | +| AWS CLI Python | 3.14.3 (bundled) | +| System Python | 3.12.3 | +| System urllib3 | 2.0.7 | +| System botocore | 1.42.86 | + +## Что пробовали + +### Помогло частично: +- **ConfigMap `client-body-timeout: "120"`** — pip boto3 (Python 3.12) стал работать (84ms → 13ms) +- **ConfigMap `client-body-buffer-size: "2m"`** — применилось глобально + +### Не помогло: +- **Аннотация `proxy-request-buffering: "off"`** — shturval controller не применяет +- **`server-snippet: client_body_timeout 120s;`** — применилось но AWS CLI всё равно зависает +- **TCP_NODELAY patch через monkey-patch** — нестабильно (1-й запрос fails) + +## Решение + +Кодовый hard-limit не вводим. + +Практическое правило эксплуатации: +- для AWS CLI / botocore в текущей ingress-инфраструктуре безопасно ориентироваться на 32KB; +- протокольный лимит SQS остаётся 256KB; +- проблема 64KB+ описывается как platform ingress limitation. + +См. [решение](../decisions/message-size-limit-2026-04-11.md). + +## Статус +✅ Исследование завершено. Root cause локализован до platform ingress layer; изменение в коде shared-sqs не требуется. diff --git a/doc/progress.md b/doc/progress.md index 6664705..dd5f865 100644 --- a/doc/progress.md +++ b/doc/progress.md @@ -2,7 +2,45 @@ ## Версия v0.1.x -### v0.1.19 (2026-04-11) ✅ — ТЕКУЩАЯ DEPLOYED +### v0.1.21 (2026-04-11) — Redis schema v2 + per-message persistence +- ✅ Redis schema v2: metadata в HASH `ssq:queues`, сообщения в отдельных HASH `ssq:msg:{queueKey}` +- ✅ Per-message persistence: каждая операция (send/receive/delete/visibility) пишет только затронутое сообщение +- ✅ Migration v1→v2: автоматическая миграция при старте (59 очередей мигрировано) +- ✅ quick_test: 31/31 PASS +- ✅ shared_sqs_test: 28/28 PASS +- **Docker image:** `naeel/shared-sqs:v0.1.21` + +### Производительность v0.1.21 (benchmark) + +| Размер | shared-sqs | Yandex MQ | Сравнение | +|--------|-----------|-----------|-----------| +| 1KB | ~800ms | ~700ms | Паритет | +| 10KB | ~800ms | ~700ms | Паритет | +| 32KB | **805-917ms** | 869-3490ms | **Быстрее** (нет cold start) | +| 64KB+ | ❌ 10-52s зависание | ~900ms | Проблема nginx+botocore | + +### Проблема 65KB+ payload (расследование 2026-04-11) + +**Root cause:** botocore (AWS SDK) + urllib3 2.0 + TLS record boundary. +- urllib3 2.0 отправляет headers и body двумя отдельными send() вызовами +- botocore убирает TCP_NODELAY (алгоритм Nagle включён) +- Последняя TLS-запись (~16KB) застревает из-за Nagle + delayed ACK +- nginx `client_body_timeout` срабатывает → HTTP 408 + +**Попытка фикса nginx:** +- ConfigMap: `client-body-timeout: "120"`, `client-body-buffer-size: "2m"` — применилось +- Аннотация `proxy-request-buffering: "off"` — НЕ подхватилась shturval controller +- pip boto3 (Python 3.12): исправлено (84ms → 13ms) ✅ +- AWS CLI (Python 3.14.3 bundled): всё ещё зависает (51843ms) ❌ + +**Финальный вывод (2026-04-12):** +- проблема локализована на стороне platform ingress controller штурвала, а не в Go-сервисе shared-sqs; +- ingress приложения корректен, но controller выборочно применяет аннотации: `proxy-body-size` и `client-body-buffer-size` доходят до nginx.conf, а `proxy-request-buffering` остаётся `on`; +- upstream ingress-nginx эту аннотацию поддерживает, значит это platform-specific limitation/bug; +- hard-limit 32KB в код shared-sqs НЕ вводим; +- 32KB остаётся практической рекомендацией для AWS CLI / botocore в текущей инфраструктуре. + +### v0.1.19 (2026-04-11) ✅ — ПРЕДЫДУЩАЯ DEPLOYED - ✅ Валидация VisibilityTimeout (0–43200) в ReceiveMessage - ✅ Валидация WaitTimeSeconds (0–20) в ReceiveMessage - ✅ Пустой MessageBody → MissingParameter в SendMessage diff --git a/doc/thinking/2026-04-11.md b/doc/thinking/2026-04-11.md index a79fc66..c0c637b 100644 --- a/doc/thinking/2026-04-11.md +++ b/doc/thinking/2026-04-11.md @@ -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` — обновление лимитов в документации diff --git a/doc/thinking/2026-04-12.md b/doc/thinking/2026-04-12.md new file mode 100644 index 0000000..8386aeb --- /dev/null +++ b/doc/thinking/2026-04-12.md @@ -0,0 +1,239 @@ +# Thinking Log — 2026-04-12 +# Agent: GitHub Copilot (GPT-5.4) + +--- + +## Задача: Довести расследование проблемы 64KB+ payload до окончательного технического вывода + +### Контекст +На момент начала этой сессии уже было подтверждено следующее: +- сервер shared-sqs после перехода на Redis schema v2 и per-message persistence работает быстро на малых и средних сообщениях; +- проблема проявляется именно на payload около 64KB и выше; +- через port-forward тот же запрос проходит быстро, значит Go-сервис и Redis не являются первичным узким местом; +- через ingress проблема воспроизводится у boto3 и AWS CLI, но не воспроизводится у curl, http.client и низкоуровневого urllib3. + +Главный незакрытый вопрос был таким: это баг нашего сервиса или поведение платформенного ingress controller штурвала? + +### Рабочая гипотеза в начале сессии +Если объект ingress у shared-sqs настроен корректно, а итоговый nginx.conf внутри ingress controller не отражает часть аннотаций, то причина находится в платформенном ingress controller, а не в приложении. + +### Почему выбрал именно эту гипотезу +Потому что она была самой дешёвой для проверки и лучше всего объясняла противоречие: +- в YAML ingress аннотация `nginx.ingress.kubernetes.io/proxy-request-buffering: "off"` есть; +- в фактическом nginx.conf для host `qu.kube5s.ru` всё равно остаётся `proxy_request_buffering on;`. + +Если это подтверждается, дальнейший поиск в коде shared-sqs теряет смысл. + +--- + +## Ход расследования + +### 1. Проверка, чей это ingress вообще + +Сначала была цель не гадать, а проверить ownership в кластере. + +Что было подтверждено: +- ingress для `qu.kube5s.ru` обслуживается IngressClass `nginx`; +- этот класс ведёт на deployment `shturval-ingress-controller-controller`; +- контроллер живёт в namespace `ingress`; +- используется образ `r.shturval.tech/ingress-nginx/controller:v1.12.6`; +- это не ingress, встроенный в shared-sqs, а платформенный ingress controller кластера. + +Вывод: проблема находится в общей ingress-инфраструктуре штурвала. + +### 2. Проверка, нет ли конфликта нескольких ingress-ресурсов + +Следующая гипотеза была локальная и простая: возможно, для одного host существует несколько Ingress-объектов, и location-блок в nginx собирается из другого ресурса, не из того YAML, который мы смотрим. + +Проверка показала: +- в кластере для host `qu.kube5s.ru` существует только один ingress: `shared-sqs/shared-sqs-ingress`. + +Вывод: это не конфликт нескольких ingress-объектов на один host. + +### 3. Сверка объекта ingress с фактическим nginx.conf + +Дальше был ключевой шаг: сравнить декларацию и факт. + +В самом ingress-объекте у shared-sqs присутствуют: +- `nginx.ingress.kubernetes.io/proxy-body-size: "10m"` +- `nginx.ingress.kubernetes.io/client-body-buffer-size: "512k"` +- `nginx.ingress.kubernetes.io/proxy-request-buffering: "off"` +- `nginx.ingress.kubernetes.io/server-snippet: client_body_timeout 120s;` +- proxy timeouts. + +В сгенерированном nginx.conf для `qu.kube5s.ru` было найдено: +- `client_max_body_size 10m;` +- `client_body_buffer_size 512k;` +- `proxy_send_timeout 30s;` +- `proxy_read_timeout 30s;` +- `proxy_buffering off;` +- `proxy_request_buffering on;` + +Это важнейшая развилка расследования. + +Что это означает: +- ingress controller видит ingress-ресурс; +- часть аннотаций применяет корректно; +- но конкретно `proxy-request-buffering` не доходит до итоговой конфигурации; +- следовательно проблема не в том, что ingress целиком игнорируется; +- проблема в selective handling конкретных директив/аннотаций. + +### 4. Проверка версии и документации ingress-nginx + +Дальше нужно было отсечь ещё одну ложную ветку: а вдруг upstream ingress-nginx вообще не поддерживает `proxy-request-buffering` в нашей версии? + +Проверка документации и исходников upstream ingress-nginx показала: +- аннотация `nginx.ingress.kubernetes.io/proxy-request-buffering` официально поддерживается; +- в коде есть parser для этого поля; +- в upstream есть e2e-тест на сценарий `should turn off proxy-request-buffering`; +- в шаблоне nginx используется переменная `location.Proxy.RequestBuffering`. + +Вывод: upstream ingress-nginx такую аннотацию умеет. Значит поведение штурвала отличается не потому, что аннотация “не существует”, а потому что либо: +- в платформенной сборке/конфигурации происходит баг; +- либо значение не прокидывается на этапе построения location model; +- либо контроллер живёт в состоянии, где дефолт `on` побеждает annotation override. + +### 5. Проверка самого шаблона в pod ingress controller + +Чтобы не строить догадки про кастомный шаблон, была проверка прямо внутри pod. + +Что найдено: +- в `/etc/nginx/template/nginx.tmpl` директива не захардкожена; +- там стоит шаблонная подстановка `{{ $location.Proxy.RequestBuffering }}`; +- в итоговом `/etc/nginx/nginx.conf` для конкретного host всё равно стоит `on`. + +Это сузило диагноз ещё сильнее: +- шаблон не виноват; +- parser и upstream поддержка есть; +- значит проблема в данных, которыми шаблон кормят, либо в платформенном runtime поведении контроллера. + +Именно здесь стало окончательно понятно, что дальше копать shared-sqs бессмысленно. + +### 6. Почему `server-snippet` тоже не дал ожидаемого эффекта + +Параллельно был вопрос: если `proxy-request-buffering` не работает, можно ли продавить workaround через snippet. + +Проверка документации ingress-nginx показала: +- snippet-аннотации по умолчанию контролируются флагом `allow-snippet-annotations`; +- его дефолтное значение — `false`. + +В ConfigMap ingress controller у штурвала этот флаг не был включён. + +Вывод: +- рассчитывать на `server-snippet` и `configuration-snippet` без изменения platform ConfigMap нельзя; +- даже если ingress-объект принимает такую аннотацию, итоговая конфигурация может её не внедрить по политике безопасности. + +### 7. Наблюдение про ConfigMap drift + +Ещё один важный операционный вывод дал повторный просмотр ConfigMap ingress controller. + +Ранее вручную поднимался `client-body-timeout`, но позже в ConfigMap снова был виден `client-body-timeout: "10"`. + +Это сильный индикатор того, что: +- ручные правки штурвального ingress controller могут откатываться; +- platform layer, вероятно, управляется Helm/GitOps/reconcile-процессом; +- даже если бы ручной patch помог, он мог бы быть временным. + +Вывод: править такие настройки нужно не как разовую операцию в живом кластере, а в источнике правды платформы. + +--- + +## Итоговые технические выводы + +### Что подтверждено надёжно + +1. **Go-сервис shared-sqs не является первичной причиной зависания 64KB+ сообщений.** + Это доказано быстрым прохождением запросов через port-forward. + +2. **Проблемный слой находится на ingress path.** + Конкретно — в поведении платформенного ingress controller штурвала. + +3. **Ingress YAML приложения сам по себе не является ошибочным.** + Нужные аннотации на объекте есть. + +4. **Контроллер применяет аннотации выборочно.** + `proxy-body-size` и `client-body-buffer-size` доходят до nginx.conf, а `proxy-request-buffering` — нет. + +5. **Upstream ingress-nginx поддерживает `proxy-request-buffering`.** + Следовательно, это не “неподдерживаемая фича”, а platform-specific проблема/баг/ограничение. + +6. **Snippet-аннотации в текущем штурвальном контроллере по факту недоступны как безопасный пользовательский workaround** без отдельного platform-level разрешения. + +7. **Ручные правки ConfigMap контроллера выглядят нестабильными и могут откатываться.** + +### Что больше НЕ считаю разумным делать + +1. Продолжать искать root cause в Go-коде shared-sqs. +2. Тратить время на новые попытки “починить” только ingress приложения без изменения platform controller. +3. Внедрять большой рефакторинг сервиса ради проблемы, лежащей за пределами сервиса. +4. Форсить hard-limit в коде без острой продуктовой необходимости. + +### Финальное продуктово-техническое решение + +После всех проверок наиболее прагматичный вывод такой: +- протокольный лимит SQS остаётся 256KB; +- **в коде shared-sqs hard-limit 32KB не вводим**; +- **операционно считаем 32KB безопасным практическим размером** для клиентов AWS CLI/botocore в текущей инфраструктуре; +- проблему 64KB+ классифицируем как ограничение платформенного ingress path, а не баг shared-sqs business logic. + +--- + +## Мысли и оценка инженерного качества решения + +### Почему не стоит вводить hard-limit 32KB в коде + +Изначально идея казалась хорошей: жёстко ограничить размер сообщения и снять проблему. + +Но по мере расследования стало ясно, что это слишком грубое лечение чужой инфраструктурной болезни. Если мы режем размер в коде, мы: +- маскируем platform issue под якобы ограничение сервиса; +- вводим продуктовое ограничение, которого нет в протоколе SQS; +- создаём технический долг: потом придётся объяснять, почему сервис “совместим с SQS”, но режет на 32KB. + +То есть hard-limit удобен как короткий workaround, но архитектурно это неправильное место для фикса. + +### Почему 32KB всё-таки остаётся хорошей практической рекомендацией + +Потому что 32KB: +- заметно ниже порога деградации; +- стабильно проходит через AWS CLI/botocore в текущем ingress path; +- покрывает подавляющее большинство типовых сообщений SQS; +- даёт пользователю рабочее эксплуатационное правило без вранья про реальные причины. + +### Почему идея “переписать сервис с нуля” не выглядит рациональной + +Этот вопрос возник естественно на фоне раздражения из-за 64KB+ проблемы. + +Но расследование показало обратное: +- core shared-sqs работает хорошо; +- сервер не является бутылочным горлышком в текущем кейсе; +- переписывание сервиса не уберёт поведение ingress controller штурвала; +- значит ROI у полного переписывания низкий. + +Гораздо разумнее развивать текущий код и отдельно эскалировать platform ingress issue. + +--- + +## Что считать окончательным статусом инцидента + +### Статус +**Исследование завершено на уровне, достаточном для инженерного решения.** + +### Причина остановки дальнейшего копания +Не потому, что “не нашли”, а потому что нашли достаточно: +- место проблемы локализовано; +- границы ответственности определены; +- прикладное решение выбрано; +- дальнейшее время будет тратиться уже с плохим ROI. + +### Если когда-нибудь возвращаться к теме +Возвращаться стоит только в двух случаях: +- если появится доступ к source-of-truth штурвального ingress controller; +- если 64KB+ payload станет реально важным use-case для пользователей. + +Иначе правильнее оставить это как известное platform limitation. + +--- + +## Короткий финальный вывод одним абзацем + +Проблема 64KB+ payload у shared-sqs оказалась не багом Go-сервиса и не проблемой Redis/persistence, а ограничением ingress path в кластере штурвала: объект ingress у приложения настроен корректно, часть аннотаций применяется, но именно `proxy-request-buffering` platform controller в итоговый nginx.conf не прокидывает, при этом upstream ingress-nginx такую аннотацию поддерживает. Поэтому вводить hard-limit 32KB в коде я считаю неправильным; правильное практическое решение на текущий момент — оставить сервис без искусственного code-level ограничения, а 32KB считать безопасным эксплуатационным размером и документировать это как известную особенность инфраструктуры. \ No newline at end of file