7.0 KiB
P1.1 (per-queue mutex) — анализ и состояние (2026-10-06)
Зачем нужен
Сейчас все операции с очередями держат ОДИН глобальный models.SyncQueues.RWMutex.
Проблемы:
- Любая операция блокирует весь сервис. При этом часть операций делает
синхронный Redis-I/O прямо под глобальным локом (таймаут до 3–5с):
SaveMessageSync(send),DeleteMessagePersist(delete),DeleteMessagesPersist(retention-tick),PurgeMessagesPersist(purge),DeleteQueue. При деградации Redis весь сервис встаёт на секунды. receiveMessagesUnderLockдержит глобальный лок на время O(N)-скана сообщений.
Введённый groundwork (безопасно, поведение не меняется)
models.Queueполучил полеmu sync.RWMutex(не сериализуется) + методыLockMu/UnlockMu/RLockMu/RUnlockMu.- Исправлено копирование
Queueby value (numberOfHiddenMessagesInQueue) — иначеgo vetругался на copylocks. - Глобальный лок пока используется везде (как раньше) — поведение корректно.
Блокеры полного перехода (почему это не механическая замена)
- DLQ — общий указатель.
Queue.DeadLetterQueue *Queue. Перенос сообщения в DLQ мутируетDeadLetterQueue.Messagesиз-под лока ИСХОДНОЙ очереди (gosqs.go:130,change_message_visibility.go:76). При per-queue локах нужен порядок взятия (сначала исходная, потом DLQ) + гарантия отсутствия цикла. marshalQueueMetaмутирует ЧУЖУЮ очередь.persistence/redis.go:146-160временно зануляетqueue.MessagesИqueue.DeadLetterQueue.Messagesради сериализации. Под глобальным локом это безопасно; под per-queue — гонка с операциями над DLQ. Нужен НЕмутирующий снапшот (DTO безMessages), но нельзя копироватьQueueцеликом (копируется мьютекс) → нужен отдельный тип-снапшот.- DLQ-указатель после загрузки из Redis не релинкуется на объект из map (загружается как вложенная копия) — предсуществующая проблема, которую P1.1 обнажит (переносы в DLQ пойдут в detached-копию).
- Объём — атомарный. Переводить на per-queue локи надо СРАЗУ все операции с содержимым очереди (send/receive/delete/visibility/purge/attributes/tags/ admin/gauge-tick) — частичный перевод создаёт рассинхрон (операции перестанут взаимоисключаться). Это ~20+ мест в 8 файлах.
- Нет покрытия для FIFO/batch/DLQ/visibility — самых сложных путей.
Вывод
P1.1 — это дизайнерская задача (порядок локов + неммутирующий снапшот + релинк DLQ), а не построчная замена. Делать её «вслепую» без покрытия FIFO/batch/DLQ рискованно: ошибка = дедлоки/потеря данных на проде. Рекомендуется отдельный проход: сначала тесты на FIFO/DLQ/batch, затем атомарный перевод.
СТАТУС: РЕАЛИЗОВАНО (2026-10-06)
Перевод выполнен атомарно. Что сделано:
marshalQueueMetaбольше НЕ мутирует очередь (DTO-снапшот queueMetaDTO).- Все операции с содержимым очереди используют per-queue лок
(
QueueStore.GetLocked/GetRLocked+queue.LockMu/RUnlockMu) в gosqs, admin, metrics/gauge_updater, PeriodicTasks. - Глобальный
SyncQueues-лок остался ТОЛЬКО для доступа к map и неизменяемых полей (create/delete/list/find/snapshot). - DLQ-переносы берут лок DLQ отдельно (порядок main→DLQ).
- Устранён дедлок в create_queue (setQueueAttributesV1 вызывается ВНЕ глобального лока).
- Добавлены конкурентные тесты (два тенанта / одна очередь), прогон
go test -race— зелёный.
P1.2 (индексация сообщений) — та же природа
Индексация Messages (receipt→index) меняет структуру хранения, затронутую теми
же путями (receive/delete/visibility/tick/сериализация), и требует покрытия тех
же путей. Рекомендуется вместе с P1.1.
P1.2 — СТАТУС: ОТЛОЖЕНО (обоснование, 2026-10-06)
После P1.1 главный боттлнек (глобальный лок на синхронный Redis-I/O и O(N)-скан
по ВСЕМ очередям) устранён: теперь O(N)-сканы ограничены ОДНОЙ очередью и не
блокируют остальные. Индекс receipt→index поверх срезa []SqsMessage НЕ даёт
O(1): удаление из середины среза сдвигает все последующие индексы (O(N) на
переиндексацию). Реальный выигрыш требует смены контейнера (map+order-list), что
затрагивает сериализацию и все пути — крупная рискованная правка с маргинальной
выгодой на текущих объёмах. Отложено до появления реальной нагрузки/лимитов.
P1.2 — СДЕЛАНО (2026-10-07)
Возврат к пункту по прямому указанию. Решение — tombstone + receipt/uuid-индексы
- амортизированная компакция (удаление НЕ сдвигает срез → индексы стабильны,
O(1)-удаление без смены контейнера и без изменения Redis-схемы).
Дизайн и результаты:
p1.2-message-index-design-2026-10-07.md. По ходу найден и исправлен дефект утечки per-queue блокировки в receive (возврат доdefer UnlockMu) — см. там же, раздел 6.