Capstone Design a Short Link Service End to End

Replication Sharding and the Capstone · 50 min

System Design · Module 7 · Lesson 4 of 4 · 50 min

Capstone Design a Short Link Service End to End

Assemble requirements APIs schema capacity routing and caching into one defended design, then review retries freshness and failure recovery.

A complete design connects each guarantee to the request path and state transition that enforces it.

01 The idea

The problem this lesson solves

You now have the ingredients for a short-link service, but a complete design is more than assembling the ingredients into a diagram. Its API, schema, capacity budget, cache policy and failure behavior must agree. This capstone provides a worked design with explicit assumptions, then asks you to change it and defend the consequences.

Use the API, database, capacity and cache concepts from earlier modules. These lessons add distributed ownership and failure trade-offs.

Design packetThe linked set of requirements, interfaces, schema, estimates, diagram and failure review.
Trade-off logA record of a choice, its benefit, cost and revisit trigger.
Failure domainThe set of resources that can be lost together in a modeled incident.

02 Worked example

Defend a complete short-link service

Short-link read and write pathsApplicationGET /r/a7Cache lookupIs a7 available?HitMissReturn copySkip databaseRead databaseFill then returnCreation writes to the durable database; destination changes need a cache policy.Short-link read and write pathsApplicationGET /r/a7Cache lookupIs a7 available?HitMissReturn copySkip databaseRead databaseFill then returnCreation writes to the database.
Redirects may use an eligible cached mapping. Creation writes to the durable database; cache policy must account for destination changes.
1
SpecifyWrite features, excluded analytics, mutable-destination policy and measurable targets.
2
BuildDefine management endpoints, redirect path, unique code schema and scoped operation identity.
3
SizeEstimate external traffic, origin misses, storage and capacity during the chosen failure.
4
ReviewWalk a retry, stale fill, cold cache, missing app and lagging follower; record the user-visible outcomes.

03 The method

Understand the mechanism

Fix the product contract and workload

The service creates links for authenticated owners, redirects public readers, lists an owner’s links and permits owner-authorized destination changes. Analytics, custom domains and billing are outside this first design. Codes are unique, and an acknowledged creation survives an app restart.

Use 100,000 DAU, ten redirects per active user/day, 10,000 creations/day and a 5× peak factor. That gives about 57.87 peak redirects/s and 0.579 peak creations/s. At 500 modeled bytes per mapping and one year of retention, logical mapping data is 1.825 GB before physical overhead and recovery copies.

These assumptions justify starting with a relational source of truth and measured app capacity. They do not justify sharding simply because the course has taught sharding. Availability and durability still need a deployment and recovery contract independent of the modest QPS.

For mutable destinations, define public redirect freshness and a strict owner verification path separately. Do not claim immediate global freshness while quietly serving arbitrary cached copies. The initial strict path reads the authority; any public caching must fit a declared policy and tested race behavior.

Define identities and operations together

POST /v1/links accepts a destination and an idempotency key. The authenticated caller supplies the owner identity through trusted context, not a body field granting authority. A successful response identifies the saved code and resource location.

For this capstone, the replayed creation result identifies the immutable code and resource location; it does not promise to replay the destination’s current or original mutable fields. That makes the stored result_code sufficient for the demonstrated result. An API promising an exact original response body would need to retain that body or equivalent immutable outcome data too.

GET /r/{code} performs a public lookup and returns a temporary redirect with an explicit HTTP caching policy when the record is active. An authoritative missing result and an unavailable lookup have different responses. Management reads and edits check ownership.

The links table stores code, owner_id, destination, version and creation time. Code is the primary key. An owner-and-time index supports stable listings. A create_operations table uniquely identifies caller plus operation key and stores the request fingerprint and result code.

The illustrative SQL below demonstrates one successful transaction with fixed ids. A real handler must validate inputs, allocate collision-resistant or otherwise controlled codes, bind parameters, handle concurrent key conflicts and return the stored outcome. The schema is an executable foundation, not a complete web server.

Make creation retry-safe

On the first request with k1, begin a transaction. Claim the scoped operation identity under a unique constraint, create the mapping and store the replayable outcome in the same transaction. Commit before reporting success.

If a competing attempt uses k1 with the same fingerprint, recover the recorded outcome or follow the documented in-progress policy. If it uses a different fingerprint, reject the conflict. A crash after commit but before response must leave enough information to return the original code a7.

Code allocation has a separate uniqueness problem. Random generation can collide, so the database constraint remains necessary and the handler needs bounded collision retries. Sequential allocation has different enumeration and distribution properties. Choose the scheme according to the product rather than assuming a large alphabet removes all concerns.

Keep operation-key retention explicit. If a client can retry after the retained record expires, define how it discovers the original result or whether a new effect is possible. This is part of the API contract, not an invisible housekeeping detail.

Trace redirect and destination update

A redirect request reaches an eligible app instance, validates the code form and consults an allowed read path. An eligible cache hit can supply the destination. A miss reads the authoritative mapping, distinguishes absent from unavailable and fills only under the chosen freshness policy.

