Files
SQS-service/app/gosqs/delete_message_batch.go
T

151 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Изменено: 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
// Пустой ReceiptHandle опасен: в JSON-протоколе (x-amz-json-1.0) он не
// отсекается парсером и совпадает с ReceiptHandle НЕвыданных сообщений (""),
// что удаляло бы все такие сообщения очереди одним запросом.
if v.ReceiptHandle == "" {
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
}
}
queue := models.SyncQueues.GetLocked(key)
if queue == nil {
return utils.CreateErrorResponseV1("QueueNotFound", true)
}
defer queue.UnlockMu()
// Ключ — Id, а НЕ ReceiptHandle: два entry с одинаковым ReceiptHandle и разными
// Id не должны перезаписывать друг друга (первый исчезал бы и не попадал ни в
// Successful, ни в Failed — ревью 2026-10-11). entriesByHandle — обратный индекс.
entriesByID := make(map[string]*deleteEntry)
entriesByHandle := make(map[string][]*deleteEntry)
for _, entry := range requestBody.Entries {
de := &deleteEntry{
Id: entry.Id,
ReceiptHandle: entry.ReceiptHandle,
Deleted: false,
}
entriesByID[entry.Id] = de
entriesByHandle[entry.ReceiptHandle] = append(entriesByHandle[entry.ReceiptHandle], de)
}
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]
des, found := entriesByHandle[message.ReceiptHandle]
if !found {
continue
}
log.Debugf("FIFO Queue %s unlocking group %s:", queueName, message.GroupID)
// Читаем поля ДО RemoveIndex: компакция может перезаписать слот.
groupID, uuid := message.GroupID, message.Uuid
queue.ClearInFlight(i, message.ReceiptHandle)
queue.UnlockGroup(groupID)
// Duplicates НЕ чистим — dedup-окно 5 минут живёт независимо от удаления (AWS).
for _, de := range des {
de.Deleted = true
deletedEntries = append(deletedEntries, models.DeleteMessageBatchResultEntry{Id: de.Id})
}
deletedUuids = append(deletedUuids, uuid)
queue.RemoveIndex(i)
}
// Удаляем сообщения из Redis отдельно — O(batch_size). Ошибка → InternalError.
if err := persistence.DeleteMessagesPersist(key, deletedUuids); err != nil {
log.Errorf("DeleteMessageBatch: не удалось удалить сообщения из Redis: %v", err)
return utils.CreateErrorResponseV1("InternalError", true)
}
// Метаданные очереди (duplicates, FIFO state)
persistence.SaveQueue(key, queue)
notFoundEntries := make([]models.BatchResultErrorEntry, 0)
for _, de := range entriesByID {
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
}