- 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).
102 lines
3.6 KiB
Go
102 lines
3.6 KiB
Go
// app/gosqs/delete_middle_test.go
|
|
// Тест P1.2: удаление сообщения из СЕРЕДИНЫ очереди не ломает остальные —
|
|
// индексы receipt/uuid корректны (tombstone), удалённое не возвращается,
|
|
// оставшиеся находятся по своим ReceiptHandle.
|
|
package gosqs
|
|
|
|
import (
|
|
"net/http"
|
|
"testing"
|
|
|
|
"shared-sqs/app/models"
|
|
)
|
|
|
|
func TestDeleteMessageV1_MiddleKeepsOthersReceivable(t *testing.T) {
|
|
resetTestEnv(t)
|
|
tnt := testTenant("SSAK-aaa")
|
|
injectRequest(t, func(v interface{}) { v.(*models.CreateQueueRequest).QueueName = "q" })
|
|
if code, _ := CreateQueueV1(reqWithTenant("/", tnt)); code != http.StatusOK {
|
|
t.Fatal("create failed")
|
|
}
|
|
q := models.SyncQueues.Queues["SSAK-aaa:q"]
|
|
q.MaximumMessageSize = 262144
|
|
|
|
// Три сообщения.
|
|
for _, body := range []string{"m1", "m2", "m3"} {
|
|
b := body
|
|
injectRequest(t, func(v interface{}) {
|
|
r := v.(*models.SendMessageRequest)
|
|
r.QueueUrl = "http://x/tenant/q"
|
|
r.MessageBody = b
|
|
})
|
|
if code, _ := SendMessageV1(reqWithTenant("/", tnt)); code != http.StatusOK {
|
|
t.Fatal("send failed")
|
|
}
|
|
}
|
|
|
|
// Получаем все три разом.
|
|
injectRequest(t, func(v interface{}) {
|
|
r := v.(*models.ReceiveMessageRequest)
|
|
r.QueueUrl = "http://x/tenant/q"
|
|
r.MaxNumberOfMessages = 10
|
|
})
|
|
_, resp := ReceiveMessageV1(reqWithTenant("/", tnt))
|
|
rr := resp.(models.ReceiveMessageResponse)
|
|
if len(rr.Result.Messages) != 3 {
|
|
t.Fatalf("receive вернул %d сообщений, want 3", len(rr.Result.Messages))
|
|
}
|
|
|
|
// Удаляем СРЕДНЕЕ.
|
|
middle := rr.Result.Messages[1]
|
|
injectRequest(t, func(v interface{}) {
|
|
r := v.(*models.DeleteMessageRequest)
|
|
r.QueueUrl = "http://x/tenant/q"
|
|
r.ReceiptHandle = middle.ReceiptHandle
|
|
})
|
|
if code, _ := DeleteMessageV1(reqWithTenant("/", tnt)); code != http.StatusOK {
|
|
t.Fatal("delete failed")
|
|
}
|
|
if q.LiveCount() != 2 {
|
|
t.Fatalf("LiveCount = %d, want 2", q.LiveCount())
|
|
}
|
|
if _, ok := q.IndexOfUUID(middle.MessageId); ok {
|
|
t.Fatal("удалённое сообщение осталось в uuid-индексе")
|
|
}
|
|
// Оставшиеся по-прежнему in-flight и находятся по своим handle (индексы не «съехали»).
|
|
for _, m := range []*models.ResultMessage{rr.Result.Messages[0], rr.Result.Messages[2]} {
|
|
if _, ok := q.IndexOfReceipt(m.ReceiptHandle); !ok {
|
|
t.Fatalf("handle оставшегося сообщения потерян: %s", m.ReceiptHandle)
|
|
}
|
|
}
|
|
|
|
// Возвращаем видимость оставшимся и получаем их снова — удалённого быть не должно.
|
|
for _, m := range []*models.ResultMessage{rr.Result.Messages[0], rr.Result.Messages[2]} {
|
|
handle := m.ReceiptHandle
|
|
injectRequest(t, func(v interface{}) {
|
|
r := v.(*models.ChangeMessageVisibilityRequest)
|
|
r.QueueUrl = "http://x/tenant/q"
|
|
r.ReceiptHandle = handle
|
|
r.VisibilityTimeout = 0
|
|
})
|
|
if code, _ := ChangeMessageVisibilityV1(reqWithTenant("/", tnt)); code != http.StatusOK {
|
|
t.Fatal("change visibility failed")
|
|
}
|
|
}
|
|
|
|
injectRequest(t, func(v interface{}) {
|
|
r := v.(*models.ReceiveMessageRequest)
|
|
r.QueueUrl = "http://x/tenant/q"
|
|
r.MaxNumberOfMessages = 10
|
|
})
|
|
_, resp2 := ReceiveMessageV1(reqWithTenant("/", tnt))
|
|
rr2 := resp2.(models.ReceiveMessageResponse)
|
|
if len(rr2.Result.Messages) != 2 {
|
|
t.Fatalf("повторный receive вернул %d, want 2", len(rr2.Result.Messages))
|
|
}
|
|
for _, m := range rr2.Result.Messages {
|
|
if m.MessageId == middle.MessageId {
|
|
t.Fatal("удалённое сообщение вернулось в receive")
|
|
}
|
|
}
|
|
}
|