Files
SQS-service/doc/thinking/2026-04-12.md
T

17 KiB
Raw Blame History

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 считать безопасным эксплуатационным размером и документировать это как известную особенность инфраструктуры.