P1.2: индексация сообщений — tombstone-удаление O(1), receipt/uuid-индексы, tick по in-flight

- models.Queue: deadIdx/receiptIdx/uuidIdx/liveCount/deadCount + примитивы
  (AppendMessage/RemoveIndex/Compact/CompactIfNeeded/Reset/EnsureIndexes/
  IndexOfReceipt/IndexOfUUID/MarkInFlight/ClearInFlight/InFlightIndexes/LiveCount).
  Удаление — tombstone за O(1), физическая чистка амортизированная.
- DeleteMessage/ChangeMessageVisibility(±Batch): поиск по индексу вместо скана
  среза; удаление без сдвига остатка.
- DeleteMessageBatch: tombstone вместо полной пересборки среза.
- processQueueTick: visibility/DLQ по индексу in-flight (O(in-flight)),
  retention — tombstone, в конце — CompactIfNeeded.
- send-пути: AppendMessage + LiveCount (OOM-лимит по живым).
- admin/metrics/persistence: учёт живых сообщений, индексы после загрузки из Redis.
- Redis-схема не менялась (сообщения и так в per-message HASH).
This commit is contained in:
Repinoid
2026-10-07 08:01:49 +03:00
parent e735563a04
commit c5b2c11f5d
15 changed files with 519 additions and 130 deletions
+10 -1
View File
@@ -154,7 +154,7 @@ func ReceiveMessageV1(req *http.Request) (int, interfaces.AbstractResponseBody)
// - для FIFO-очереди блокируется группа (LockGroup) до DeleteMessage/таймаута.
func receiveMessagesUnderLock(key string, visibilityTimeout, maxNumberOfMessages int) ([]*models.ResultMessage, bool) {
queue := models.SyncQueues.GetLocked(key)
if queue == nil || len(queue.Messages) == 0 {
if queue == nil || queue.LiveCount() == 0 {
return nil, false
}
defer queue.UnlockMu()
@@ -169,6 +169,9 @@ func receiveMessagesUnderLock(key string, visibilityTimeout, maxNumberOfMessages
if numMsg >= maxNumberOfMessages {
break
}
if queue.IsDead(i) {
continue // tombstone: сообщение логически удалено (P1.2)
}
msg := &queue.Messages[i]
if msg.ReceiptHandle != "" {
continue // сообщение уже в полёте у другого получателя
@@ -192,6 +195,9 @@ func receiveMessagesUnderLock(key string, visibilityTimeout, maxNumberOfMessages
msg.VisibilityTimeout = time.Now().Add(time.Duration(queue.VisibilityTimeout) * time.Second)
}
msg.NumberOfReceives++
// Регистрируем handle→index: по нему DeleteMessage/ChangeMessageVisibility
// работают за O(1), а visibility-тик итерирует только in-flight (P1.2).
queue.MarkInFlight(i, msg.ReceiptHandle)
messages = append(messages, buildResultMessage(msg))
numMsg++
@@ -246,6 +252,9 @@ func buildResultMessage(m *models.SqsMessage) *models.ResultMessage {
// читает общее состояние очереди.
func queueHasReceivableMessages(queue *models.Queue) bool {
for i := range queue.Messages {
if queue.IsDead(i) {
continue // tombstone (P1.2)
}
msg := &queue.Messages[i]
if msg.ReceiptHandle != "" {
continue