# System Design: [News feed name]

> Reference design for a social news feed: users follow other accounts and see
> a ranked, paginated feed of their posts. It covers hybrid fan-out (push for
> most authors, pull for accounts with very large followings), a ranking
> pipeline, feed caching, cursor pagination, media delivery and the
> consistency the product can tolerate. It follows the structure of the System
> Design Document template and is pre-filled with a worked design to adapt.
> Every value marked *Example* is an assumption: replace it and recompute.

<!--
Guidance: How to use this template
- Change the inputs in 3.1 first (users, posts per day, follower
  distribution). The celebrity threshold in 7.2 depends on them.
- The follower distribution matters more than the averages. Get the p99 and
  maximum follower counts from real data before choosing a threshold.
- Ranking (7.4) is the part product teams iterate on. Keep the serving
  contract stable even if the model changes weekly.
- Keep sections 9.1 and 10 filled; reviewers check failure modes and
  rejected options first.
-->

## Document control

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

| Field | Value |
|---|---|
| Document ID | [SDD-FEED-001] |
| System / feature | [Home feed] |
| 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], [Ranking model card], [ADR: fan-out strategy] |

### 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 |
| Ranking / ML lead | [@name] | [Decision] | YYYY-MM-DD |
| Trust and safety | [@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:* Hybrid fan-out with threshold at 10 000 followers |
| 0.3 | YYYY-MM-DD | [@name] | *Example:* Two-stage ranking (candidate retrieval, then scoring) |
| 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

[*Example:* The current feed is computed on every request by querying posts
from all followed accounts. p99 latency is 2.4 s for users following more than
1 000 accounts, the database is at 80% CPU at peak, and the order is purely
chronological, so engagement drops when users follow active accounts.]

### 1.2 Proposed solution

[*Example:* When an author with fewer than 10 000 followers posts, a fan-out
service writes the post ID into each follower's precomputed feed in an
in-memory store (push). Posts from accounts above the threshold are fetched at
read time and merged (pull). A feed service combines both candidate sets,
filters blocked and deleted content, ranks with a lightweight model and
returns a page with an opaque cursor. Post bodies and media are hydrated from
caches and a CDN.]

### 1.3 Decisions requested from reviewers

| ID | Question | Recommended option | Section |
|---|---|---|---|
| Q-01 | *Example:* Fan-out on write, on read, or hybrid? | Hybrid, threshold 10 000 followers | 7.2, 10 |
| Q-02 | *Example:* How long is the precomputed feed? | 800 entries per active user | 3.4, 7.3 |
| Q-03 | *Example:* Do we fan out to inactive users? | No; rebuild on return | 7.3 |
| Q-04 | *Example:* Is pagination stable while new posts arrive? | Yes, cursor holds a ranked snapshot ID | 7.5 |
| [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 post with text and up to 4 images or one video | Must |
| FR-02 | *Example:* Follow and unfollow accounts | Must |
| FR-03 | *Example:* Show a ranked home feed of followed accounts' posts | Must |
| FR-04 | *Example:* Paginate with infinite scroll; no duplicates or skips within a session | Must |
| FR-05 | *Example:* Show a "new posts" pill when fresh content is available | Should |
| FR-06 | *Example:* Remove deleted, blocked or muted content from feeds within 1 min | Must |
| FR-07 | *Example:* Offer a chronological view as a user option | Should |
| [FR-nn] | [Requirement] | [Must / Should / Could] |

### 2.2 Non-functional requirements

| ID | Quality | Target | Measured as |
|---|---|---|---|
| NFR-01 | Latency | *Example:* Feed page p99 at or below 250 ms server time | Service metrics |
| NFR-02 | Availability | *Example:* 99.95% monthly for feed reads | Synthetic checks |
| NFR-03 | Freshness | *Example:* A new post appears in followers' feeds within 10 s at p95 | Canary account probe |
| NFR-04 | Durability | *Example:* No acknowledged post is lost | RF=3, backups |
| NFR-05 | Correctness | *Example:* Deleted posts never shown after 60 s | Deletion probe |
| [NFR-nn] | [Quality] | [Target] | [Measurement] |

### 2.3 Out of scope

- [*Example:* recommendations from accounts the user does not follow]
- [*Example:* ads insertion (separate design; reserved slots only)]
- [*Example:* comments and reactions storage]
- [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 |
|---|---|---|---|
| Daily active users | $U$ | *Example:* 300 million | Growth plan |
| Feed page requests per user per day | $v$ | *Example:* 10 | Product analytics |
| Posts per user per day | $p$ | *Example:* 0.2 | Product analytics |
| Average followers per author (below threshold) | $f$ | *Example:* 200 | Follow graph |
| Stored post record (text + metadata) | $s_p$ | *Example:* 1 KB | Schema in 5.2 |
| Feed entry (post ID + author ID + score) | $s_e$ | *Example:* 20 bytes | 7.3 |
| Feed length kept per user | $L$ | *Example:* 800 entries | Decision Q-02 |
| Peak to average factor | $k$ | *Example:* 3 | Traffic history |

### 3.2 Throughput

Feed reads:

$$
QPS_{read} = \frac{U \times v}{86\,400} = \frac{3 \times 10^{8} \times 10}{86\,400} \approx 34\,700 \ \text{req/s}, \qquad \text{peak} \approx 104\,000 \ \text{req/s}
$$

Posts:

$$
P_{day} = U \times p = 6 \times 10^{7}, \qquad QPS_{post} \approx \frac{6 \times 10^{7}}{86\,400} \approx 700 \ \text{posts/s}, \qquad \text{peak} \approx 2\,100
$$

Fan-out writes (feed inserts):

$$
W_{fan} = QPS_{post} \times f \approx 700 \times 200 = 140\,000 \ \text{inserts/s}, \qquad \text{peak} \approx 420\,000
$$

### 3.3 Why the celebrity case breaks push

One post from an account with $5 \times 10^{7}$ followers would require
$5 \times 10^{7}$ inserts. At 420 000 inserts/s of spare capacity that single
post would take about 2 minutes to fan out and would delay every other post
behind it. This is why accounts above the threshold are pulled at read time.

### 3.4 Storage and memory

Posts per year:

$$
S_{posts} = 6 \times 10^{7} \times 1 \ \text{KB} \times 365 \approx 21.9 \ \text{TB/year} \ (\approx 66 \ \text{TB with RF} = 3)
$$

Precomputed feeds in memory (all daily active users):

$$
M_{feed} = U \times L \times s_e = 3 \times 10^{8} \times 800 \times 20 \ \text{B} = 4.8 \ \text{TB}
$$

With one replica that is 9.6 TB of memory, for example about 150 nodes with
64 GB usable each. Reducing $L$ to 400 halves it; measure how deep users
actually scroll before deciding.

Media (separate object store):

$$
S_{media} = P_{day} \times 0.2 \times 500 \ \text{KB} = 6 \ \text{TB/day}
$$

assuming 20% of posts carry media averaging 500 KB after compression.

### 3.5 Summary

| Quantity | Estimate | Design consequence |
|---|---|---|
| Feed reads | ~35 000/s average, ~104 000/s peak | Serve from precomputed in-memory feeds |
| Fan-out inserts | ~140 000/s average, ~420 000/s peak | Asynchronous workers; skip inactive users |
| Feed memory | ~4.8 TB, ~9.6 TB with replica | Sharded in-memory store; length cap |
| Post storage | ~22 TB/year logical | Partitioned post store; body cache |
| Media | ~6 TB/day | Object storage plus CDN, never through feed service |

## 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/posts` | Create a post | User token | With `Idempotency-Key` |
| DELETE | `/v1/posts/{id}` | Delete own post | User token | Yes |
| GET | `/v1/feed?cursor=&limit=20` | Home feed page | User token | Yes |
| GET | `/v1/feed/new-count?since=` | Count of newer items for the "new posts" pill | User token | Yes |
| PUT / DELETE | `/v1/follows/{accountId}` | Follow or unfollow | User token | Yes |

### 4.2 Request and response examples

A feed page returns hydrated items plus an opaque cursor for the next page:

```http
GET /v1/feed?limit=20 HTTP/1.1
Authorization: Bearer [token]
```

```json
{
  "items": [
    {"postId": "p_7a91", "author": {"id": "a_12", "name": "City Library"}, "text": "Late opening hours start Monday.", "media": [{"type": "image", "url": "https://media.example/p_7a91/0.webp"}], "createdAt": "2026-03-14T08:01:00Z", "reason": "followed"}
  ],
  "nextCursor": "eyJzbmFwIjoiczg4MiIsIm9mZiI6MjB9"
}
```

### 4.3 Errors, limits and versioning

| Status | Problem type | When |
|---|---|---|
| 400 | `invalid-cursor` | Cursor malformed or expired (older than 30 min); client restarts from top |
| 413 | `media-too-large` | Upload exceeds limit |
| 429 | `rate-limited` | Post or follow rate exceeded; `Retry-After` set |

- Rate limits: [*Example:* 50 posts per hour, 400 follows per day per user].
- Versioning: `/v1` path prefix; feed item schema is additive only.

## 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

Posts, the follow graph and the per-user precomputed feed are the three core
data sets; engagement events feed ranking.

```mermaid
erDiagram
  ACCOUNT {
    string account_id PK
    string name
    bigint follower_count
    boolean is_pull_author
  }
  POST {
    string post_id PK
    string author_id FK
    string text
    string media_keys
    datetime created_at
    string state
  }
  FOLLOW {
    string follower_id PK
    string followee_id PK
    datetime created_at
  }
  FEED_ENTRY {
    string user_id PK
    string post_id PK
    string author_id
    float base_score
    datetime inserted_at
  }
  ENGAGEMENT {
    string user_id PK
    string post_id PK
    string kind PK
    datetime at
  }
  ACCOUNT ||--o{ POST : writes
  ACCOUNT ||--o{ FOLLOW : "follows (follower)"
  ACCOUNT ||--o{ FEED_ENTRY : "receives"
  POST ||--o{ FEED_ENTRY : "appears in"
  POST ||--o{ ENGAGEMENT : "receives"
```

### 5.2 Storage choices

| Data | Store | Partition key | Main queries | Why |
|---|---|---|---|---|
| Posts | *Example:* Wide-column or sharded relational store | `post_id` (with author index) | Get by IDs (batch); recent by author | Batch point reads for hydration |
| Follow graph | *Example:* Sharded store with both directions materialised | `follower_id` and `followee_id` | Followers of X (fan-out); followees of Y (pull) | Both directions are hot |
| Precomputed feeds | *Example:* In-memory sorted sets | `user_id` | Top N by score; trim to $L$ | Sub-millisecond reads |
| Author recent posts | In-memory list per pull author | `author_id` | Latest 200 posts | Cheap merge for celebrities |
| Engagement events | Event log, then feature store | `user_id` | Ranking features | Streaming aggregation |
| Media | Object storage + CDN | Content hash | Get by URL | Large objects off the hot path |

## 6. High-level design

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

The write path (post, fan-out) and the read path (feed, rank, hydrate) share
only the stores.

```mermaid
flowchart LR
  client([Client]) --> gw[API gateway]
  gw --> postsvc[Post service]
  postsvc --> postdb[(Post store)]
  postsvc --> media[(Object storage + CDN)]
  postsvc --> log[[Post events]]
  log --> fan[Fan-out workers]
  fan --> fgraph[(Follow graph)]
  fan --> feeds[(Feed cache)]
  gw --> feedsvc[Feed service]
  feedsvc --> feeds
  feedsvc --> celeb[(Pull-author recent posts)]
  feedsvc --> rank[Ranking service]
  rank --> fs[(Feature store)]
  feedsvc --> hydr[Hydration: post and user caches]
  hydr --> postdb
  client --> media
```

| Component | Responsibility | Scales by |
|---|---|---|
| Post service | Validate, store posts, emit events, handle deletes | Stateless replicas |
| Fan-out workers | Read follower lists in pages, insert into active followers' feeds | Consumer partitions |
| Feed service | Merge pushed and pulled candidates, filter, call ranking, paginate | Stateless replicas |
| Ranking service | Score candidates with features | Model replicas, GPU or CPU |
| Hydration | Batch fetch post bodies, author profiles, counters | Cache cluster size |
| [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 Feed read (critical path)

The feed service reads the pushed feed and pulled candidates in parallel, then
ranks and hydrates only the page it returns.

```mermaid
sequenceDiagram
  participant C as Client
  participant F as Feed service
  participant Q as Feed cache
  participant G as Follow graph
  participant P as Pull-author posts
  participant R as Ranking service
  participant H as Hydration caches
  C->>F: GET /v1/feed?cursor=...
  par pushed candidates
    F->>Q: top 500 entries for user
    Q-->>F: post IDs + base scores
  and pulled candidates
    F->>G: pull authors followed by user
    G-->>F: author IDs (cached)
    F->>P: recent posts per author
    P-->>F: post IDs
  end
  F->>F: merge, dedupe, filter blocked and deleted
  F->>R: score up to 500 candidates
  R-->>F: scores
  F->>F: store ranked snapshot, slice page 1
  F->>H: hydrate 20 posts and authors
  H-->>F: bodies, media URLs, counts
  F-->>C: items + nextCursor
```

If ranking times out (budget 60 ms), the service falls back to `base_score`
order (recency with author affinity) so the user always gets a page.

### 7.2 Hybrid fan-out

- An account becomes a **pull author** when `follower_count` exceeds
  *Example:* 10 000, with hysteresis (back to push below 8 000) to avoid flapping.
- **Push**: on a post event, workers page through the author's followers
  (1 000 per batch) and insert `(post_id, author_id, base_score)` into each
  **active** follower's sorted set, trimming to $L$ entries.
- **Pull**: on read, the feed service fetches the latest posts of each pull
  author the user follows. Most users follow few such accounts; cap at
  *Example:* 200 pull authors per user, chosen by affinity.
- **Inactive users** (no session for 14 days) are skipped during fan-out. When
  they return, the feed is rebuilt from followees' recent posts in the
  background, and the first page is served by a pull-only path meanwhile.

### 7.3 Feed cache layout

Each user's feed is a sorted set keyed by `feed:{user_id}`, scored by a
time-decayed base score so trimming keeps the most relevant recent items.
Entries hold IDs only; bodies live in the post cache so edits and deletes take
effect without rewriting feeds. A deletion event adds the post to a
short-lived tombstone set consulted by the filter step, and a background job
removes the entries.

### 7.4 Ranking pipeline

| Stage | Input | Output | Budget |
|---|---|---|---|
| Candidate generation | Pushed feed + pulled authors | Up to 500 candidates | 20 ms |
| Filtering | Blocks, mutes, deleted, already seen in last 24 h | Valid candidates | 5 ms |
| Scoring | Features: author affinity, post age, engagement velocity, media type | Predicted engagement score | 60 ms |
| Re-ranking | Diversity rules (max 2 consecutive posts per author), freshness boost | Final order | 5 ms |

A typical score combines predicted actions with weights and an age decay:

$$
\text{score} = \left(\sum_{a} w_a \, P(a \mid u, p)\right) \times e^{-\lambda \, t_{age}}, \qquad \lambda = \frac{\ln 2}{h}
$$

where $h$ is the half-life, for example 6 hours. Weights $w_a$ are set by
product goals and reviewed for integrity effects (for example down-weighting
predicted outrage).

### 7.5 Pagination and consistency model

- The first page call stores the ranked list of up to 500 IDs as a **snapshot**
  for 30 minutes; the cursor encodes `snapshot_id` and offset. Later pages read
  the snapshot, so new posts never shift items and cause duplicates or skips.
- New content is surfaced through `new-count`, and pulling to refresh creates
  a new snapshot.
- **Consistency**: feeds are eventually consistent. A post appears in pushed
  feeds within seconds; follows take effect on the next snapshot; unfollows
  and blocks are enforced by the filter step at read time, so they apply
  immediately even before fan-out entries are cleaned up.
- **Read-your-writes**: the author's own new posts are injected into their
  first page from the post service, independent of fan-out lag.

### 7.6 Media

Clients upload media directly to object storage with pre-signed URLs; a
processing job produces resized WebP/AVIF variants and video renditions, and
the post becomes visible only when its media are ready. Feed items reference
CDN URLs with content-hash names so they can be cached for a year.

## 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 |
|---|---|---|
| Fan-out volume | Skip inactive users; pull authors above threshold | *Example:* ~1 million inserts/s with 512 worker partitions |
| Feed cache memory | Length cap, IDs only, active users only | Memory per node; reshard by `user_id` hash |
| Hot posts | Post body cache with request coalescing | One viral post at ~500 000 reads/s via local caches |
| Ranking cost | Score at most 500 candidates; cache scores per snapshot | Model latency budget 60 ms |
| Follow graph reads | Followers paged; pull-author list cached per user | [Limit] |

Latency budget for a first feed page (target p99 250 ms):

| Step | Budget |
|---|---|
| Gateway and auth | 15 ms |
| Candidate fetch (parallel) | 30 ms |
| Merge and filter | 10 ms |
| Ranking | 60 ms |
| Hydration | 40 ms |
| Serialisation | 10 ms |
| Headroom | 85 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 |
|---|---|---|---|
| Feed cache node lost | Affected users get empty pushed feeds | Cache error rate | Pull-only fallback for those users; rebuild from followees in background |
| Fan-out backlog | New posts appear late | Consumer lag | Autoscale workers; prioritise recent posts; NFR-03 alert |
| Ranking service down | Lower quality order | Error and timeout rate | Fallback to `base_score` order |
| Post store partition unavailable | Some posts cannot hydrate | Hydration miss rate | Drop unhydratable items from the page rather than fail it |
| Deletion event lost | Deleted post still shown | Deletion probe | Filter checks post `state` during hydration as a second guard |
| [Failure] | [Impact] | [Signal] | [Mitigation] |

### 9.2 Security and abuse

| Threat | Control |
|---|---|
| Private posts leaking to non-followers | Visibility checked at hydration against current follow and audience settings |
| Spam accounts mass-following to harvest content | Follow rate limits, new-account restrictions |
| Coordinated inauthentic engagement gaming ranking | Engagement features weighted by account trust; anomaly detection |
| Harmful content amplification | Integrity classifiers feed a demotion signal; removal events honoured within 60 s |
| [Threat] | [Control] |

### 9.3 Observability

| Signal | Metric or log | Alert threshold |
|---|---|---|
| Feed latency | p50, p99 | p99 above 250 ms for 10 min |
| Freshness | Canary post visible in canary follower feed | Above 30 s |
| Fallback rate | Pages served without ranking | Above 2% for 10 min |
| Empty feeds | Pages with zero items for users with followees | Above 0.1% |
| Fan-out lag | Consumer lag | Above 60 s |

### 9.4 Cost estimate

| Item | Unit cost | Quantity | Monthly cost |
|---|---|---|---|
| Feed cache memory | [$/GB-month] | *Example:* 9.6 TB | [$] |
| Fan-out and feed services | [$/node-month] | [n] | [$] |
| Ranking inference | [$/million scores] | *Example:* ~500 candidates x first pages | [$] |
| Post store | [$/TB-month] | *Example:* 66 TB per year of growth | [$] |
| Media storage and CDN egress | [$/TB] | *Example:* 180 TB/month new media | [$] |
| **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 |
|---|---|---|---|---|
| Fan-out | Pure push; pure pull; hybrid | Hybrid | Fast reads for most users, bounded write cost for celebrities | Two candidate paths, threshold tuning |
| Feed contents | Full post copies; IDs only | IDs only | Small memory, edits and deletes apply instantly | Hydration step on every read |
| Inactive users | Fan out to all; skip inactive | Skip | Large saving in writes and memory | Slower first load after a long absence |
| Pagination | Offset; time cursor; snapshot cursor | Snapshot cursor | No duplicates or skips while ranking changes | Snapshot storage, 30 min expiry |
| Ranking | Chronological; single heavy model; two-stage | Two-stage, light scorer | Fits latency budget, easy fallback | Less accuracy than a heavy model on all candidates |
| [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 | Push fan-out, chronological feed, cursor pagination | p99 under 250 ms in shadow traffic | YYYY-MM-DD |
| M2 | Pull authors and hybrid merge | Celebrity post visible to all followers in under 10 s | YYYY-MM-DD |
| M3 | Ranking v1 behind experiment | Positive or neutral on guardrail metrics in A/B test | YYYY-MM-DD |
| M4 | Inactive-user skip and rebuild | Memory down 30% with no freshness regression | YYYY-MM-DD |

### 11.2 Rollout and rollback

[*Example:* Dual-write feeds for two weeks while the old query path serves
traffic; compare outputs on 1% of requests. Switch reads per percentage of
users; rollback switches reads back while dual writes continue.]

### 11.3 Risks

| ID | Risk | Likelihood | Impact | Mitigation | Owner |
|---|---|---|---|---|---|
| R-01 | *Example:* Threshold flapping causes duplicate or missing posts | Medium | Medium | Hysteresis; dedupe by `post_id` at merge | [@name] |
| R-02 | *Example:* Ranking change harms wellbeing metrics | Medium | High | Guardrail metrics in every experiment; integrity review | [@name] |
| R-03 | *Example:* Memory cost exceeds budget | Medium | Medium | Reduce $L$; compress entries | [@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:* Should muted keywords be filtered server side? | [@name] | YYYY-MM-DD |
| OQ-02 | *Example:* What half-life works per content type? | [@name] | YYYY-MM-DD |
| [OQ-nn] | [Question] | [@name] | YYYY-MM-DD |

### 12.2 Glossary

| Term | Meaning |
|---|---|
| Fan-out on write (push) | Copy a post reference into followers' feeds when it is created |
| Fan-out on read (pull) | Fetch followees' posts when the feed is requested |
| Pull author | Account whose posts are fetched at read time because of follower count |
| Hydration | Replacing IDs with full post and author data |
| Snapshot cursor | Cursor pointing at a stored ranked list for stable pagination |
| [Term] | [Meaning] |

### 12.3 References

| Reference | Use |
|---|---|
| RFC 9110 HTTP Semantics | API methods and caching |
| RFC 9457 Problem Details for HTTP APIs | Error format in 4.3 |
| [Internal ranking model card] | Features and evaluation for 7.4 |

## Standard and licence note

Based on common industry practice for social feed system design. This is a
reference design to adapt: the numbers are illustrative assumptions, not
measurements. Original text written for this collection. Template licence: MIT.
