119 lines
3.1 KiB
Go
119 lines
3.1 KiB
Go
package redisstate
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"galaxy/mail/internal/domain/common"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
// CleanupReport describes the work done by IndexCleaner.
|
|
type CleanupReport struct {
|
|
// ScannedIndexes stores how many secondary index keys were inspected.
|
|
ScannedIndexes int
|
|
|
|
// ScannedMembers stores how many index members were examined.
|
|
ScannedMembers int
|
|
|
|
// RemovedMembers stores how many stale members were removed.
|
|
RemovedMembers int
|
|
}
|
|
|
|
// IndexCleaner removes stale delivery references from the Mail Service
|
|
// secondary indexes after primary delivery keys expire by TTL.
|
|
type IndexCleaner struct {
|
|
client *redis.Client
|
|
keyspace Keyspace
|
|
}
|
|
|
|
// NewIndexCleaner constructs one delivery-index cleanup helper.
|
|
func NewIndexCleaner(client *redis.Client) (*IndexCleaner, error) {
|
|
if client == nil {
|
|
return nil, errors.New("new redis index cleaner: nil client")
|
|
}
|
|
|
|
return &IndexCleaner{
|
|
client: client,
|
|
keyspace: Keyspace{},
|
|
}, nil
|
|
}
|
|
|
|
// CleanDeliveryIndexes scans every `mail:idx:*` key and removes members that
|
|
// no longer have a primary delivery record.
|
|
func (cleaner *IndexCleaner) CleanDeliveryIndexes(ctx context.Context) (CleanupReport, error) {
|
|
if cleaner == nil || cleaner.client == nil {
|
|
return CleanupReport{}, errors.New("clean delivery indexes in redis: nil cleaner")
|
|
}
|
|
if ctx == nil {
|
|
return CleanupReport{}, errors.New("clean delivery indexes in redis: nil context")
|
|
}
|
|
|
|
var (
|
|
report CleanupReport
|
|
cursor uint64
|
|
)
|
|
|
|
for {
|
|
keys, nextCursor, err := cleaner.client.Scan(ctx, cursor, cleaner.keyspace.SecondaryIndexPattern(), 0).Result()
|
|
if err != nil {
|
|
return CleanupReport{}, fmt.Errorf("clean delivery indexes in redis: %w", err)
|
|
}
|
|
|
|
for _, key := range keys {
|
|
if key == cleaner.keyspace.MalformedCommandCreatedAtIndex() {
|
|
continue
|
|
}
|
|
|
|
report.ScannedIndexes++
|
|
|
|
members, err := cleaner.client.ZRange(ctx, key, 0, -1).Result()
|
|
if err != nil {
|
|
return CleanupReport{}, fmt.Errorf("clean delivery indexes in redis: read index %q: %w", key, err)
|
|
}
|
|
|
|
report.ScannedMembers += len(members)
|
|
for _, member := range members {
|
|
remove, err := cleaner.shouldRemoveMember(ctx, member)
|
|
if err != nil {
|
|
return CleanupReport{}, fmt.Errorf("clean delivery indexes in redis: inspect index %q member %q: %w", key, member, err)
|
|
}
|
|
if !remove {
|
|
continue
|
|
}
|
|
|
|
if err := cleaner.client.ZRem(ctx, key, member).Err(); err != nil {
|
|
return CleanupReport{}, fmt.Errorf("clean delivery indexes in redis: remove index %q member %q: %w", key, member, err)
|
|
}
|
|
report.RemovedMembers++
|
|
}
|
|
}
|
|
|
|
if nextCursor == 0 {
|
|
return report, nil
|
|
}
|
|
cursor = nextCursor
|
|
}
|
|
}
|
|
|
|
func (cleaner *IndexCleaner) shouldRemoveMember(ctx context.Context, member string) (bool, error) {
|
|
if strings.TrimSpace(member) == "" {
|
|
return true, nil
|
|
}
|
|
|
|
deliveryID := common.DeliveryID(member)
|
|
if err := deliveryID.Validate(); err != nil {
|
|
return true, nil
|
|
}
|
|
|
|
exists, err := cleaner.client.Exists(ctx, cleaner.keyspace.Delivery(deliveryID)).Result()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return exists == 0, nil
|
|
}
|