Files
SQS-service/app/gosqs/delete_message_batch.go
T
Repinoid c5b2c11f5d 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).
2026-10-07 08:01:49 +03:00

134 lines
3.9 KiB
Go

// Изменено: 2026-04-09
// DeleteMessageBatchV1 — пакетное удаление сообщений из очереди тенанта.
package gosqs
import (
"net/http"
"strings"
"shared-sqs/app/interfaces"
"shared-sqs/app/models"
"shared-sqs/app/persistence"
"shared-sqs/app/utils"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
)
func DeleteMessageBatchV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
requestBody := models.NewDeleteMessageBatchRequest()
ok := utils.REQUEST_TRANSFORMER(requestBody, req, false)
if !ok {
log.Error("Invalid Request - DeleteMessageBatchV1")
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
}
t := getTenantFromContext(req)
if t == nil {
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
}
queueUrl := requestBody.QueueUrl
queueName := ""
if queueUrl == "" {
vars := mux.Vars(req)
queueName = vars["queueName"]
} else {
uriSegments := strings.Split(queueUrl, "/")
queueName = uriSegments[len(uriSegments)-1]
}
key := tenantQueueKey(t.AccessKey, queueName)
if len(requestBody.Entries) == 0 {
return utils.CreateErrorResponseV1("EmptyBatchRequest", true)
}
if len(requestBody.Entries) > 10 {
return utils.CreateErrorResponseV1("TooManyEntriesInBatchRequest", true)
}
ids := map[string]bool{}
for _, v := range requestBody.Entries {
if _, found := ids[v.Id]; found {
return utils.CreateErrorResponseV1("BatchEntryIdsNotDistinct", true)
}
ids[v.Id] = true
}
queue := models.SyncQueues.GetLocked(key)
if queue == nil {
return utils.CreateErrorResponseV1("QueueNotFound", true)
}
defer queue.UnlockMu()
deleteMessageMap := make(map[string]*deleteEntry)
for _, entry := range requestBody.Entries {
deleteMessageMap[entry.ReceiptHandle] = &deleteEntry{
Id: entry.Id,
ReceiptHandle: entry.ReceiptHandle,
Deleted: false,
}
}
deletedEntries := make([]models.DeleteMessageBatchResultEntry, 0)
deletedUuids := make([]string, 0)
// Идём по индексам, не пересобирая срез (P1.2): удаление = tombstone за O(1),
// индексы не сдвигаются, поэтому итерация остаётся корректной.
for i := range queue.Messages {
if queue.IsDead(i) {
continue
}
message := &queue.Messages[i]
de, found := deleteMessageMap[message.ReceiptHandle]
if !found {
continue
}
log.Debugf("FIFO Queue %s unlocking group %s:", queueName, message.GroupID)
// Читаем поля ДО RemoveIndex: компакция может перезаписать слот.
groupID, dedupID, uuid := message.GroupID, message.DeduplicationID, message.Uuid
queue.ClearInFlight(i, message.ReceiptHandle)
queue.UnlockGroup(groupID)
delete(queue.Duplicates, dedupID)
de.Deleted = true
deletedEntries = append(deletedEntries, models.DeleteMessageBatchResultEntry{Id: de.Id})
deletedUuids = append(deletedUuids, uuid)
queue.RemoveIndex(i)
}
// Удаляем сообщения из Redis отдельно — O(batch_size)
persistence.DeleteMessagesPersist(key, deletedUuids)
// Метаданные очереди (duplicates, FIFO state)
persistence.SaveQueue(key, queue)
notFoundEntries := make([]models.BatchResultErrorEntry, 0)
for _, de := range deleteMessageMap {
if !de.Deleted {
notFoundEntries = append(notFoundEntries, models.BatchResultErrorEntry{
Code: "1",
Id: de.Id,
Message: "Message not found",
SenderFault: true,
})
}
}
respStruct := models.DeleteMessageBatchResponse{
Xmlns: models.BaseXmlns,
Result: models.DeleteMessageBatchResult{
Successful: deletedEntries,
Failed: notFoundEntries,
},
Metadata: models.BaseResponseMetadata,
}
return http.StatusOK, respStruct
}
type deleteEntry struct {
Id string
ReceiptHandle string
Error string
Deleted bool
}