Shrink
Self-serve URL shortener with click analytics, built solo end to end.
Problem & constraints
A URL shortener is a deceptively simple idea with genuinely interesting engineering decisions underneath — how do you make redirects fast at scale? Where does your relational data end and your cache begin? How do you keep click analytics correct without blocking the redirect path?
Built solo, end to end, with a focus on exploring caching strategy alongside a relational store — not on hitting a specific feature count.
Key decisions
Redis cache in front of PostgreSQL for redirect lookups
Why: Every redirect is a read. Most links get clicked in a burst soon after they are created. Serving those reads from an in-memory cache keeps p50 redirect latency in single-digit milliseconds.
Tradeoff: Cache-invalidation complexity on link edits and deletes. When a destination URL changes, the cache entry needs to be evicted immediately — which adds a write path that touches both stores.
The hard part
The interesting design tension was around click analytics. The naive approach — write a row to the database on every redirect — adds a synchronous write to the hot path, which is exactly where you do not want extra latency. The better approach is to queue the analytics event asynchronously and let a background worker flush it to Postgres. This keeps the redirect fast and keeps the analytics eventually consistent, which is the right tradeoff for a click counter.
Results & lessons
Previously hosted on Render (may need re-deploying — confirm status before publishing). The project achieved its goal of forcing real decisions around caching, async writes, and the split between a fast read path and a slower analytics path.
- →The redirect path and the analytics path have fundamentally different latency requirements — design them separately.
- →Cache invalidation on writes is a first-class concern, not an afterthought.
- →Building something "simple" end to end reveals the decisions that real systems have already made for you.
