package worker import ( "context" "time" "github.com/prabhupadasays/spsays-bff/internal/repository" "go.uber.org/zap" ) // LikesWorker periodically folds the pending Redis like-deltas into // posts.likes_count, so the hot counter is written in batches rather than on // every like. It also does a best-effort final flush on shutdown so deltas // aren't lost. type LikesWorker struct { repo *repository.LikeRepo interval time.Duration logger *zap.Logger } func NewLikesWorker(repo *repository.LikeRepo, logger *zap.Logger) *LikesWorker { return &LikesWorker{repo: repo, interval: 15 * time.Minute, logger: logger} } // Run starts the flush loop in its own goroutine; it exits when ctx is done. func (w *LikesWorker) Run(ctx context.Context) { go func() { w.logger.Info("likes worker started", zap.Duration("interval", w.interval)) defer w.logger.Info("likes worker stopped") ticker := time.NewTicker(w.interval) defer ticker.Stop() for { select { case <-ctx.Done(): // Final flush so a graceful shutdown doesn't drop pending // deltas. Fresh, short-lived context since ctx is cancelled. flushCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) if _, err := w.repo.FlushDeltas(flushCtx); err != nil { w.logger.Warn("likes worker: final flush failed", zap.Error(err)) } cancel() return case <-ticker.C: n, err := w.repo.FlushDeltas(ctx) if err != nil { w.logger.Warn("likes worker: flush failed", zap.Error(err)) } else if n > 0 { w.logger.Info("likes worker: flushed", zap.Int("posts", n)) } } } }() }