P1.3: реализован MessageRetentionPeriod (эвикция просроченных сообщений)

processQueueTick теперь эвиктит сообщения старше retention (SentTime + retention < now):
удаляет их из очереди и из Redis (DeleteMessagesPersist), разблокируя FIFO-группы.
Retention == 0 означает отсутствие истечения. PeriodicTasks итерируется по (key, queue),
чтобы передать queueKey для удаления из Redis. Устраняет забивание очередей до OverLimit.
This commit is contained in:
Repinoid
2026-10-06 18:10:57 +03:00
parent 5642b893a8
commit db41878951
+38 -7
View File
@@ -5,6 +5,7 @@ import (
"time"
"shared-sqs/app/models"
"shared-sqs/app/persistence"
log "github.com/sirupsen/logrus"
)
@@ -30,21 +31,25 @@ func PeriodicTasks(d time.Duration, quit chan bool) {
for {
select {
case <-ticker.C:
// Снапшот указателей на очереди под коротким RLock.
// Снапшот (key, указатель) очередей под коротким RLock.
// Указатели остаются валидными после снятия RLock: очереди из map
// не удаляются во время итерации, а объекты живут в куче.
models.SyncQueues.RLock()
queues := make([]*models.Queue, 0, len(models.SyncQueues.Queues))
for _, queue := range models.SyncQueues.Queues {
queues = append(queues, queue)
type qEntry struct {
key string
queue *models.Queue
}
queues := make([]qEntry, 0, len(models.SyncQueues.Queues))
for key, queue := range models.SyncQueues.Queues {
queues = append(queues, qEntry{key, queue})
}
models.SyncQueues.RUnlock()
// Каждую очередь обрабатываем под ОТДЕЛЬНЫМ Lock — блокировка одной
// очереди не мешает обработке остальных и HTTP-запросам.
for _, queue := range queues {
for _, qi := range queues {
models.SyncQueues.Lock()
processQueueTick(queue)
processQueueTick(qi.key, qi.queue)
models.SyncQueues.Unlock()
}
case <-quit:
@@ -59,6 +64,7 @@ func PeriodicTasks(d time.Duration, quit chan bool) {
// ВЫЗЫВАТЬ ТОЛЬКО под удержанием SyncQueues.Lock.
//
// Логическая схема:
// 0. Эвикция сообщений по MessageRetentionPeriod (старше retention удаляются).
// 1. Deduplication-окно (FIFO): удаляем записи старше DeduplicationPeriod.
// 2. Проходим по сообщениям очереди:
// - пустой ReceiptHandle (не in-flight) — пропускаем;
@@ -67,7 +73,32 @@ func PeriodicTasks(d time.Duration, quit chan bool) {
// FIFO-группы, обновление ReceiptTime), инкрементируем Retry;
// - если Retry >= MaxReceiveCount и настроена DLQ — переносим сообщение
// в DLQ и удаляем из исходной очереди.
func processQueueTick(queue *models.Queue) {
func processQueueTick(key string, queue *models.Queue) {
// 0. Эвикция просроченных сообщений по MessageRetentionPeriod.
// Retention == 0 означает «без истечения» (не эвиктируем).
if queue.MessageRetentionPeriod > 0 {
cutoff := time.Now().Add(-time.Duration(queue.MessageRetentionPeriod) * time.Second)
expired := make([]string, 0) // uuid просроченных сообщений для удаления из Redis
kept := queue.Messages[:0]
for i := range queue.Messages {
msg := &queue.Messages[i]
if msg.SentTime.Before(cutoff) {
expired = append(expired, msg.Uuid)
if msg.GroupID != "" {
queue.UnlockGroup(msg.GroupID)
}
continue
}
kept = append(kept, *msg)
}
if len(expired) > 0 {
queue.Messages = kept
log.Infof("Queue [%s]: evicted %d messages by MessageRetentionPeriod", queue.Name, len(expired))
// Удаляем просроченные из Redis, чтобы не воскресли после рестарта.
persistence.DeleteMessagesPersist(key, expired)
}
}
// 1. Просроченные записи deduplication-окна.
for dedupId, startTime := range queue.Duplicates {
if time.Now().After(startTime.Add(models.DeduplicationPeriod)) {