Forge64 logo
All app types

Social network architecture

Social network architecture: the decisions that matter

The architecture of a social network comes down to 4 choices: hybrid fan-out: push for normal accounts, pull for celebrities, sharded, batched engagement counters, keyset (cursor) pagination everywhere and SSE plus polling fallback in phase 1, websocket tier later. Each is explained below with the alternatives that were rejected and what you accept by choosing it.

Last checked

Hybrid fan-out: push for normal accounts, pull for celebrities

Situation: A pure push model writes one row per follower per post, so a 5M-follower account causes 5M writes per post; a pure pull model joins the whole follow graph on every timeline read and cannot hit a latency budget at 100k+ users.

Choice: Precompute timeline_entries by a queued, resumable, batched job for authors under FANOUT_CELEBRITY_THRESHOLD followers; for celebrities skip the write and merge their recent posts into the reader's page at read time from a capped set of followed celebrities.

  • Rejected: Pure fan-out-on-write: fast reads but unbounded write amplification and cost for large accounts.
  • Rejected: Pure fan-out-on-read: no write cost but read cost grows with followings and breaks the p95 budget.
  • Rejected: Third-party feed service: fast to start but per-activity pricing is an uncapped surprise cost and removes control.
  • Consequence: Read path does one indexed read plus a bounded merge.
  • Consequence: Threshold uses hysteresis so accounts do not flap between modes.
  • Consequence: Per-post fan-out volume is capped and fails over to pull mode.
  • Consequence: Unfollow and block cleanup is lazy; correctness is enforced by read-time filters.

Sharded, batched engagement counters

Situation: A viral post receives thousands of likes per second; incrementing one row serialises every writer on a row lock and makes the post the system's hottest row.

Choice: Likes are inserted idempotently into a likes table and counted into N shard rows chosen by hash(user_id); a cron rollup sums touched shards into post_counters, the only table readers use.

  • Rejected: Single counter row with UPDATE ... SET n = n + 1: simple but lock contention on hot posts.
  • Rejected: count(*) over likes at read time: exact but O(n) per request.
  • Rejected: Per-event synchronous counter in an in-memory store: fast but an extra stateful tier, deferred.
  • Consequence: Counts are eventually consistent within the rollup interval (60 s, 10 s for hot posts).
  • Consequence: Shard count is raised for hot posts without a migration.
  • Consequence: A nightly reconcile detects and repairs drift.

Keyset (cursor) pagination everywhere

Situation: Feeds are append-heavy: OFFSET pagination duplicates and skips items as new posts arrive and gets slower with depth.

Choice: Every list pages by an opaque signed cursor over an indexed (created_at, id) tuple with a capped page size.

  • Rejected: OFFSET/LIMIT: simple but unstable and O(offset).
  • Rejected: Page tokens stored server-side: extra state with no benefit.
  • Consequence: Stable pages under concurrent writes.
  • Consequence: No random jump to page N, accepted for feeds.
  • Consequence: Every list query needs a matching composite index (enforced in the data model).

SSE plus polling fallback in phase 1, websocket tier later

Situation: Users expect live notifications and DMs, but a websocket tier adds sticky routing and a pub/sub dependency the locked stack does not include.

Choice: A single capped SSE stream per client carrying only signals, fed by LISTEN/NOTIFY, with 30 s polling as the fallback; typing and presence are deferred.

  • Rejected: Polling only: simple but wasteful at scale.
  • Rejected: Websockets from day one: lowest latency, but a new stateful tier before it is needed.
  • Consequence: Latency of a few seconds is acceptable for notifications and DMs.
  • Consequence: Connection count per node is bounded by SSE_MAX_CONNECTIONS_PER_USER and node limits.
  • Consequence: A migration path to a websocket tier is documented with a trigger.

How the data is indexed

Indexes are chosen from the questions the app asks most often. Each line below names a table and the question its main index answers.

  • profiles: unique (handle) — profile lookup by @handle and handle uniqueness.
  • follows: unique (follower_id, followee_id) — follow is idempotent and double clicks cannot duplicate.
  • posts: (author_id, created_at DESC) — profile page and the celebrity pull-merge at timeline read time.
  • post_media: (post_id, position) — render attachments in order.
  • timeline_entries: (user_id, post_created_at DESC, post_id DESC) — home timeline keyset page, one range read.
  • likes: unique (user_id, post_id) — like is idempotent and 'did I like it' is one index probe.
  • post_counter_shards: unique (post_id, shard) — upsert target for sharded increments.
  • post_counters: (rolled_up_at) — rollup refreshes the stalest hot counters first.

What Forge64 provides underneath

Forge64 matches your description to one of 12 reference architectures, writes the plan and data model into the project, generates the app, and runs a quality gate over the code before you see it. Each app gets its own Postgres database and sign-in with no per-user fee. Hosting on Forge Cloud and Forge Payments are rolling out.

This is a reference design for the category, not a promise that every item is generated today. Built-ins such as email, background jobs, file uploads and search are coming soon, so anything here that needs them is something you add yourself for now.

FAQ

What is the most important architecture decision for a social network?
Hybrid fan-out: push for normal accounts, pull for celebrities. Precompute timeline_entries by a queued, resumable, batched job for authors under FANOUT_CELEBRITY_THRESHOLD followers; for celebrities skip the write and merge their recent posts into the reader's page at read time from a capped set of followed celebrities.
Do I have to make these decisions myself?
No. Forge AI plans them from your description and writes the decisions into the project as architecture records you can read and change.
What do the alternatives cost me?
For example, for "Sharded, batched engagement counters": Single counter row with UPDATE ... SET n = n + 1: simple but lock contention on hot posts. count(*) over likes at read time: exact but O(n) per request.

Build your app with Forge64

One prompt to a running app with auth, database, hosting and payments. Start free, no credit card required.

Start building for free