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

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


Внешние ссылки для возврата к теме 64KB+

Если к проблеме придётся вернуться через недели или месяцы, начинать смотреть отсюда.

Платформа Штурвал

Зачем это важно:

  • страница подтверждает, что Nginx Ingress Controller входит в состав платформы;
  • это усиливает вывод, что ingress controller для qu.kube5s.ru является platform-managed компонентом, а не частью shared-sqs.

Официальная документация ingress-nginx

Ключевые места в этих документах:

  • nginx.ingress.kubernetes.io/proxy-request-buffering официально поддерживается;
  • allow-snippet-annotations по умолчанию имеет значение false;
  • server-snippet и configuration-snippet нельзя считать доступным workaround без platform-level разрешения.

Upstream исходники ingress-nginx

Зачем это важно:

  • это прямое подтверждение, что в upstream поведение поддерживается и ожидается;
  • если проблема повторится, не надо заново спорить, существует ли такая аннотация вообще.

Что проверить первым делом при новом раунде расследования

  1. Существует ли по-прежнему только один ingress для qu.kube5s.ru.
  2. Осталась ли аннотация proxy-request-buffering: "off" на объекте ingress.
  3. Что реально сгенерировано в /etc/nginx/nginx.conf у ingress controller.
  4. Не изменились ли версия штурвала и версия ingress-nginx controller.
  5. Не включили ли на platform уровне allow-snippet-annotations.
  6. Не появился ли доступ к source-of-truth конфигурации платформенного ingress controller.

Задача: собрать отдельный сравнительный benchmark-отчёт только до 32KB

Контекст

После завершения расследования по 64KB+ пользователь явно зафиксировал новую рамку: в сравнительном отчёте не трогать 64KB и выше, а ограничиться practically useful диапазоном до 32KB.

Что сделал

  1. Выделил отдельный benchmark-сценарий tests/benchmark_compare_32k.sh, чтобы не смешивать его с прежними широкими сценариями.
  2. Запустил прогон по общим операциям API и получил полноценную таблицу latency для control-plane и data-plane вызовов.
  3. Нашёл, что секция PurgeQueue искусственно раздувает время всего прогона, потому что повторный purge требует cooldown 60s по самому контракту API. Убрал многократные sleep и оставил одиночный контрольный замер.
  4. Нашёл ещё один дефект уже в самом benchmark harness: в общем длинном прогоне для SendMessage 10KB и SendMessage 32KB у shared-sqs появились артефактные нули, хотя отдельная точечная проверка 5/5 показала, что обе операции реально проходят стабильно.
  5. Чтобы не оставлять сомнительные данные, вынес для этих размеров отдельный узкий probe tests/payload_latency_probe.sh и снял повторные latency-цифры отдельно.
  6. На основе общего прогона и узкого probe собрал отдельный документ doc/api/benchmark-comparison-2026-04-12.md.

Что подтвердилось

  • До 32KB shared-sqs не проиграл Yandex MQ ни по одной из общих измеренных операций.
  • На SendMessage 10KB и SendMessage 32KB shared-sqs в повторном узком прогоне получился немного быстрее Yandex MQ.
  • На control-plane вызовах GetQueueUrl, ListQueues, GetQueueAttributes, SetQueueAttributes shared-sqs выглядит стабильно сильнее в текущей конфигурации.
  • Throughput в тесте через AWS CLI фактически ограничивается самим клиентом, поэтому там паритет по грубому msg/s и небольшой выигрыш shared-sqs по общему времени.

Почему это важно

Этот отчёт теперь отделяет две разные темы, которые раньше легко спутать:

  • вопрос прикладной конкурентоспособности shared-sqs в practically useful диапазоне до 32KB;
  • отдельную transport/platform проблему 64KB+, уже локализованную на ingress path.

Именно такое разделение и нужно, чтобы дальше не смешивать хорошие рабочие метрики сервиса с чужим инфраструктурным ограничением.