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
+16 -9
View File
@@ -461,8 +461,11 @@ func (h *Handler) listTenantQueues(w http.ResponseWriter, r *http.Request) {
p.q.RLockMu()
visible := 0
notVisible := 0
for _, msg := range p.q.Messages {
if msg.ReceiptHandle != "" {
for i := range p.q.Messages {
if p.q.IsDead(i) {
continue
}
if p.q.Messages[i].ReceiptHandle != "" {
notVisible++
} else {
visible++
@@ -598,11 +601,15 @@ func (h *Handler) peekQueueMessages(w http.ResponseWriter, r *http.Request) {
}
}
q.RLockMu()
result := make([]peekMessageItem, 0, len(q.Messages))
for i, msg := range q.Messages {
if i >= limit {
result := make([]peekMessageItem, 0, limit)
for i := range q.Messages {
if len(result) >= limit {
break
}
if q.IsDead(i) {
continue
}
msg := &q.Messages[i]
result = append(result, peekMessageItem{
ID: msg.Uuid,
Body: msg.MessageBody,
@@ -652,7 +659,7 @@ func (h *Handler) sendMessageToQueue(w http.ResponseWriter, r *http.Request) {
return
}
msg := gosqs.BuildSqsMessage(req.Body)
q.Messages = append(q.Messages, msg)
q.AppendMessage(msg)
// Персистим одно сообщение отдельно
persistence.SaveMessage(key, &msg)
persistence.SaveQueue(key, q)
@@ -678,7 +685,7 @@ func (h *Handler) purgeQueue(w http.ResponseWriter, r *http.Request) {
return
}
q.LockMu()
q.Messages = q.Messages[:0]
q.Reset()
// Удаляем все сообщения из Redis одной командой
persistence.PurgeMessagesPersist(key)
persistence.SaveQueue(key, q)
@@ -720,7 +727,7 @@ func (h *Handler) detailedHealth(w http.ResponseWriter, r *http.Request) {
models.SyncQueues.RUnlock()
for _, q := range uiQueues {
q.RLockMu()
msgCount += len(q.Messages)
msgCount += q.LiveCount()
q.RUnlockMu()
}
@@ -745,7 +752,7 @@ func (h *Handler) detailedHealth(w http.ResponseWriter, r *http.Request) {
msgCount := 0
for _, q := range allQueues {
q.RLockMu()
msgCount += len(q.Messages)
msgCount += q.LiveCount()
q.RUnlockMu()
}