Files
SQS-service/doc/thinking/p1.1-per-queue-lock-analysis-2026-10-06.md
T
Repinoid ed17c6cbd0 P1.1 groundwork: per-queue mutex в Queue + анализ блокеров
Добавлены поле mu (не сериализуется) и LockMu/RLockMu-аксессоры в models.Queue;
исправлено копирование Queue by value (numberOfHiddenMessagesInQueue).
Глобальный лок пока используется везде — поведение не менялось.
Задокументированы блокеры полного перехода (DLQ-указатель, мутирующий
marshalQueueMeta, релинк DLQ после загрузки, атомарность перевода).
2026-10-06 18:39:26 +03:00

4.3 KiB

P1.1 (per-queue mutex) — анализ и состояние (2026-10-06)

Зачем нужен

Сейчас все операции с очередями держат ОДИН глобальный models.SyncQueues.RWMutex. Проблемы:

  1. Любая операция блокирует весь сервис. При этом часть операций делает синхронный Redis-I/O прямо под глобальным локом (таймаут до 3–5с): SaveMessageSync (send), DeleteMessagePersist (delete), DeleteMessagesPersist (retention-tick), PurgeMessagesPersist (purge), DeleteQueue. При деградации Redis весь сервис встаёт на секунды.
  2. receiveMessagesUnderLock держит глобальный лок на время O(N)-скана сообщений.

Введённый groundwork (безопасно, поведение не меняется)

  • models.Queue получил поле mu sync.RWMutex (не сериализуется) + методы LockMu/UnlockMu/RLockMu/RUnlockMu.
  • Исправлено копирование Queue by value (numberOfHiddenMessagesInQueue) — иначе go vet ругался на copylocks.
  • Глобальный лок пока используется везде (как раньше) — поведение корректно.

Блокеры полного перехода (почему это не механическая замена)

  1. DLQ — общий указатель. Queue.DeadLetterQueue *Queue. Перенос сообщения в DLQ мутирует DeadLetterQueue.Messages из-под лока ИСХОДНОЙ очереди (gosqs.go:130, change_message_visibility.go:76). При per-queue локах нужен порядок взятия (сначала исходная, потом DLQ) + гарантия отсутствия цикла.
  2. marshalQueueMeta мутирует ЧУЖУЮ очередь. persistence/redis.go:146-160 временно зануляет queue.Messages И queue.DeadLetterQueue.Messages ради сериализации. Под глобальным локом это безопасно; под per-queue — гонка с операциями над DLQ. Нужен НЕмутирующий снапшот (DTO без Messages), но нельзя копировать Queue целиком (копируется мьютекс) → нужен отдельный тип-снапшот.
  3. DLQ-указатель после загрузки из Redis не релинкуется на объект из map (загружается как вложенная копия) — предсуществующая проблема, которую P1.1 обнажит (переносы в DLQ пойдут в detached-копию).
  4. Объём — атомарный. Переводить на per-queue локи надо СРАЗУ все операции с содержимым очереди (send/receive/delete/visibility/purge/attributes/tags/ admin/gauge-tick) — частичный перевод создаёт рассинхрон (операции перестанут взаимоисключаться). Это ~20+ мест в 8 файлах.
  5. Нет покрытия для FIFO/batch/DLQ/visibility — самых сложных путей.

Вывод

P1.1 — это дизайнерская задача (порядок локов + неммутирующий снапшот + релинк DLQ), а не построчная замена. Делать её «вслепую» без покрытия FIFO/batch/DLQ рискованно: ошибка = дедлоки/потеря данных на проде. Рекомендуется отдельный проход: сначала тесты на FIFO/DLQ/batch, затем атомарный перевод.

P1.2 (индексация сообщений) — та же природа

Индексация Messages (receipt→index) меняет структуру хранения, затронутую теми же путями (receive/delete/visibility/tick/сериализация), и требует покрытия тех же путей. Рекомендуется вместе с P1.1.