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.
Document control
| 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
| 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
1. Overview
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
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
3.1 Assumptions
| Input | Symbol | Value | Source |
|---|
| New links per month | Nw | 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×106 seconds in a month:
QPSw=2.6×106Nw=2.6×106108≈40 writes/s QPSr=r⋅QPSw≈4000 reads/s,QPSpeak=k⋅QPSr≈12000 reads/s 3.3 Storage
S=Nw×12×Y×s=108×12×5×500 B=3 TB With replication factor 3 the raw footprint is about 9 TB.
3.4 Bandwidth and memory
Bout≈QPSpeak×500 B≈6 MB/s Caching the hottest 20% of daily read keys (Pareto assumption):
M=0.2×QPSr×86400×s≈35 GB 3.5 Key space
With base62 keys of length L the key space is 62L. For L=7:
627≈3.5×1012≫6×109 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
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.
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/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/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
5.1 Entities
Logical entities and their keys. Physical types depend on the stores
chosen in 5.2.
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
| 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
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.
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
| 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
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
| 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
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
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.