系統設計系列文10:Media Pipeline 上傳一張圖片,中間發生了什麼事?
昨天把 CDN 接上了 PokeThreads,圖片跟影片不再經過 Application Server,直接由 CDN 搭配 Object Storage 提供服務:

但這裡其實跳過了一個環節:
Pikachu 上傳一張 20MB 的原始照片,到這張照片變成 CDN 上那張輕巧的圖片之間,中間到底發生了什麼事?
總不可能不處理 20MB 的原始檔案,直接拿去讓全世界的人下載吧。
技術相關的心得
檢視所有標籤昨天把 CDN 接上了 PokeThreads,圖片跟影片不再經過 Application Server,直接由 CDN 搭配 Object Storage 提供服務:

但這裡其實跳過了一個環節:
Pikachu 上傳一張 20MB 的原始照片,到這張照片變成 CDN 上那張輕巧的圖片之間,中間到底發生了什麼事?
總不可能不處理 20MB 的原始檔案,直接拿去讓全世界的人下載吧。
前面幾天,PokeThreads 的架構已經逐漸長成分散式系統的樣子,但焦點始終放在文字貼文要怎麼被處理跟讀取,現在想加入一個很自然的新功能:讓使用者也能上傳圖片與影片。
新問題馬上出現:這些圖片跟影片應該放在哪裡?
目前 PokeThreads 的架構已經有多台 Server 和 Primary / Replica Database:

Read 壓力被 Replica 分攤掉了,但仔細想想
這些 Read 真的每一次都需要重新查詢 Database 嗎?
假設現在有 100 萬個使用者,很多人一打開 PokeThreads 第一件事就是看 Feed。回想 Day 3 的內容,光是組出一份 Feed,後端就要做:
找到 Following
↓
找到 Posts
↓
排序
↓
Pagination
如果每一次 Request 都重新跑一次這整套流程:
Request 1 → Database
Request 2 → Database
Request 3 → Database
Request 4 → Database
...
代表大量 Request 其實都在請 Database 重複做很類似的事情。
但有些資料根本不需要每次都重新算,例如:
這些資料在一段時間內,可能會被大量使用者反覆讀取。那能不能把這些常用資料,暫時放在一個更適合大量讀取的地方?
這就是 Cache(快取)要解決的問題。
前幾天把 Application Server 從一台變成多台,也把 Session 從 Server Memory 搬到共用的 Session Store:
瀏覽器 -> Load Balancer -> Server(多台,Stateless)-> Database
你可注意到了:Server 變多了,Database 卻還是只有一台。
不管前面有幾台 Server 在分攤流量,最後所有的讀寫都會匯聚到同一個 Database。
Application Layer 雖然順利做到了 Horizontal Scaling,Database 反而變成了新的瓶頸。
這也是大型系統很常出現的現象:解決了一個瓶頸,下一層往往會冒出新的瓶頸。
昨天我們把 Application Server 從一台擴展成多台,靠 Load Balancer 分攤流量:

這解決了單台 Server 的容量問題,但當時刻意跳過了一個細節:
如果同一個使用者的 Request,被分配到不同的 Server,這些 Server 還認得出他是誰嗎?
回想 Day 2,我們是把 Session 直接存在 Server 的 Memory 裡:
Server
├── Application
└── Session(存在 Memory 裡)
├── Pikachu
├── Charmander
└── Squirtle
這種把使用者狀態保存在 Server 本機的做法,稱為 Stateful Server。在只有一台 Server 的時候完全沒問題,但現在情況不一樣了。
這幾天我們把最陽春的 PokeThreads 做出來了,也解決了 Feed 該回傳什麼內容的問題,但整個架構其實還停留在最原始的狀態:
瀏覽器 -> Server -> Database
只有一台 Server,所有使用者的 Request 都打到同一個地方。當時的假設是使用者只有 100 人,這台 Server 綽綽有餘,但如果使用者從 100 人變成 10 萬人、100 萬人呢?
即使 Database 還撐得住,這一台 Server 本身的運算能力終究有極限,於是我們要面對一個很基本、卻也很重要的問題:
當一台 Server 不夠用了,我們該怎麼辦?
昨天把 Feed 的核心邏輯做出來了,GET /feed 已經能正確回傳 Pikachu 追蹤的人發的貼文,但那時候刻意跳過了一個問題:這支 API 到底該回傳什麼形狀的資料?
目前的做法很直覺,後端把 Post 撈出來,整包塞進 JSON 回傳:
{
"posts": [
{
"id": 101,
"author_id": 42,
"content": "Hello PokeThreads",
"created_at": "2026-01-01T00:00:00Z"
}
],
"next_cursor": "abc123"
}
這種「一個資源、一個固定形狀」的 API 設計方式,就是 REST(Representational State Transfer)。
今天想討論的是:RESTful API 會在哪裡開始卡住?
昨天做出了最陽春的 PokeThreads,但故意跳過了一個問題:
GET /feed
這支 API 收到 Request 之後,到底該回傳哪些貼文?
上一篇估算出來的流量其實非常小,每秒寫入大概 1~2 次、讀取大概 100 多次,這種規模不需要任何花俏的架構,先做出一個 「堪用」 的版本就好。
這也是這個系列一路會遵守的原則:
不要一開始就把系統蓋大,而是先用最簡單的方式解決問題,避免過度工程(Over-engineering)。
這系列文章想陪大家一起做一件事:從零開始設計一個名為 PokeThreads 的社群平台(參考自 Meta 的 Threads),並且讓它隨著使用者變多、流量變大,一步一步演化成真正的分散式系統。