Files
spsays-bff/internal/worker/likes_worker.go
2026-06-09 16:28:40 +05:30

56 lines
1.6 KiB
Go

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))
}
}
}
}()
}