Skip to Content

Engineering

System Design, Live

Real numbers from the running server, not a diagram from a slide deck. Opening this page wakes the API if it's asleep.

Right Now

What the Server Is Doing

Server sleeps when idle and wakes on your first request

Awake For

—

Since this instance last woke

Requests Served

—

Since waking

Cache Hit Ratio

—

Active Links

—

Database Size

—

Bloom Filter

—

Route Latency Since Waking
RouteCallsp50p95p99
Loading…
Data Structures in Memory
StructureLive Value
LRU cache—
Bloom filter—
Rate limiter—
Code keyspace—
HyperLogLog—
Last cleanup—
AI Providers
ProviderRoleModelStatus
Jev by TypeSafeRisk decisions, categories, smart defaults and alias ranking——

Request X-Ray

What happened inside the server, span by span

Make a request, like shortening a link, and its internal timeline appears here.

Chaos Mode

Break It on Purpose

Inject real failures into your own request and watch the retries, timeouts and fallbacks happen live. Nothing is faked: the same code paths run in production.

Design an Experiment

Faults apply only to your request, not to anyone else using the site.

Inject Failures

Nothing Broken Yet

Pick a failure and run the experiment. You'll see how the system detects it, what it falls back to, and how long it costs, span by span.

Request Lifecycle

Two Paths Through the System

Creating a link is write-heavy and careful. Opening one is read-heavy and fast.

Create a Link

  1. 1Edge: request ID, origin check
  2. 2Rate limiter: create bucket
  3. 3Idempotency key check
  4. 4Validate and normalise URL
  5. 5Heuristics → safe fetch → Jev risk decision
  6. 6Bloom + database uniqueness check
  7. 7Encrypt with AES-256-GCM
  8. 8Insert, add to Bloom filter, return claim code

Open a Link

  1. 1Next.js route handler forwards the visitor
  2. 2Rate limiter: resolve bucket
  3. 3Bloom filter: definitely absent → 404
  4. 4LRU cache → database on miss
  5. 5Lazy expiry check
  6. 6Rules: lock, password, warning, confirmation
  7. 7Atomic click update with limit check
  8. 8302 redirect, analytics written after response

Trade-Offs

Design Decisions

Every choice has a cost. Here's what we picked, why, and what we gave up.

Design decisions and trade-offs
DecisionWhyTrade-Off
SQLite with WAL on a Railway volumeOne instance, modest write rate, zero network hops to the database. Separate reader and writer connections give snapshot reads.Can't scale writes horizontally. Moving to Postgres would be the step for multiple instances.
In-process LRU cache instead of RedisA single instance can serve hot links from memory with no extra service to run or pay for while sleeping.Cache is empty after every wake. Acceptable because cold reads are still a single indexed query.
Bloom filter in front of the databaseRandom or mistyped codes are rejected in microseconds without touching SQLite, which also blunts enumeration attempts.Can't delete entries, so it's rebuilt during each cleanup sweep.
No background timersRailway sleeps idle services. Any timer or poller would keep it awake and cost money.Cleanup runs lazily on reads and piggybacks on requests, so expired rows can linger until the next visit.
AES-256-GCM with the code as associated dataDestinations can contain tokens. Binding ciphertext to its code means rows can't be swapped between links.Search by destination isn't possible in SQL; only the domain is stored in plain text.
HyperLogLog for unique visitorsCounts uniques in 4 KB per link with ±1.6% error, without storing who visited.Estimates, not exact numbers. Small counts use linear counting for accuracy.
One AI provider: Jev for every decisionTyped answers with calibrated confidence make a better gate than free text. Jev decides risk, category, smart defaults and alias ranking; summaries come from the page itself.No generated prose, so a summary is only as good as the page's own description.
Workspace keys instead of accountsNo passwords to leak or reset. A random browser key sent as a header also makes the API immune to CSRF.Losing the key loses access, so Settings offers copy, switch and rotate, and claim codes move single links.
Server-side redirects through Next.jsShort links stay on the main domain, the API stays private behind the site, and redirects set no cookies on visitors.One extra hop from the edge to the API for each redirect.

Failure-First

What Happens When Things Break

Designed for the unhappy path first.

Failure modes
FailureHow It's DetectedWhat Happens
AI provider slow or down7 second budget, typed upstream errorsHeuristic verdict is used; the report shows which checker failed
Server asleepRequest takes longer than 1.5 secondsUI says the server is waking; safe requests retry with backoff
Double-submitted formIdempotency-Key headerSecond request replays the first result instead of creating a duplicate
Two people use the last openConditional UPDATE … RETURNINGExactly one succeeds; the other sees a used-up page
Chat app unfurls a burn-after-read linkBot user agents and a human confirmation stepPreviews never consume opens
A destination turns malicious laterVerdicts older than 6 hours are re-checked after an openLater visitors see a warning; the owner sees what changed
Client floods the APIToken bucket per client and action429 with Retry-After and a friendly message
Destination points to an internal networkDNS resolved and checked before every fetch and redirect hopRejected at creation and never fetched
Unhandled exceptionGlobal handler with request IDSafe message to the user, full trace in structured logs