An owner update checks permission and an expected version, changes the destination and advances the version atomically. A stale version should produce a conflict/precondition outcome rather than overwrite a newer edit.

After updating, the owner’s verification reads a sufficiently fresh authority path. Public cached reads require the separate invalidation or version-coordination design. Reproduce the late-reader fill race before claiming a freshness bound; a new TTL on old data is not enough.

A redirect returns a Location for the browser to follow. It does not require the service to fetch arbitrary destinations. Adding a preview crawler would introduce server-side fetching, additional security controls and a different capacity model, so it remains outside this initial design.

Size normal operation and recovery

At 90% eligible cache hits, the modeled peak origin redirect rate is about 5.79/s. At zero hits it is 57.87/s. Keep creation writes, management reads and background maintenance as additional rows rather than assuming the read-cache multiplier applies to everything.

Benchmark per-instance serving capacity at the latency and error target, then provision enough survivors for the chosen failure. Coordinate database connection pools across the app fleet. Extra app instances must not create unbounded origin concurrency during a cache outage.

Budget table data, indexes, live copies, logs, backups and maintenance space separately. Define recovery point and recovery time objectives, and perform a restore drill. A read replica does not protect against every historical data mistake.

Monitor gateway latency/errors, app saturation, pool waits, database work, cache misses and logical creation outcomes. A single “service up” metric cannot explain whether a slow redirect is waiting at the gateway, pool or database.

Review five incidents end to end

Lost creation response: the client retries k1 and receives a7 from durable operation state. Cache late fill: the old reader must obey the declared freshness mechanism, or strict reads bypass the copy. Cold cache: admission and origin budgets prevent unbounded rebuild work.

App failure: routing removes the instance after detection, survivors provide the planned capacity, and eligible retries retain operation identity. Replica lag: fresh reads use the authority or a verified progress policy rather than returning a false missing result.

For every incident, name the caller-visible outcome, the component that owns recovery and the evidence that recovery completed. “Retry,” “fail over” and “invalidate” are starting verbs, not complete incident protocols.

Also test combined conditions required by the product. An app failure during cache recovery may consume more reserve than either isolated event. State the failure scope you support and the degraded behavior outside it.

Grow only when a measured limit changes the decision

If read demand exceeds the tested database budget, investigate query efficiency, caching and suitable replica reads. If write or storage ownership needs partitioning, choose a shard key from the dominant access path and design the owner-listing secondary path.

If a single key becomes viral, adding shards may leave that key’s owner overloaded. If a region is lost, DNS and routing changes must account for caches, connections and surviving data authority. Growth changes specific constraints; it does not make every distributed mechanism necessary at once.

Keep a decision log with choice, benefit, cost and revisit trigger. Example: one relational authority simplifies uniqueness and transactions; revisit when measured write/storage capacity or required failure isolation exceeds the tested design.

The finished packet should be executable by another engineer: requirements, workload worksheet, API outcomes, schema, request diagrams, transaction boundaries, cache contract, recovery tests and growth triggers. That is the standard for a self-contained design, beyond naming the right components.

Guided tutorial

  1. Create the executable schema

    Run the SQL below in a disposable PostgreSQL database. It creates links, the owner-listing index and operation records, then commits a7 with k1 in one transaction. Verify that the final SELECT returns k1 and a7 with the stored destination.

  2. Review replay and conflict behavior

    Attempt to insert another operation for the same caller and k1 in a separate transaction. The unique key must reject it. Explain how the real handler would roll back or use a conflict-aware claim, then read the stored fingerprint and result rather than blindly create another link.

  3. Perform a conditional destination edit

    Update a7 only where owner_id=7 and version=1, set a new destination and advance version to 2. Repeat an update expecting version 1 and observe zero changed rows. Explain the response to the stale client and the cache-freshness implications of the successful update.

  4. Submit and challenge the design packet

    Include the concrete API outcomes, 57.87 peak read QPS, 1.825 GB logical annual data, schema and both creation/redirect paths. Walk all five incidents from the explanation. Then increase traffic by 100× and identify the first measurement needed before choosing the next architectural change.

PostgreSQL capstone setup

