package stream import ( "context" "os" "strconv" "time" "go-micro.dev/v4/logger" ) var ( janitorConsumerTimeout = 24 * time.Hour // threshold for an "old" consumer janitorFrequency = 4 * time.Hour // how often do we run the janitor defaultTrimDuration = 5 * 24 * time.Hour // oldest event in stream ) func (r *redisStream) runJanitor() { // Some times it's possible that a consumer group has old consumers that have failed to be deleted. // Janitor will clean up any consumers that haven't been seen for X duration go func() { for { if err := r.cleanupConsumers(); err != nil { logger.Errorf("Error cleaning up consumers") } time.Sleep(janitorFrequency) } }() } func (r *redisStream) cleanupConsumers() error { ctx := context.Background() now := time.Now() keys, err := r.redisClient.Keys(ctx, "stream-*").Result() if err != nil { return err } for _, streamName := range keys { logger.Infof("Cleaning up stream %s", streamName) s, err := r.redisClient.XInfoStreamFull(ctx, streamName, 1).Result() if err != nil { logger.Errorf("Error getting info on groups for %s: %s", streamName, err) continue } for _, g := range s.Groups { logger.Infof("Cleaning up stream %s group %s", streamName, g.Name) for _, c := range g.Consumers { // Seen time is the last time this consumer read a message successfully. // This means if the stream is low volume you could delete currently connected consumers // This isn't a massive problem because the clients should reconnect with a new consumer if c.SeenTime.Add(janitorConsumerTimeout).After(now) { continue } logger.Infof("Cleaning up consumer %s, it is %s old", c.Name, time.Since(c.SeenTime)) if err := r.redisClient.XGroupDelConsumer(ctx, streamName, g.Name, c.Name).Err(); err != nil { logger.Errorf("Error deleting consumer %s %s %s: %s", streamName, g.Name, c.Name, err) continue } } } d := defaultTrimDuration durationStr := os.Getenv("MICRO_REDIS_TRIM_DURATION") if len(durationStr) > 0 { parsed, err := time.ParseDuration(durationStr) if err != nil { logger.Warnf("Failed to parse MICRO_REDIS_TRIM_DURATION %s", err) } else { d = parsed } } if err := r.redisClient.XTrimMinID(ctx, streamName, strconv.FormatInt(time.Now().Add(-d).Unix()*1000, 10)); err != nil { logger.Errorf("Error trimming %s", err) } } return nil }