The cron piggyback: how we got 4 leaderboards out of 1 Vercel function
The Vercel Hobby plan caps you at 12 serverless functions and 1 cron job. We needed history features for a producer leaderboard, a mover board, a beat-of-the-week, and (counting) — without burning a single new function slot.
The problem
Every history feature on StudioMode has the same shape: once a day, freeze the current state of some live aggregate and write it to an append-only table. Today's leaderboard becomes yesterday's leaderboard, which becomes the data behind a "trends over time" feature months later.
The naïve plan looks like this:
- Cron 1: snapshot the producer leaderboard
- Cron 2: snapshot the beat-of-the-week winner
- Cron 3: snapshot follower counts
- Cron 4: …
Three problems. Vercel Hobby allows exactly one cron. Each cron is a function (so we'd burn 3-4 of our 12 slots). And the wall clock between them isn't synchronized — if cron 1 runs at 14:00 and cron 2 at 14:05, the snapshots disagree about what "today" is across timezones.
The piggyback
We already have a daily-digest cron — it runs once at 14:00 UTC and pushes notifications to users about new beats from producers they follow. That function is the only cron we use. Snapshot RPCs are short, idempotent, and run inline at the top of that handler:
// /api/notifications/daily-digest.ts
export default async function handler(req, res) {
// 0a. Snapshot today's producer leaderboard.
await supabaseAdmin.rpc('snapshot_producer_rank_history');
// 0b. Same idempotent piggyback for beat-of-the-week.
await supabaseAdmin.rpc('snapshot_beat_of_the_week');
// 1. The actual digest work…
}
Two snapshot calls. Maybe 50ms total. The digest does its real job afterwards. Same cron run, same wall clock, same function slot.
The idempotence that makes it work
The thing that makes this safe is that the snapshot RPCs are
idempotent on (snapshot_date). Today's row gets
inserted once and any second call same-day is a no-op:
insert into beat_of_the_week_history (snapshot_date, ...)
select current_date, ...
on conflict (snapshot_date) do nothing;
That tiny clause is doing serious work. It means:
- Manual reruns are safe. If the digest cron crashes mid-flight and we re-trigger it, history doesn't double-write.
- Backfill is trivial. Want to backfill a missing day? Set
snapshot_dateto the past date and call the RPC. It either writes or does nothing. - Order doesn't matter. If we add a 5th snapshot tomorrow, ordering between the others doesn't change correctness.
Idempotence turns piggybacking from a hack into a contract.
Why not just use a database cron?
Supabase has pg_cron, which would actually work
here. We considered it. Two reasons we didn't:
-
Observability. Vercel's cron logs surface in
the same dashboard as our other functions.
pg_cronruns invisibly inside the database; failures don't page anyone and there's no obvious "did it run today?" panel. - Simplicity over correctness margin. The digest cron is already running, already monitored, already the source of truth for "we did our daily work." Hooking snapshots into it gives us one thing to watch instead of two.
The pattern, generalized
If you're on Vercel Hobby (or any platform with a function quota), look for these:
- You already have a cron that runs roughly when you'd want the snapshot to run.
- The snapshot work is short — milliseconds, not seconds. (If it takes minutes, run it as its own cron and pay the function cost, but it probably needs background-job infra anyway.)
- The snapshot is idempotent on a key like
(date)or(date, entity_id). - It's okay if the snapshot occasionally misses (cron flake, network blip). History pages tolerate ±a-day-or-two via fuzzy matching at read time.
Tick all four boxes and you can keep adding history features forever inside one function. We're at four; the next one probably won't need its own cron either.
What I'd do differently
Add a tiny health-check column to each snapshot table —
created_at timestamptz default now(). Lets us write
a one-line "did we snapshot today?" dashboard query without
relying on Vercel's log retention. It's the smallest amount of
future-proofing that I keep wishing I'd done from day one.
Find beats. Build a vault.
StudioMode is a discovery layer that surfaces beats artists actually use. Free for now.
Try StudioMode →