Keep the recurring SCUM database poll bounded

Each dispatched poll is a durable job with two job log streams, so an
unbounded poll history would grow the platform store by roughly two
thousand jobs a day per server. A new poll now retires older terminal jobs
of the same template together with their job log streams, leaving at most
two rows per template while nothing is in flight.
This commit is contained in:
npc0-hue
2026-09-16 18:08:18 +08:00
parent 300390dc4d
commit 6985f77963
3 changed files with 120 additions and 1 deletions
@@ -1,6 +1,7 @@
package service
import (
"errors"
"testing"
"time"
@@ -225,3 +226,61 @@ func TestSCUMQueryProjectionIgnoresUndeclaredTemplateResults(t *testing.T) {
t.Fatalf("expected no projected users for an undeclared template, users=%+v err=%v", users, err)
}
}
func TestSCUMQueryJobRetentionKeepsBoundedHistory(t *testing.T) {
svc, _, instance, clock := newSCUMQueryIngestFixture(t)
aligned := time.Date(2026, 9, 16, 8, 0, 0, 0, time.UTC)
*clock = aligned
refreshSCUMQueryRunHeartbeat(t, svc, aligned)
playersJobs := func() []domain.Job {
jobs := countSCUMQueryJobs(t, svc, instance.ID)
players := make([]domain.Job, 0, len(jobs))
for _, job := range jobs {
if job.ExecutionInput.Inputs["templateKey"] == "scum.database.players" {
players = append(players, job)
}
}
return players
}
var firstJob domain.Job
for cycle := 0; cycle < 4; cycle++ {
if cycle > 0 {
*clock = aligned.Add(time.Duration(cycle) * 61 * time.Second)
refreshSCUMQueryRunHeartbeat(t, svc, *clock)
}
if err := svc.ReconcileSCUMQueryTemplates(instance.ID); err != nil {
t.Fatalf("reconcile cycle %d: %v", cycle, err)
}
for _, job := range playersJobs() {
if isTerminalJobState(job.State) {
continue
}
if cycle == 0 {
firstJob = job
}
job.State = domain.JobStateSucceeded
job.TerminalAt = *clock
if err := svc.store.Jobs().Update(job); err != nil {
t.Fatalf("complete query job: %v", err)
}
}
}
retained := playersJobs()
if len(retained) != scumQueryJobRetention {
t.Fatalf("expected %d retained player query jobs, got %d", scumQueryJobRetention, len(retained))
}
for _, job := range retained {
if !isTerminalJobState(job.State) {
t.Fatalf("retention must not remove in-flight jobs: %+v", job)
}
}
if _, err := svc.store.Jobs().Get(firstJob.ID); !errors.Is(err, repo.ErrNotFound) {
t.Fatalf("expected the oldest query job to be retired, err=%v", err)
}
if _, err := svc.store.LogStreams().Get(jobLogStreamID(firstJob.ID, "stdout")); !errors.Is(err, repo.ErrNotFound) {
t.Fatalf("expected retired query job log streams to be removed, err=%v", err)
}
}