My short-link TTL taught me that expiry is a feature you ship, not a cron you bolt on
Description and use
Implementing TTL for short links revealed issues with clock skew, process dependency, and race conditions. The solution was to enforce TTL in the query and use the database as the source of truth, with a sweeper only reclaiming expired rows.
Problem
Implementing temporary short links with TTL required reliable expiry handling, but naive background sweeper caused clock skew, process‑dependent expiry, and race conditions.
Outcome
TTL now reliably expires links regardless of clock skew, process restarts, or race conditions; expired or non‑existent links uniformly return 410.
Workflow
- 1. Store link with optional expires_at timestamp.
- 2. On redirect request, query WHERE slug = ? AND (expires_at IS NULL OR expires_at > now()).
- 3. If row exists, serve redirect; else return 410 Gone.
- 4. Periodic sweeper runs to delete rows where expires_at < now() (garbage collection).
Evidence
First, clock skew. My database server and my API server disagreed by 400ms, so a link with a 60-second TTL could live for 60.4 seconds or die at 59.6 depending on which process touched it last.
Second, the sweeper only ran while the process was alive. Restart the bot, and expiry silently stopped.
Third, and this is the one that actually bit me: deletion wasn't atomic with lookup.
The fix wasn't a transaction — it was moving the expiry check into the query itself: WHERE slug = ? AND (expires_at IS NULL OR expires_at > now()).
- Record date: 2026-10-18T19:41:27.149Z