Comment blocks in cmd/minstrel and 30 smaller internal packages, rewritten
to rule 32's standard with each citation read in context: task, milestone
and rule ids go, and so do the stories they were attached to ("used to",
"Before #N", measurements from one install, quotes, pointers to Fabled
Librarian's records and to docs/superpowers files that are not in the
repo). Where a story wrapped a real constraint, the constraint stays as a
present-tense fact.
Comments that had gone stale are corrected:
- taste.Config: the tuned values are overlaid by internal/recsettings;
it is no longer "a later follow-up".
- StorageConfig: the data dir holds art, caches and the stream secret,
not only playlist covers.
- the Subsonic album list and getUser comments describe what they do
today rather than a milestone that would change it.
Checked: the non-comment token stream of every changed file matches
HEAD, and gofmt -l is empty.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
2.6 KiB
Go
83 lines
2.6 KiB
Go
// Periodic compactor for the library_changes log. Every mutation in the
|
|
// library writes one row, so the table grows unbounded without a retention
|
|
// policy. This worker trims rows older than CompactorRetention on a daily
|
|
// tick.
|
|
//
|
|
// Why 30 days: clients keep a cursor and pull deltas from the last seen id.
|
|
// A client that checks in at least once per retention window never sees its
|
|
// cursor go stale; one offline longer than that gets the /api/library/sync
|
|
// 410 and does a full resync.
|
|
|
|
package sync
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
|
)
|
|
|
|
// CompactorRetention is the age past which library_changes rows are
|
|
// dropped. Mutable for tests; production runs should not change it
|
|
// without considering the corresponding client cursor-stale fallback.
|
|
var CompactorRetention = 30 * 24 * time.Hour
|
|
|
|
// CompactorInterval is how often the worker checks. Daily is enough —
|
|
// the table grows on the order of a few thousand rows/day for an
|
|
// active library, well below a size where intra-day trimming matters.
|
|
var CompactorInterval = 24 * time.Hour
|
|
|
|
// Compactor is a background worker that periodically deletes
|
|
// library_changes rows older than CompactorRetention.
|
|
type Compactor struct {
|
|
pool *pgxpool.Pool
|
|
logger *slog.Logger
|
|
interval time.Duration
|
|
}
|
|
|
|
// NewCompactor builds a compactor with production defaults.
|
|
func NewCompactor(pool *pgxpool.Pool, logger *slog.Logger) *Compactor {
|
|
return &Compactor{
|
|
pool: pool,
|
|
logger: logger,
|
|
interval: CompactorInterval,
|
|
}
|
|
}
|
|
|
|
// Run blocks until ctx is cancelled, ticking every c.interval.
|
|
// Fires one tick immediately on startup so a long-running process
|
|
// that hasn't been compacted in a while catches up.
|
|
func (c *Compactor) Run(ctx context.Context) {
|
|
c.tickOnce(ctx)
|
|
t := time.NewTicker(c.interval)
|
|
defer t.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-t.C:
|
|
c.tickOnce(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
// tickOnce deletes rows older than CompactorRetention and logs the
|
|
// row count. Errors are logged at WARN; we never abort the loop on
|
|
// transient DB failures.
|
|
func (c *Compactor) tickOnce(ctx context.Context) {
|
|
cutoff := pgtype.Timestamptz{Time: time.Now().Add(-CompactorRetention), Valid: true}
|
|
rows, err := dbq.New(c.pool).DeleteOldLibraryChanges(ctx, cutoff)
|
|
if err != nil {
|
|
c.logger.Warn("library_changes compactor: delete failed", "err", err)
|
|
return
|
|
}
|
|
if rows > 0 {
|
|
c.logger.Info("library_changes compactor: trimmed rows",
|
|
"count", rows, "older_than", CompactorRetention)
|
|
}
|
|
}
|