- outbox_worker.retention 配置块(默认关闭,max_age<24h 拒绝启动) - 清理走预留的 idx_room_outbox_retention 索引,先选主键再小批量 DELETE - 移除与 idx_room_outbox_pending 完全重复的 idx_room_outbox_retry, auto_migrate=true 时 Migrate 自动 DROP - 开启清理前需和数据侧确认保留窗口覆盖统计重放/补数工具 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
87 lines
3.0 KiB
Go
87 lines
3.0 KiB
Go
package service_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
|
|
"hyapp/pkg/appcode"
|
|
"hyapp/services/room-service/internal/integration"
|
|
"hyapp/services/room-service/internal/room/outbox"
|
|
roomservice "hyapp/services/room-service/internal/room/service"
|
|
"hyapp/services/room-service/internal/router"
|
|
"hyapp/services/room-service/internal/testutil/mysqltest"
|
|
)
|
|
|
|
// 一轮清理必须按批删空所有过期 delivered 事件,同时保留窗口内 delivered 和未完成投递的事件。
|
|
func TestProcessOutboxRetentionDrainsExpiredDelivered(t *testing.T) {
|
|
ctx := context.Background()
|
|
repository := mysqltest.NewRepository(t)
|
|
svc := roomservice.New(roomservice.Config{
|
|
NodeID: "node-outbox-retention-test",
|
|
}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
|
|
|
|
roomID := "room-outbox-retention"
|
|
newRecord := func(eventID string) outbox.Record {
|
|
return outbox.Record{
|
|
AppCode: appcode.Default,
|
|
EventID: eventID,
|
|
EventType: "RoomGiftSent",
|
|
RoomID: roomID,
|
|
Status: outbox.StatusPending,
|
|
Envelope: &roomeventsv1.EventEnvelope{
|
|
AppCode: appcode.Default,
|
|
EventId: eventID,
|
|
RoomId: roomID,
|
|
EventType: "RoomGiftSent",
|
|
},
|
|
CreatedAtMS: 1000,
|
|
}
|
|
}
|
|
|
|
expiredMS := time.Now().UTC().Add(-48 * time.Hour).UnixMilli()
|
|
records := make([]outbox.Record, 0, 7)
|
|
expired := make([]string, 0, 5)
|
|
for index := 0; index < 5; index++ {
|
|
eventID := fmt.Sprintf("evt-retention-expired-%d", index)
|
|
expired = append(expired, eventID)
|
|
records = append(records, newRecord(eventID))
|
|
}
|
|
records = append(records, newRecord("evt-retention-fresh"), newRecord("evt-retention-pending"))
|
|
if err := repository.SaveOutbox(ctx, records); err != nil {
|
|
t.Fatalf("save outbox failed: %v", err)
|
|
}
|
|
for _, eventID := range expired {
|
|
repository.SetOutboxDeliveredAt(eventID, expiredMS)
|
|
}
|
|
if err := repository.MarkOutboxDelivered(ctx, "evt-retention-fresh"); err != nil {
|
|
t.Fatalf("mark fresh delivered failed: %v", err)
|
|
}
|
|
|
|
// BatchSize=2 强制一轮内分 3 批删完 5 条过期事件,覆盖“整批删满继续、删空退出”的循环边界。
|
|
total, err := svc.ProcessOutboxRetention(ctx, roomservice.OutboxRetentionOptions{
|
|
MaxAge: 24 * time.Hour,
|
|
BatchSize: 2,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("process outbox retention failed: %v", err)
|
|
}
|
|
if total != int64(len(expired)) {
|
|
t.Fatalf("retention deleted %d rows, want %d", total, len(expired))
|
|
}
|
|
|
|
for _, eventID := range expired {
|
|
if _, ok := repository.OutboxRecord(eventID); ok {
|
|
t.Fatalf("expired delivered event %s must be purged", eventID)
|
|
}
|
|
}
|
|
if record, ok := repository.OutboxRecord("evt-retention-fresh"); !ok || record.Status != outbox.StatusDelivered {
|
|
t.Fatalf("fresh delivered event must survive: ok=%v record=%+v", ok, record)
|
|
}
|
|
if record, ok := repository.OutboxRecord("evt-retention-pending"); !ok || record.Status != outbox.StatusPending {
|
|
t.Fatalf("pending event must survive: ok=%v record=%+v", ok, record)
|
|
}
|
|
}
|