478 lines
34 KiB
Markdown
478 lines
34 KiB
Markdown
# 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 считать безопасным эксплуатационным размером и документировать это как известную особенность инфраструктуры.
|
||
|
||
---
|
||
# Agent: GitHub Copilot (GPT-5.4)
|
||
|
||
## Задача: дать обычному demo-пользователю понятный вход в UI без личного Nubes JWT
|
||
|
||
### Контекст
|
||
После переработки публичного showcase-репозитория осталась продуктовая дыра: AWS CLI уже имел публичные demo credentials, а UI по-прежнему требовал личный nubes JWT. Для обычного пользователя это ломало демо-сценарий: CLI можно попробовать сразу, а UI нет.
|
||
|
||
### Локальная гипотеза
|
||
Если в коде уже существует сидированный demo tenant с фиксированными AWS credentials, то самый дешёвый и чистый путь — не придумывать новую сущность, а добавить отдельный UI demo token, который логинит ровно в этот tenant. Тогда CLI и UI будут опираться на один и тот же демонстрационный контур.
|
||
|
||
### Что проверил перед правкой
|
||
1. В `app/admin/admin.go` единственный публичный UI login endpoint `POST /ui/api/auth` принимал только JWT, парсил claims и всегда вызывал `PingNubesAPI`.
|
||
2. В `app/ui/index.html` логин-форма явно требовала `API Token (nubes JWT)`.
|
||
3. В `app/cmd/seed.go` уже существует seeded demo tenant:
|
||
- tenant ID: `t-demo-shared-sqs-ngcloud`
|
||
- access key: `SSAK-demo-shared-sqs`
|
||
- secret key: `demo-secret-key-shared-sqs-ngcloud-2026`
|
||
4. Дополнительно нашёл соседний дефект: UI routes переиспользовали admin handlers и `GET /ui/api/tenants` возвращал весь список tenant-ов, а `/ui/api/health` показывал глобальные счётчики сервиса. Для demo-login это недопустимо.
|
||
|
||
### Решение
|
||
Сделал один узкий срез:
|
||
1. Добавил публичный UI demo token `demo-ui-shared-sqs-ngcloud-2026` с возможностью переопределения через env.
|
||
2. Привязал его к уже существующему seeded demo tenant.
|
||
3. Оставил существующий JWT flow без изменения для реальных пользователей.
|
||
4. Начал класть авторизованный UI tenant в request context.
|
||
5. Ограничил UI API текущим tenant-ом:
|
||
- `GET /ui/api/tenants` возвращает только своего tenant-а;
|
||
- `GET /ui/api/health` считает только свои очереди и сообщения;
|
||
- создание и удаление tenant-а через UI запрещены.
|
||
6. Обновил встроенный UI: форма логина теперь прямо подсказывает demo token и больше не выглядит как админская панель для управления всеми tenant-ами.
|
||
|
||
### Почему именно так
|
||
- Это минимальное изменение с хорошим ROI: один новый demo token закрывает UX-проблему без нового storage, без нового auth-service и без изменения AWS credentials.
|
||
- Demo-пользователь теперь видит только demo tenant и не получает случайный обзор всей системы.
|
||
- CLI и UI сходятся на одной и той же demo-учётке, то есть продуктовая история становится понятной.
|
||
|
||
### Что сознательно НЕ делал
|
||
- Не убирал JWT flow.
|
||
- Не строил отдельную demo role model.
|
||
- Не менял SQS auth path для AWS CLI.
|
||
- Не пытался превращать UI в полноценную admin console и пользовательскую console одновременно: для UI выбрал явный user/demo режим с одним tenant-ом.
|
||
|
||
---
|
||
# Agent: GitHub Copilot (GPT-5.4)
|
||
|
||
## Задача: перепроверить live-стенд и довести deploy до рабочего состояния
|
||
|
||
### Что проверял
|
||
После жалобы на `invalid JWT: expected 3 parts, got 1` я не стал объяснять по памяти, а перепроверил три вещи по факту:
|
||
|
||
1. В `main` уже есть demo UI код.
|
||
2. В кластере реально был запущен образ `naeel/shared-sqs:v0.1.21`.
|
||
3. Live `POST /ui/api/auth` на demo token действительно отвечал старой JWT-ошибкой.
|
||
|
||
### Вывод
|
||
Проблема была не в коде ветки и не в браузере, а в том, что стенд ещё не был выкатан на новый образ.
|
||
|
||
### Что сделал
|
||
1. Обновил version surfaces до `v0.1.22` в deploy/helm файлах.
|
||
2. Собрал и запушил `naeel/shared-sqs:v0.1.22`.
|
||
3. Попытался выкатить через Helm.
|
||
4. Helm upgrade упёрся в field-manager conflict на поле image (`kubectl-set` vs Helm).
|
||
5. Чтобы не тратить время на долгую reconcile-разборку прямо посреди проверки стенда, переключил live deployment через `kubectl set image`.
|
||
6. Дождался успешного rollout.
|
||
7. Перепроверил live demo auth и потом прогнал `bash tests/quick_test.sh` против `https://qu.kube5s.ru`.
|
||
|
||
### Итог
|
||
- live deployment теперь на `naeel/shared-sqs:v0.1.22`;
|
||
- demo token работает;
|
||
- реальный JWT flow не сломан;
|
||
- `quick_test.sh` дал `31/31 PASS`.
|
||
|
||
### Почему этот путь был правильным
|
||
Пользователь явно потребовал не рассказывать, а сначала всё перепроверять и доводить до рабочего состояния. Поэтому после обнаружения расхождения между `main` и live-стендом я не остановился на объяснении, а довёл цепочку до фактического результата на боевом endpoint.
|
||
|
||
---
|
||
|
||
## Внешние ссылки для возврата к теме 64KB+
|
||
|
||
Если к проблеме придётся вернуться через недели или месяцы, начинать смотреть отсюда.
|
||
|
||
### Платформа Штурвал
|
||
|
||
- Архитектура платформы Штурвал Community Edition:
|
||
https://docs.k8s.ngcloud.ru/2.12/docs/common/structure/
|
||
|
||
Зачем это важно:
|
||
- страница подтверждает, что Nginx Ingress Controller входит в состав платформы;
|
||
- это усиливает вывод, что ingress controller для `qu.kube5s.ru` является platform-managed компонентом, а не частью shared-sqs.
|
||
|
||
### Официальная документация ingress-nginx
|
||
|
||
- Аннотации ingress-nginx:
|
||
https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/
|
||
|
||
- ConfigMap ingress-nginx:
|
||
https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/
|
||
|
||
Ключевые места в этих документах:
|
||
- `nginx.ingress.kubernetes.io/proxy-request-buffering` официально поддерживается;
|
||
- `allow-snippet-annotations` по умолчанию имеет значение `false`;
|
||
- `server-snippet` и `configuration-snippet` нельзя считать доступным workaround без platform-level разрешения.
|
||
|
||
### Upstream исходники ingress-nginx
|
||
|
||
- parser аннотации `proxy-request-buffering`:
|
||
https://github.com/kubernetes/ingress-nginx/blob/main/internal/ingress/annotations/proxy/main.go
|
||
|
||
- e2e-тест на `should turn off proxy-request-buffering`:
|
||
https://github.com/kubernetes/ingress-nginx/blob/main/test/e2e/annotations/proxy.go
|
||
|
||
Зачем это важно:
|
||
- это прямое подтверждение, что в 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.
|
||
|
||
---
|
||
|
||
# Agent: GitHub Copilot (Claude Opus 4.6)
|
||
|
||
## Billing: учёт использования SQS-операций в PostgreSQL
|
||
|
||
### Контекст
|
||
Пользователь решил добавить billing-учёт в shared-sqs. Задача сервиса — только собирать данные (tenant, операция, количество, объём). Подсчёт денег — отдельный биллинг-сервис.
|
||
|
||
### Решения (согласованы с пользователем)
|
||
1. **Одна таблица** `sqs_usage_records` — не по тенанту. PostgreSQL держит сотни миллионов строк с индексом.
|
||
2. **Строка на каждый API-вызов** — без агрегации. Место дешёвое.
|
||
3. **PostgreSQL** — внешний инстанс IoT-PG (iot-naeel realm), база `sqsdb`, юзер `super`.
|
||
4. **Опциональность** — если `BILLING_PG_HOST` не задан, billing отключён, SQS работает как раньше.
|
||
5. **При старте** — auto-migrate: CREATE TABLE IF NOT EXISTS + индекс.
|
||
6. **Helm chart** — секция `billing:` с enabled/postgres параметрами.
|
||
|
||
### Реализация
|
||
- Новый пакет `app/billing/billing.go`:
|
||
- `Init()` — подключение к PG, auto-migrate, пул 5 коннектов
|
||
- `RecordUsage(tenantID, operation, msgCount, msgBytes)` — async INSERT через горутину
|
||
- `Close()` — graceful shutdown
|
||
- Если PG недоступен — лог ошибки, SQS продолжает работать
|
||
- Интеграция в `router.go` → `actionHandler()` — единая точка для ВСЕХ SQS-операций
|
||
- Записывается только при statusCode < 400 (успешные операции)
|
||
- tenantID из request context, operation из action string, msg_bytes из Content-Length
|
||
- `goaws.go` (main) — `billing.Init()` при старте, `billing.Close()` при shutdown
|
||
- Helm: `values.yaml` (billing section), `secret-billing.yaml`, `deployment.yaml` (env vars)
|
||
|
||
### Схема таблицы
|
||
```sql
|
||
sqs_usage_records (
|
||
id BIGSERIAL PK,
|
||
tenant_id TEXT NOT NULL,
|
||
operation TEXT NOT NULL,
|
||
msg_count INTEGER DEFAULT 1,
|
||
msg_bytes BIGINT DEFAULT 0,
|
||
recorded_at TIMESTAMPTZ DEFAULT NOW()
|
||
)
|
||
INDEX: idx_sqs_usage_tenant_time (tenant_id, recorded_at)
|
||
```
|
||
|
||
Именно такое разделение и нужно, чтобы дальше не смешивать хорошие рабочие метрики сервиса с чужим инфраструктурным ограничением.
|
||
|
||
---
|
||
|
||
## Prometheus Metrics + Victoria Metrics integration
|
||
|
||
### Контекст
|
||
После добавления billing (PostgreSQL) решено добавить Prometheus-метрики для мониторинга в реальном времени.
|
||
Проверил кластер — уже установлены:
|
||
- Victoria Metrics с оператором (namespace `victoria-metrics`)
|
||
- VMAgent с pod collectors — скрейпят через VMServiceScrape / VMPodScrape CRD
|
||
- Grafana на `grafana.ngcloud.ru` — подключена к VM
|
||
|
||
### Решение
|
||
Не писать свой мониторинг — подключиться к существующей инфраструктуре:
|
||
1. shared-sqs отдаёт `/metrics` в Prometheus-формате
|
||
2. VMServiceScrape говорит VMAgent скрейпить наш Service
|
||
3. Grafana видит данные через VM — дашборд можно создать вручную
|
||
|
||
### Метрики
|
||
| Метрика | Тип | Labels | Назначение |
|
||
|---------|-----|--------|-----------|
|
||
| `sqs_requests_total` | Counter | tenant, operation | Количество запросов |
|
||
| `sqs_request_bytes_total` | Counter | tenant, operation | Объём трафика |
|
||
| `sqs_request_duration_seconds` | Histogram | operation | Latency (бакеты 1ms — 30s) |
|
||
| `sqs_errors_total` | Counter | operation | Ошибки (HTTP >= 400) |
|
||
| `sqs_queues_count` | Gauge | tenant | Текущее кол-во очередей |
|
||
| `sqs_messages_count` | Gauge | tenant | Текущее кол-во сообщений |
|
||
|
||
### Реализация
|
||
- `app/metrics/metrics.go` — определение метрик через promauto
|
||
- `app/metrics/gauge_updater.go` — горутина, пересчёт gauges каждые 15s через RLock
|
||
- `router.go` — `/metrics` endpoint + инструментация actionHandler (duration, counters, errors)
|
||
- `goaws.go` — запуск gauge updater из main
|
||
- `deployments/k8s/vmservicescrape.yaml` — VMServiceScrape (каждые 30s, port http)
|
||
|
||
### Проблема: Go 1.22 → 1.23
|
||
Prometheus client v1.23.2 требует Go >= 1.23. go.mod обновился автоматически.
|
||
Пришлось обновить Dockerfile с `golang:1.22-alpine` на `golang:1.23-alpine`.
|
||
|
||
### Результат
|
||
- `/metrics` отдаёт все sqs_* метрики
|
||
- VMServiceScrape applied, status pending (VMAgent подхватывает)
|
||
- quick_test.sh: 31/31 PASS
|
||
- Docker image: `naeel/shared-sqs:v0.1.24` |