-- Run once in a disposable PostgreSQL database.
CREATE TABLE sd_capstone_links (
  code text PRIMARY KEY,
  owner_id bigint NOT NULL,
  destination text NOT NULL,
  version bigint NOT NULL DEFAULT 1 CHECK (version > 0),
  created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX sd_capstone_owner_recent
  ON sd_capstone_links (owner_id, created_at DESC, code DESC);

CREATE TABLE sd_capstone_create_operations (
  caller_id bigint NOT NULL,
  operation_key text NOT NULL,
  request_fingerprint text NOT NULL,
  result_code text REFERENCES sd_capstone_links(code),
  state text NOT NULL DEFAULT 'pending' CHECK (state IN ('pending','completed')),
  PRIMARY KEY (caller_id, operation_key),
  CHECK ((state = 'pending' AND result_code IS NULL)
      OR (state = 'completed' AND result_code IS NOT NULL))
);

-- One successful attempt. A real handler binds values and handles conflicts.
BEGIN;
INSERT INTO sd_capstone_create_operations
  (caller_id, operation_key, request_fingerprint)
VALUES (7, 'k1', 'create:https://example.com/guide');
INSERT INTO sd_capstone_links (code, owner_id, destination)
VALUES ('a7', 7, 'https://example.com/guide');
UPDATE sd_capstone_create_operations
SET result_code = 'a7', state = 'completed'
WHERE caller_id = 7 AND operation_key = 'k1';
COMMIT;

SELECT o.operation_key, l.code, l.destination
FROM sd_capstone_create_operations o
JOIN sd_capstone_links l ON l.code = o.result_code
WHERE o.caller_id = 7 AND o.operation_key = 'k1';

-- Owner edit: only the expected version can be changed.
UPDATE sd_capstone_links
SET destination = 'https://example.com/updated', version = version + 1
WHERE code = 'a7' AND owner_id = 7 AND version = 1
RETURNING code, version;

Check your result

Your submission contains an API contract, schema, capacity estimate, labeled request paths and a failure review. Walk one creation retry and one stale-cache race from the caller’s perspective.

04 Cheat sheet

Review the decisions

Use this table to explain the mechanism and its limit without memorizing a product name.

Choice or conceptRememberCheck
CreationAuthenticated idempotent management actionDurable scoped replay and unique code
RedirectLookup plus documented redirectMissing vs unavailable; cache freshness
CapacityWorkload and surviving budgetsWarm, cold and failure tests
GrowthMeasured revisit thresholdSecondary paths and migration cost

05 Where & why

Check the primary references

Use official references for protocol and implementation details. The questions here are teaching prompts.

Primary reference
HTTP semantics, methods and status codes

Read HTTP semantics, methods and status codes

Primary reference
PostgreSQL table constraints

Read PostgreSQL table constraints

Primary reference
PostgreSQL backup and restore

Read PostgreSQL backup and restore

State the implementation version and the workload assumptions when defending your own design.

06 Interview questions

Explain the reasoning out loud

Answer before opening the explanation. The questions progress from vocabulary to decisions and failure behavior.

What does Design packet mean?
The linked set of requirements, interfaces, schema, estimates, diagram and failure review.
What does Trade-off log mean?
A record of a choice, its benefit, cost and revisit trigger.
What does Failure domain mean?
The set of resources that can be lost together in a modeled incident.
What makes this more than separate documents?
The identities and guarantees agree across requirements, API, schema, diagram and tests. Each artifact constrains and explains the others.
Why is sharding optional in the capstone?
The declared traffic and data size do not alone require it. A simpler candidate should be tested before accepting partitioning costs.
Where is code uniqueness enforced?
At the authoritative creation boundary using the chosen generation/collision strategy and durable uniqueness rule. A cache lookup is not a sufficient global guard.
What belongs in the failure table?
The trigger, affected request, user-visible result, recovery policy, responsible component and a useful observation or metric.
How do you defend a future scaling change?
Name a measured limit or forecast threshold, the benefit of the change and its new operational and consistency costs.

07 Practice problems

Practice a new case

Solve the new cases before revealing hints. Complete worked solutions follow the set.

Identify the primary path

Easy
Name the main lookup key for a public redirect and for an owner listing.
Show the hint
The entry identities differ.

Name the authority

Easy
Choose where durable code ownership belongs in the initial design.
Show the hint
The copy may disappear.

Capacity check

Medium
Compute peak reads for 50,000 DAU, eight reads each daily and a four-times peak factor.
Show the hint
Use a full-day conversion.

Unknown create

Medium
A client times out after creation may have committed; specify the next safe request.
Show the hint
Reuse one logical action identity.

Mutable destination

Medium
State the cache decision if updates must be visible immediately to new reads after acknowledgment.
Show the hint
Plain TTL does not eliminate late fills.

Capstone defense

Hard
Submit the six-part short-link design packet from the tutorial and justify whether it needs sharding under the the estimation module workload.
Follow-up
You must handle retries, cache failure and read-after-write behavior as well as peak reads.
Show the hint
Start with a simpler candidate and name measurable expansion triggers.

Worked solutions

1. Identify the primary path

A redirect starts with code; an owner listing starts with authenticated owner identity.

2. Name the authority

The relational source of truth owns the mapping and uniqueness contract; an eligible cache is replaceable serving state.

3. Capacity check

400,000 / 86,400 × 4 ≈ 18.52 peak read QPS before extra internal work.

4. Unknown create

Retry the same scoped idempotency key and payload under the documented retry window, then replay or recover the stored result. A new key can mean a new action.

5. Mutable destination

Use a coordinated stronger freshness path or bypass cache for these mutable reads. Define in-flight behavior and test the stale-fill race.

6. Capstone defense

A defensible initial design uses stateless app instances, an authenticated management API with scoped idempotency, a relational source of truth with unique codes, and cache-aside only under a stated freshness policy. the estimation module's modeled load alone does not justify sharding. Include overload limits, instance-loss capacity and a replica read policy if replicas are used. If growth forces sharding, explain code routing, the owner index, migration and cross-shard costs.