You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Right now all entity data refreshes lazily on lookup (TTL-gated) or via manually-run scripts/. Nothing refreshes on a schedule. That's fine for seeding the DB, but it doesn't scale: leaderboard rank, follower counts and the commit tail drift for anyone nobody visits; org contribution totals never refresh on the request path at all; and we keep only the latest value for everything, so month-over-month trends are impossible for orgs and lossy for historical profile scalars.
This epic is the umbrella for getting the refresh / backfill / history platform right — how we schedule and prioritize background refresh, what history we snapshot, and where the worker runs — so we stop hand-spinning scripts. Scripts stay great for one-off seeding; recurring work should be automatic.
Current state (so we decide from reality, not memory)
Users — src/lib/cache.ts: TAIL_TTL = 60s; all six contribution types stored per completed month in monthly_commits; the in-progress month is excluded (monthlyWindows uses a strict <). Refresh happens only on lookup.
Orgs — src/lib/org-cache.ts: ORG_TTL = 7 days; MAX_ORG_MEMBERS = 25 (bigger orgs are recorded and filled by scripts/backfill-orgs.ts). Org numbers are lifetime sums on entities, rolled up from org_members — no monthly / time-series storage on main. On the request path, only profile metadata refreshes when stale; contribution totals never do — they wait for a script.
In-progress month is dropped for both users and orgs (yearlyWindows/monthlyWindows stop at the first of the current month). So every headline number is "last completed month", not "today". (Worth ratifying — see Show the current in-progress month as a dashed 'trend' segment #35.)
Scripts (all manual, bun scripts/…): refresh.ts (user profile metadata), backfill-contributions.ts (user full rebuild, rate-limited), backfill-orgs.ts (org lifetime totals, rate-limited), suspend.ts (moderation). No cron / scheduled mechanism exists anywhere. Deploy is TanStack Start → Nitro node-server, Dockerized, on Coolify.
Goals
Proactive, politely rate-limited refresh on a cadence, prioritized so what people actually see stays fresh.
Snapshot enough history to power trend indicators (e.g. "+50% vs last month") for both users and orgs (they're two different data shapes).
A durable place for the worker to run — not a laptop running bun scripts/….
Key decisions (need a human call)
Cadence — daily vs weekly vs monthly, per tier. Contributions are monthly-resolution, so sub-weekly refresh buys little for the curve; profile/follower/rank drift faster. Starting proposal: hot tier (recent lookups + top-250) weekly; long-tail monthly; profile-metadata (followers/rank) possibly more frequent. → Refresh cadence & prioritization policy (background updates) #107.
Myth-buster: Nitro does fire scheduledTasks on the node-server preset — it uses an in-process croner engine, no external trigger needed. (The "doesn't fire on Node" belief comes from the Vercel/Cloudflare presets, which don't apply here.)
But: a long, rate-limited job in the web process risks memory contention, and — the sharp edge — gets killed with no resume on every web redeploy (Coolify replaces the container; croner does no catch-up).
Coolify has built-in Scheduled Tasks (container-level cron). A dedicated worker service (own container, own resource limits, own deploy) triggered by Coolify survives web deploys and isolates the load.
Leaning: dedicated Coolify worker for the heavy refresh; keep the job code as a shared script/package in this repo so either runner can call it. Nitro tasks stay viable for light/idempotent jobs. Whatever we pick, jobs must be idempotent and checkpointed (our scripts already are). Full write-up in Worker runtime: Nitro scheduled task vs dedicated Coolify worker (decide + spike) #108.
The per-type / daily fetching mechanics (#39 closed, #40/#42) — this epic is about scheduling, prioritization, history, and where it runs, not how a single fetch is shaped.
Right now all entity data refreshes lazily on lookup (TTL-gated) or via manually-run
scripts/. Nothing refreshes on a schedule. That's fine for seeding the DB, but it doesn't scale: leaderboard rank, follower counts and the commit tail drift for anyone nobody visits; org contribution totals never refresh on the request path at all; and we keep only the latest value for everything, so month-over-month trends are impossible for orgs and lossy for historical profile scalars.This epic is the umbrella for getting the refresh / backfill / history platform right — how we schedule and prioritize background refresh, what history we snapshot, and where the worker runs — so we stop hand-spinning scripts. Scripts stay great for one-off seeding; recurring work should be automatic.
Current state (so we decide from reality, not memory)
src/lib/cache.ts:TAIL_TTL = 60s; all six contribution types stored per completed month inmonthly_commits; the in-progress month is excluded (monthlyWindowsuses a strict<). Refresh happens only on lookup.src/lib/org-cache.ts:ORG_TTL = 7 days;MAX_ORG_MEMBERS = 25(bigger orgs are recorded and filled byscripts/backfill-orgs.ts). Org numbers are lifetime sums onentities, rolled up fromorg_members— no monthly / time-series storage onmain. On the request path, only profile metadata refreshes when stale; contribution totals never do — they wait for a script.yearlyWindows/monthlyWindowsstop at the first of the current month). So every headline number is "last completed month", not "today". (Worth ratifying — see Show the current in-progress month as a dashed 'trend' segment #35.)bun scripts/…):refresh.ts(user profile metadata),backfill-contributions.ts(user full rebuild, rate-limited),backfill-orgs.ts(org lifetime totals, rate-limited),suspend.ts(moderation). No cron / scheduled mechanism exists anywhere. Deploy is TanStack Start → Nitro node-server, Dockerized, on Coolify.Goals
bun scripts/….Key decisions (need a human call)
Deployment research (feeds decision #5 / #108)
scheduledTaskson thenode-serverpreset — it uses an in-process croner engine, no external trigger needed. (The "doesn't fire on Node" belief comes from the Vercel/Cloudflare presets, which don't apply here.)Sub-issues
New (this epic):
Existing (fold in / organize):
Related infra: #75 (healthcheck / Coolify).
Non-goals
The per-type / daily fetching mechanics (#39 closed, #40/#42) — this epic is about scheduling, prioritization, history, and where it runs, not how a single fetch is shaped.