# System Design: [System or feature name]

> Complete system design document for a service or platform at scale:
> requirements, capacity estimates, API, data model, high-level and
> detailed design, scaling, reliability, security, operations, cost and
> trade-offs. Replace every `[bracketed]` value and every value marked
> *Example*. Worked numbers use a URL-shortening service so the formulas
> stay concrete; swap in your own.

<!--
Guidance: How to use this template
- Write sections 2 and 3 before drawing anything. Most bad designs come
  from unstated requirements or unchecked numbers.
- Keep every estimate as a formula with its inputs, so reviewers can
  change an assumption and see the effect.
- Section 10 (trade-offs) is mandatory. A design that lists no rejected
  options has not been reviewed.
-->

## Document control

<!-- Guidance: A design is a proposal until it is approved; record who decided and when. -->

| Field | Value |
|---|---|
| Document ID | [SDD-SYSTEM-001] |
| System / feature | [Name] |
| Version | [0.1] |
| Status | [Draft / In review / Approved / Implemented / Superseded] |
| Author(s) | [@author] |
| Reviewers | [@reviewer-1], [@reviewer-2] |
| Last updated | YYYY-MM-DD |
| Related | [PRD / SRS], [Architecture description], [ADRs] |

### Approvals

| Role | Name | Decision | Date |
|---|---|---|---|
| Tech lead | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
| Staff / principal engineer | [@name] | [Decision] | YYYY-MM-DD |
| SRE / operations | [@name] | [Decision] | YYYY-MM-DD |
| Security | [@name] | [Decision] | YYYY-MM-DD |

## Revision history

<!-- Guidance: Note what changed in the design, not the wording. -->

| Version | Date | Author | Change summary |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@name] | First draft for design review |
| 0.2 | YYYY-MM-DD | [@name] | *Example:* Switched ID generation to base62 counter ranges after review |
| 1.0 | YYYY-MM-DD | [@name] | *Example:* Approved |

## Contents

<!-- Guidance: Keep aligned with the headings. -->

