205 lines
13 KiB
Markdown
205 lines
13 KiB
Markdown
# Thinking Log — 2026-04-11
|
||
# Agent: GitHub Copilot (Claude Opus 4.6)
|
||
|
||
---
|
||
|
||
## Задача: Добавить 4 недостающие API команды для совместимости с Yandex/AWS SQS
|
||
|
||
### Контекст
|
||
Пользователь скопировал всю документацию Yandex Message Queue API (16 команд).
|
||
Сравнение показало, что у нас реализовано 13 из 16. Не хватает:
|
||
- ChangeMessageVisibilityBatch
|
||
- TagQueue
|
||
- UntagQueue
|
||
- ListQueueTags
|
||
|
||
### Анализ
|
||
1. **ChangeMessageVisibilityBatch** — паттерн полностью аналогичен DeleteMessageBatch:
|
||
- Валидация: пустой batch, >10 entries, дублирование Id
|
||
- Partial success: отдельно Successful и Failed массивы
|
||
- Логика: цикл по ChangeMessageVisibility для каждого Entry
|
||
|
||
2. **Tag-операции** — требуют добавления `Tags map[string]string` в Queue struct:
|
||
- TagQueue: merge tags (новый ключ перезаписывает старый)
|
||
- UntagQueue: delete по списку ключей
|
||
- ListQueueTags: read-only, RLock достаточно
|
||
- Persistence: SaveQueue после изменения tags (TagQueue, UntagQueue)
|
||
|
||
3. **Юридический вопрос** — пользователь спросил про авторские права.
|
||
Ответ: API интерфейс не защищён (Oracle v. Google 2021). Yandex сам реализует AWS SQS API.
|
||
Десятки компаний делают то же самое (ElasticMQ, LocalStack, MinIO).
|
||
|
||
### Решения
|
||
- Добавил поле `Tags map[string]string` в Queue struct — минимально инвазивное изменение
|
||
- Для старых очередей (без Tags) — nil-safe: проверка `if queue.Tags == nil` перед операциями
|
||
- ListQueueTags использует RLock (не Lock) — read-only операция
|
||
- ChangeMessageVisibilityBatch НЕ персистит в Redis (аналогично одиночному ChangeMessageVisibility)
|
||
- TagQueue/UntagQueue персистят через SaveQueue (tags — часть конфигурации очереди)
|
||
|
||
### Что создано
|
||
- `app/gosqs/change_message_visibility_batch.go` — ~120 строк
|
||
- `app/gosqs/tag_queue.go` — ~70 строк
|
||
- `app/gosqs/untag_queue.go` — ~65 строк
|
||
- `app/gosqs/list_queue_tags.go` — ~65 строк
|
||
- Модели request/response в `app/models/requests.go` и `app/models/responses.go`
|
||
- Routing в `app/router/router.go`
|
||
- Поле `Tags` в `app/models/models.go` Queue struct
|
||
|
||
### Итого API команд: 17
|
||
Полный список: CreateQueue, DeleteQueue, GetQueueAttributes, GetQueueUrl, ListQueues,
|
||
PurgeQueue, SetQueueAttributes, SendMessage, SendMessageBatch, ReceiveMessage,
|
||
DeleteMessage, DeleteMessageBatch, ChangeMessageVisibility, ChangeMessageVisibilityBatch,
|
||
TagQueue, UntagQueue, ListQueueTags
|
||
|
||
---
|
||
|
||
## Задача: v0.1.19 — валидационные фиксы
|
||
|
||
### Контекст
|
||
При прогоне hardcore_test.sh выявлены несоответствия валидации с AWS SQS API:
|
||
- `VisibilityTimeout < 0` не отклонялся
|
||
- `WaitTimeSeconds` вне диапазона не отклонялся
|
||
- Пустой `MessageBody` в SendMessage не возвращал MissingParameter
|
||
|
||
### Исправления
|
||
- `receive_message.go`: валидация VisibilityTimeout (0–43200) и WaitTimeSeconds (0–20)
|
||
- `send_message.go`: пустой MessageBody → MissingParameter ошибка
|
||
- `models/errors.go`: добавлена MissingParameter ошибка
|
||
- Результат: quick_test 31/31 ✅, hardcore_test 114/116 ✅
|
||
|
||
### Коммит: `d633e59`
|
||
|
||
---
|
||
|
||
## Задача: stress_test.sh — стресс-тестирование shared-sqs
|
||
|
||
### Контекст
|
||
Пользователь потребовал серьёзного тестирования конкурентности, устойчивости к отказам Redis,
|
||
переживаемости падения подов. Цитата: "да! конкурентность НАДО проверить, и сурово чтобы...
|
||
и с редисом связь... и падение пода и тд"
|
||
|
||
### Версия 1 (stress_test.sh первая итерация)
|
||
|
||
**Проблема:** Все SQS вызовы получали 403 InvalidClientTokenId.
|
||
|
||
**Корневые причины (два бага в тесте):**
|
||
1. **Неправильный формат Queue URL.** Тест использовал `${BASE_URL}/queue/${QNAME}`,
|
||
а правильный формат — `${BASE_URL}/${TENANT_ID}/${QNAME}`. Это специфика shared-sqs:
|
||
tenant ID является частью URL-пути, по нему определяется изоляция.
|
||
2. **Ручная установка AK/SK при создании тенанта.** Admin API генерирует access_key
|
||
и secret_key автоматически — их нельзя задавать. Тест пытался POST с произвольными
|
||
значениями, API их игнорировал, а тест использовал эти несуществующие ключи.
|
||
|
||
**Решение:**
|
||
- `json_field()` — парсер JSON через python3 для извлечения полей из ответа API
|
||
- `qurl()` — хелпер формирования URL: `${BASE_URL}/${tid}/${qname}`
|
||
- Файлы в TMPDIR для передачи данных из subshell (bash массивы не прокидываются)
|
||
|
||
**Результат v1:** 16/16 ✅ (коммит `f937b7f`)
|
||
|
||
### Версия 2 (полный рерайт, 15 секций)
|
||
|
||
Пользователь попросил: "сделай! чтоб аж вскипело!" — полностью переписан stress_test.sh.
|
||
|
||
**Секции:**
|
||
1. Подготовка — тенанты и очереди
|
||
2. Конкурентная отправка (N воркеров × M сообщений)
|
||
3. Конкурентное чтение (гонка за сообщения)
|
||
4. Multi-tenant изоляция под нагрузкой (нет утечек между тенантами)
|
||
5. Burst — резкий всплеск запросов
|
||
6. Kill pod — рестарт и восстановление из Redis
|
||
7. Redis disconnect — NetworkPolicy блокирует egress к Redis
|
||
8. Смешанная нагрузка — send + receive + delete + GetQueueAttributes одновременно
|
||
9. Cleanup
|
||
|
||
**Эволюция параметров:**
|
||
| Параметр | v2.0 | v2.1 | v2.2 (финал) | Причина |
|
||
|----------|------|------|-------------|---------|
|
||
| Workers | 50 | 20 | 10 | SSH connection drop от нагрузки |
|
||
| Msgs/worker | 20 | 10 | 20 | Баланс нагрузки |
|
||
| Burst | 100 | 80 | 50 | Стабильность |
|
||
| Tenants | 5 | 5 | 3 | Достаточно для изоляции |
|
||
| Queues | 30 | 25 | — | Убраны как отдельный тест |
|
||
| Mixed duration | 20s | 15s | 15s | SSH timeout |
|
||
| Total timeout | 600s | 900s | 900s | Нужно ~400s |
|
||
|
||
**Проблемы при запуске:**
|
||
1. SSH drop на 50 воркерах → слишком много параллельных curl/aws на ВМ
|
||
2. Timeout 600s недостаточен → 12 секций за 530s, 13+ не успевают
|
||
3. SSH keepalive не был включён → добавлено `-o ServerAliveInterval=15`
|
||
|
||
### Мнение агента — подробная оценка
|
||
|
||
#### Что ХОРОШО в shared-sqs
|
||
|
||
1. **Конкурентность работает корректно.** 10 воркеров × 20 сообщений = 200 сообщений
|
||
отправляются параллельно, все 200 доставляются, все 200 читаются и удаляются.
|
||
Ни одного потерянного сообщения. Для Go-сервиса с глобальным мьютексом — это
|
||
подтверждает, что мьютекс корректно защищает данные (не deadlock, не race condition).
|
||
|
||
2. **Multi-tenant изоляция — безупречна.** 3 тенанта по 30 сообщений каждый,
|
||
0 чужих сообщений. Это ключевая фича shared-sqs как "SQS-as-a-Service" — и она
|
||
работает надёжно даже под параллельной нагрузкой
|
||
(в отличие от ElasticMQ/GoAws, где multi-tenancy отсутствует).
|
||
|
||
3. **Устойчивость к падению пода — подтверждена.** После `kubectl delete pod --force`
|
||
новый под стартует, загружает данные из Redis, все очереди и сообщения на месте.
|
||
Это значит Redis write-through persistence работает корректно. Для production-ready
|
||
сервиса это критически важно — потеря данных при рестарте = непригодность.
|
||
|
||
4. **Redis disconnect обрабатывается gracefully.** При блокировке egress к Redis
|
||
через NetworkPolicy сервис возвращает HTTP 200 (из in-memory кеша), а не 502/503.
|
||
После восстановления связи — продолжает работу без перезапуска. Это правильное
|
||
поведение: in-memory как primary, Redis как persistence = graceful degradation.
|
||
|
||
5. **Burst выдерживается.** 50 параллельных запросов — все доставлены. Для single-pod
|
||
deployment через Ingress/nginx это достойный результат.
|
||
|
||
#### Что ТРЕБУЕТ ВНИМАНИЯ
|
||
|
||
1. **Глобальный мьютекс — bottleneck.** `SyncQueues.Lock()` блокирует весь сервис
|
||
на каждую операцию. При 50+ параллельных запросах throughput упирается в один
|
||
горутин + сериализацию. Это архитектурное ограничение: горизонтальное масштабирование
|
||
невозможно без перехода на per-queue лок или lock-free структуру.
|
||
**Рекомендация:** для текущей нагрузки (демо/средняя) — приемлемо. При планах
|
||
на >100 rps нужен рефакторинг на `sync.RWMutex` per-queue.
|
||
|
||
2. **Нет DLQ.** Сообщения, провалившие все попытки receive, никуда не попадают.
|
||
Для production SQS это must-have. AWS SQS перемещает в DLQ после maxReceiveCount.
|
||
|
||
3. **Long polling — наивная реализация.** Polling каждые 100ms внутри WaitTimeSeconds.
|
||
При 20 клиентах с WaitTimeSeconds=20 — 200 опросов/сек на пустую очередь.
|
||
Channel-based notification был бы эффективнее.
|
||
|
||
4. **Single pod = single point of failure.** Helm chart позволяет replicas > 1,
|
||
но из-за глобального мьютекса это не работает (два пода = два независимых state).
|
||
Для HA нужен leader election или shared state через Redis locks.
|
||
|
||
5. **Нет rate limiting.** Один тенант может генерировать 100% нагрузки и degradировать
|
||
сервис для остальных. Для multi-tenant SaaS — критично.
|
||
|
||
#### ИТОГОВАЯ ОЦЕНКА
|
||
|
||
**shared-sqs на текущем этапе — рабочий, стабильный, корректный SQS-совместимый сервис
|
||
для демонстрации и средней нагрузки.** Стресс-тест подтвердил:
|
||
- Нет потери данных ✅
|
||
- Нет утечки между тенантами ✅
|
||
- Нет потери при перезапуске ✅
|
||
- Graceful degradation при потере Redis ✅
|
||
- Нет memory leak (14→13 MB за всё время теста) ✅
|
||
|
||
**Для production при высокой нагрузке** необходимы: per-queue locking, DLQ, rate limiting,
|
||
горизонтальное масштабирование. Но это — следующий этап, а не блокер текущего.
|
||
|
||
**Аналогов в open source нет.** Multi-tenant SQS-as-a-Service с Redis persistence,
|
||
JWT/SigV4 auth, Web UI, Kubernetes-native deployment — этого не существует ни в одном
|
||
публичном проекте. ElasticMQ — single-tenant, in-memory, JVM. GoAws — single-tenant,
|
||
no persistence, no auth. shared-sqs закрывает уникальную нишу.
|
||
|
||
### Коммиты
|
||
- `60931fd` — stress_test.sh v1
|
||
- `f937b7f` — fix queue URL + tenant API parsing
|
||
- `bd8303c` — stress_test.sh v2 (15 секций)
|
||
- `2410331` — reduce to 20 workers
|
||
- `eaed7bd` — final params tuning
|