mirror of https://github.com/go-redis/redis.git
156 lines
2.7 KiB
Go
156 lines
2.7 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"go.uber.org/zap"
|
||
|
|
||
|
"github.com/redis/go-redis/v9"
|
||
|
)
|
||
|
|
||
|
func main() {
|
||
|
ctx := context.Background()
|
||
|
|
||
|
rdb := redis.NewClient(&redis.Options{
|
||
|
Addr: ":6379",
|
||
|
})
|
||
|
|
||
|
_ = rdb.Set(ctx, "key_with_ttl", "bar", time.Minute).Err()
|
||
|
_ = rdb.Set(ctx, "key_without_ttl_1", "", 0).Err()
|
||
|
_ = rdb.Set(ctx, "key_without_ttl_2", "", 0).Err()
|
||
|
|
||
|
checker := NewKeyChecker(rdb, 100)
|
||
|
|
||
|
start := time.Now()
|
||
|
checker.Start(ctx)
|
||
|
|
||
|
iter := rdb.Scan(ctx, 0, "", 0).Iterator()
|
||
|
for iter.Next(ctx) {
|
||
|
checker.Add(iter.Val())
|
||
|
}
|
||
|
if err := iter.Err(); err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
deleted := checker.Stop()
|
||
|
fmt.Println("deleted", deleted, "keys", "in", time.Since(start))
|
||
|
}
|
||
|
|
||
|
type KeyChecker struct {
|
||
|
rdb *redis.Client
|
||
|
batchSize int
|
||
|
ch chan string
|
||
|
delCh chan string
|
||
|
wg sync.WaitGroup
|
||
|
deleted int
|
||
|
logger *zap.Logger
|
||
|
}
|
||
|
|
||
|
func NewKeyChecker(rdb *redis.Client, batchSize int) *KeyChecker {
|
||
|
return &KeyChecker{
|
||
|
rdb: rdb,
|
||
|
batchSize: batchSize,
|
||
|
ch: make(chan string, batchSize),
|
||
|
delCh: make(chan string, batchSize),
|
||
|
logger: zap.L(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *KeyChecker) Add(key string) {
|
||
|
c.ch <- key
|
||
|
}
|
||
|
|
||
|
func (c *KeyChecker) Start(ctx context.Context) {
|
||
|
c.wg.Add(1)
|
||
|
go func() {
|
||
|
defer c.wg.Done()
|
||
|
if err := c.del(ctx); err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
c.wg.Add(1)
|
||
|
go func() {
|
||
|
defer c.wg.Done()
|
||
|
defer close(c.delCh)
|
||
|
|
||
|
keys := make([]string, 0, c.batchSize)
|
||
|
|
||
|
for key := range c.ch {
|
||
|
keys = append(keys, key)
|
||
|
if len(keys) < cap(keys) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if err := c.checkKeys(ctx, keys); err != nil {
|
||
|
c.logger.Error("checkKeys failed", zap.Error(err))
|
||
|
}
|
||
|
keys = keys[:0]
|
||
|
}
|
||
|
|
||
|
if len(keys) > 0 {
|
||
|
if err := c.checkKeys(ctx, keys); err != nil {
|
||
|
c.logger.Error("checkKeys failed", zap.Error(err))
|
||
|
}
|
||
|
keys = nil
|
||
|
}
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
func (c *KeyChecker) Stop() int {
|
||
|
close(c.ch)
|
||
|
c.wg.Wait()
|
||
|
return c.deleted
|
||
|
}
|
||
|
|
||
|
func (c *KeyChecker) checkKeys(ctx context.Context, keys []string) error {
|
||
|
cmds, err := c.rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
||
|
for _, key := range keys {
|
||
|
pipe.TTL(ctx, key)
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
for i, cmd := range cmds {
|
||
|
d, err := cmd.(*redis.DurationCmd).Result()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if d == -1 {
|
||
|
c.delCh <- keys[i]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *KeyChecker) del(ctx context.Context) error {
|
||
|
pipe := c.rdb.Pipeline()
|
||
|
|
||
|
for key := range c.delCh {
|
||
|
fmt.Printf("deleting %s...\n", key)
|
||
|
pipe.Del(ctx, key)
|
||
|
c.deleted++
|
||
|
|
||
|
if pipe.Len() < c.batchSize {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if _, err := pipe.Exec(ctx); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if _, err := pipe.Exec(ctx); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|