1. [Overview](#1-overview)
2. [Requirements](#2-requirements)
3. [Capacity estimation](#3-capacity-estimation)
4. [API design](#4-api-design)
5. [Data model](#5-data-model)
6. [High-level design](#6-high-level-design)
7. [Detailed design](#7-detailed-design)
8. [Scalability and performance](#8-scalability-and-performance)
9. [Reliability, security and operations](#9-reliability-security-and-operations)
10. [Trade-offs and alternatives](#10-trade-offs-and-alternatives)
11. [Delivery plan and risks](#11-delivery-plan-and-risks)
12. [Appendices](#12-appendices)

## 1. Overview

<!-- Guidance: One page that a busy reviewer can read alone: problem, proposed solution in a paragraph, and the decisions that need their attention. -->

### 1.1 Problem statement

[What problem exists today, who has it, and what it costs. Example: "Our
marketing links are 120+ characters, break in SMS, and give us no click
data."]

### 1.2 Proposed solution

[One paragraph. Example: "A stateless redirect service backed by a
key-value store and a CDN, generating 7-character base62 keys from
pre-allocated counter ranges, with click events streamed to the
analytics pipeline."]

### 1.3 Decisions requested from reviewers

| ID | Question | Recommended option | Section |
|---|---|---|---|
| Q-01 | *Example:* Counter ranges or hashing for key generation? | Counter ranges | 7.1, 10 |
| Q-02 | *Example:* Accept eventual consistency for click counts? | Yes, up to 60 s lag | 7.3 |
| [Q-nn] | [Question] | [Option] | [Section] |

## 2. Requirements

<!-- Guidance: Functional requirements describe behaviour; non-functional requirements describe qualities with numbers. Mark what is out of scope so nobody designs for it. -->

### 2.1 Functional requirements

| ID | Requirement | Priority |
|---|---|---|
| FR-01 | *Example:* Create a short link for a long URL, optionally with a custom alias | Must |
| FR-02 | *Example:* Redirect a short link to its long URL | Must |
| FR-03 | *Example:* Expire links at an optional expiry date | Should |
| FR-04 | *Example:* Show click counts per link per day | Should |
| [FR-nn] | [Requirement] | [Must / Should / Could] |

### 2.2 Non-functional requirements

| ID | Quality | Target | Measured as |
|---|---|---|---|
| NFR-01 | Latency | *Example:* Redirect p99 at or below 50 ms at the edge | Edge logs, rolling 5 min |
| NFR-02 | Availability | *Example:* 99.99% monthly for redirects | Synthetic checks |
| NFR-03 | Durability | *Example:* No created link is ever lost | Backup and restore drills |
| NFR-04 | Consistency | *Example:* A new link redirects within 1 s of creation | Integration test |
| NFR-05 | Security | *Example:* No open redirect to blocklisted domains | Security test suite |
| [NFR-nn] | [Quality] | [Target] | [Measurement] |

### 2.3 Out of scope

- [Example: link editing after creation]
- [Example: user accounts beyond API keys]
- [Item deferred, with the release that will take it]

## 3. Capacity estimation

<!-- Guidance: Back-of-the-envelope numbers decide the shape of the design. Show inputs, formulas and results; round generously and say which way you rounded. -->

### 3.1 Assumptions

| Input | Symbol | Value | Source |
|---|---|---|---|
| New links per month | $N_w$ | *Example:* 100 million | Marketing forecast |
| Read to write ratio | $r$ | *Example:* 100 : 1 | Comparable services |
| Average stored record | $s$ | *Example:* 500 bytes | Schema in 5.2 |
| Retention | $Y$ | *Example:* 5 years | Product decision |
| Peak to average factor | $k$ | *Example:* 3 | Traffic history |

### 3.2 Throughput

Average write rate, with $2.6 \times 10^6$ seconds in a month:

$$
QPS_w = \frac{N_w}{2.6 \times 10^{6}} = \frac{10^{8}}{2.6 \times 10^{6}} \approx 40 \ \text{writes/s}
$$

Reads and peak:

$$
QPS_r = r \cdot QPS_w \approx 4\,000 \ \text{reads/s}, \qquad
QPS_{peak} = k \cdot QPS_r \approx 12\,000 \ \text{reads/s}
$$

### 3.3 Storage

$$
S = N_w \times 12 \times Y \times s = 10^{8} \times 12 \times 5 \times 500 \ \text{B} = 3 \ \text{TB}
$$

With replication factor 3 the raw footprint is about 9 TB.

### 3.4 Bandwidth and memory

$$
B_{out} \approx QPS_{peak} \times 500 \ \text{B} \approx 6 \ \text{MB/s}
$$

Caching the hottest 20% of daily read keys (Pareto assumption):

$$
M = 0.2 \times QPS_r \times 86\,400 \times s \approx 35 \ \text{GB}
$$

### 3.5 Key space

With base62 keys of length $L$ the key space is $62^{L}$. For $L = 7$:

$$
62^{7} \approx 3.5 \times 10^{12} \gg 6 \times 10^{9} \ \text{links in 5 years}
$$

### 3.6 Summary

| Quantity | Estimate | Design consequence |
|---|---|---|
| Write rate | ~40/s average | A single partitioned store handles writes |
| Read rate | ~4 000/s average, ~12 000/s peak | Reads must be served from cache and CDN |
| Storage | ~3 TB logical, ~9 TB replicated | Horizontally partitioned key-value store |
| Cache | ~35 GB | Fits a small in-memory cache cluster |
| [Quantity] | [Estimate] | [Consequence] |

## 4. API design

<!-- Guidance: Define the contract first: resources, methods, request and response shapes, errors, idempotency, pagination, rate limits and versioning. Use RFC 9457 problem details for errors. -->

### 4.1 Endpoints

| Method | Path | Purpose | Auth | Idempotent |
|---|---|---|---|---|
| POST | `/v1/links` | Create a short link | API key | With `Idempotency-Key` header |
| GET | `/{key}` | Redirect (302) to the long URL | None | Yes |
| GET | `/v1/links/{key}` | Read link metadata | API key | Yes |
| GET | `/v1/links/{key}/stats?from=&to=` | Daily click counts | API key | Yes |
| DELETE | `/v1/links/{key}` | Disable a link | API key | Yes |

### 4.2 Request and response examples

Create a link. The `Idempotency-Key` header makes retries safe: the same
key within 24 hours returns the original response instead of a second
link.

```http
POST /v1/links HTTP/1.1
Content-Type: application/json
Idempotency-Key: 7f9c2ba4-e88f-11ee-a951-0242ac120002

{"url": "https://example.com/campaigns/spring?utm_source=sms", "alias": null, "expiresAt": "2027-01-01T00:00:00Z"}
```

```http
HTTP/1.1 201 Created
Location: /v1/links/aZ3kP9q
Content-Type: application/json

{"key": "aZ3kP9q", "shortUrl": "https://sho.rt/aZ3kP9q", "url": "https://example.com/campaigns/spring?utm_source=sms", "expiresAt": "2027-01-01T00:00:00Z"}
```

### 4.3 Errors

```http
HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{"type": "https://sho.rt/problems/alias-taken", "title": "Alias already in use", "status": 409, "detail": "The alias 'spring' belongs to another link.", "instance": "/v1/links"}
```

| Status | Problem type | When |
|---|---|---|
| 400 | `invalid-url` | URL is malformed or not http(s) |
| 403 | `blocked-domain` | Target domain is on the abuse blocklist |
| 409 | `alias-taken` | Custom alias exists |
| 429 | `rate-limited` | Client exceeded its quota; `Retry-After` is set |

### 4.4 Rate limits and versioning

- Rate limit: [Example: 100 creates per minute per API key, token bucket].
- Versioning: path prefix `/v1`; breaking changes go to `/v2` with a
  [12-month] deprecation window announced by the `Deprecation` header.

## 5. Data model

<!-- Guidance: Choose storage by access pattern, not by habit. Show entities, keys, partitioning and the queries each store must answer. -->

### 5.1 Entities

Logical entities and their keys. Physical types depend on the stores
chosen in 5.2.

```mermaid
erDiagram
  LINK {
    string key PK
    string long_url
    string owner_id
    datetime created_at
    datetime expires_at
    boolean disabled
  }
  CLICK_DAILY {
    string key PK
    date day PK
    int clicks
  }
  API_KEY {
    string id PK
    string owner_id
    int rate_limit
  }
  LINK ||--o{ CLICK_DAILY : "counted in"
  API_KEY ||--o{ LINK : creates
```

### 5.2 Storage choices

| Data | Store | Partition key | Main queries | Why |
|---|---|---|---|---|
| Links | *Example:* Distributed key-value store | `key` | Get by key | Single-key lookups at high read rate |
| Daily clicks | *Example:* Wide-column or time-series store | `key`, `day` | Range by key and day | Append-heavy counters |
| API keys | *Example:* Relational database | `id` | Get by id | Small, transactional |
| [Data] | [Store] | [Key] | [Queries] | [Reason] |

## 6. High-level design

<!-- Guidance: One diagram that shows every component on the request paths and where state lives. Name components by responsibility. -->

```mermaid
flowchart LR
  client([Client]) --> cdn[CDN / edge cache]
  cdn -->|miss| lb[Load balancer]
  lb --> redirect[Redirect service]
  lb --> api[Link API service]
  redirect --> cache[(In-memory cache)]
  redirect --> kv[(Key-value store)]
  api --> idgen[Key range allocator]
  api --> kv
  redirect --> events[[Click event stream]]
  events --> agg[Click aggregator]
  agg --> stats[(Click store)]
  api --> stats
```

| Component | Responsibility | Scales by |
|---|---|---|
| CDN / edge cache | Serve popular redirects close to users | Provider |
| Redirect service | Resolve key, emit click event, return 302 | Stateless replicas |
| Link API service | Create, read, disable links; enforce quotas | Stateless replicas |
| Key range allocator | Hand out unique counter ranges to API instances | Single leader, rarely called |
| Click aggregator | Turn click events into daily counts | Consumer group partitions |
| [Component] | [Responsibility] | [Scaling unit] |

## 7. Detailed design

<!-- Guidance: Go deep on the parts that are new, risky or contested. Show the sequence of calls and the failure behaviour of each step. -->

### 7.1 Key generation

[Describe the algorithm. Example: each API instance leases a block of
1 000 000 counter values from the allocator, encodes the next value in
base62 and pads to 7 characters. Leases are stored durably; a crashed
instance wastes at most one block. Custom aliases are inserted with a
conditional write that fails if the key exists.]

### 7.2 Redirect path

The redirect never waits for analytics: the click event is published
asynchronously after the long URL is known, and a cache miss repopulates
the cache for later requests.

```mermaid
sequenceDiagram
  participant C as Client
  participant E as CDN
  participant R as Redirect service
  participant M as Cache
  participant K as Key-value store
  participant S as Event stream
  C->>E: GET /aZ3kP9q
  E->>R: Cache miss
  R->>M: Get aZ3kP9q
  alt cache hit
    M-->>R: long URL
  else cache miss
    R->>K: Get aZ3kP9q
    K-->>R: long URL
    R->>M: Set aZ3kP9q (TTL 24 h)
  end
  R--)S: click event (async)
  R-->>E: 302 Location: long URL
  E-->>C: 302
```

### 7.3 Click counting

[Example: redirect instances publish click events without waiting; the
aggregator consumes per partition, counts in memory for 30 s and adds
the totals to the daily row. Counts may lag by up to 60 s and may
over-count by duplicates on consumer restart; dashboards state this.]

### 7.4 Expiry and deletion

[Example: expiry is checked on read; a daily job removes expired links
from the store and purges them from the CDN.]

## 8. Scalability and performance

<!-- Guidance: Show how the design meets the numbers in section 3 and where it will break first. -->

| Concern | Approach | Limit before redesign |
|---|---|---|
| Read throughput | CDN and in-memory cache absorb most reads; redirect service scales horizontally | *Example:* ~200 000 reads/s per region |
| Write throughput | Partitioned key-value store; key ranges avoid coordination | *Example:* ~5 000 writes/s |
| Hot keys | CDN caching with short TTL; request coalescing in the redirect service | One viral link at ~50 000 reads/s |
| Storage growth | Add partitions; expire old links | [Limit] |
| [Concern] | [Approach] | [Limit] |

Latency budget for a redirect cache miss (target p99 50 ms):

| Step | Budget |
|---|---|
| Edge to region network | 20 ms |
| Load balancer and service | 5 ms |
| Cache lookup | 2 ms |
| Key-value lookup on cache miss | 10 ms |
| Headroom | 13 ms |

## 9. Reliability, security and operations

<!-- Guidance: Name the failure modes, the threats and the signals operators watch. Each mitigation should be testable. -->

### 9.1 Failure modes

| Failure | Impact | Detection | Mitigation |
|---|---|---|---|
| Cache cluster down | Higher latency, more store load | Cache error rate | Fall through to store; store sized for full read load at reduced latency |
| Key-value store partition unavailable | Some keys fail | Error rate per partition | Replication factor 3, quorum reads |
| Event stream down | Click counts lag | Consumer lag alert | Redirects never block on events; replay from buffer |
| Region outage | Full outage in region | Synthetic checks | Active-active in two regions with replicated store |
| [Failure] | [Impact] | [Signal] | [Mitigation] |

### 9.2 Security and abuse

| Threat | Control |
|---|---|
| Malicious target URLs (phishing, malware) | Domain blocklist and URL reputation check at creation; periodic rescans |
| Key enumeration | Non-sequential presentation of keys; rate limits on reads per IP |
| API key leakage | Keys hashed at rest, per-key quotas, rotation endpoint |
| Data exposure | TLS everywhere; minimal personal data; access logging |
| [Threat] | [Control] |

### 9.3 Observability

| Signal | Metric or log | Alert threshold |
|---|---|---|
| Redirect latency | p50, p99 per region | p99 above 50 ms for 10 min |
| Redirect errors | 5xx rate | Above 0.1% for 5 min |
| Cache hit ratio | Hits / requests | Below 85% for 30 min |
| Click pipeline lag | Consumer lag seconds | Above 120 s |
| [Signal] | [Metric] | [Threshold] |

### 9.4 Cost estimate

| Item | Unit cost | Quantity | Monthly cost |
|---|---|---|---|
| Compute (redirect + API) | [$/instance-month] | [n] | [$] |
| Key-value store | [$/TB-month] | [9 TB] | [$] |
| Cache | [$/GB-month] | [35 GB] | [$] |
| CDN egress | [$/GB] | [GB/month] | [$] |
| **Total** | | | **[$]** |

## 10. Trade-offs and alternatives

<!-- Guidance: For each major decision list the options, what each costs and why you chose one. Reviewers will propose the alternatives anyway; answer them here. -->

| Decision | Options | Chosen | Why | What we give up |
|---|---|---|---|---|
| Key generation | Hash of URL; random keys with collision check; counter ranges | Counter ranges | No collisions, no read-before-write | Keys reveal rough creation order unless shuffled |
| Redirect status | 301 permanent; 302 temporary | 302 | Every click reaches us for counting and disabling | More origin traffic than 301 |
| Click counts | Synchronous increment; async stream | Async stream | Redirect latency unaffected | Counts lag up to 60 s |
| [Decision] | [Options] | [Chosen] | [Why] | [Cost] |

## 11. Delivery plan and risks

<!-- Guidance: How the design reaches production safely, and what could stop it. -->

### 11.1 Milestones

| Milestone | Scope | Exit criterion | Target date |
|---|---|---|---|
| M1 | Create and redirect, single region | Load test at 2x peak passes | YYYY-MM-DD |
| M2 | Click counts and dashboards | Lag below 60 s for 7 days | YYYY-MM-DD |
| M3 | Second region, active-active | Region failover drill passes | YYYY-MM-DD |
| [Mn] | [Scope] | [Criterion] | YYYY-MM-DD |

### 11.2 Rollout and rollback

[Feature flags, canary percentage, metrics watched during rollout, and
the exact rollback steps.]

### 11.3 Risks

| ID | Risk | Likelihood | Impact | Mitigation | Owner |
|---|---|---|---|---|---|
| R-01 | *Example:* Traffic forecast is 3x too low | Medium | High | Load test at 5x; autoscaling limits reviewed | [@name] |
| R-02 | *Example:* Abuse overwhelms blocklist process | Medium | Medium | Automated reputation service; manual review queue | [@name] |
| [R-nn] | [Risk] | [L / M / H] | [L / M / H] | [Mitigation] | [@name] |

## 12. Appendices

<!-- Guidance: Supporting detail, glossary and open questions. -->

### 12.1 Open questions

| ID | Question | Owner | Due |
|---|---|---|---|
| OQ-01 | *Example:* Do we need per-link analytics beyond daily counts? | [@name] | YYYY-MM-DD |
| [OQ-nn] | [Question] | [@name] | YYYY-MM-DD |

### 12.2 Glossary

| Term | Meaning |
|---|---|
| QPS | Queries (requests) per second |
| p99 | 99th percentile latency |
| TTL | Time to live of a cached entry |
| Base62 | Encoding with digits, lowercase and uppercase letters |
| [Term] | [Meaning] |

### 12.3 References

| Reference | Use |
|---|---|
| RFC 9110 HTTP Semantics | Status codes and methods |
| RFC 9457 Problem Details for HTTP APIs | Error format in 4.3 |
| [Internal ADR or document] | [Use] |

## Standard and licence note

Based on common industry practice for system design documents and on the
HTTP standards cited in 12.3. Original text written for this collection;
example numbers are illustrative. Template licence: MIT.
