雲端儲存 (cloud storage) 已成為跨裝置檔案協作的基礎。Google Drive、Dropbox、Microsoft OneDrive 與 Apple iCloud 等服務透過將雲端儲存無縫整合進日常工作流程,徹底改變了檔案管理方式。本章將探討 Google Drive 行動客戶端的設計。
Figure 1 展示了 Google Drive 的行動介面,說明複雜需求如何轉化為簡潔、易用的體驗。

Step 1: 理解問題並確立設計範圍#
以下是與面試官的對話範例:
- Candidate: 我想先界定 Google Drive 行動客戶端的核心功能。基本功能應包含上傳與下載檔案 (並可取消進行中的操作)、建立與刪除資料夾以組織內容,並支援常見的檔案類型 (照片、影片、文件)。
- Interviewer: 可以。既然是行動 app,網路可靠性是個問題。讓上傳支援可恢復 (resumable),讓使用者在連線中斷後能接續上傳。
- Candidate: 沒問題。關於檔案大小,是否要設定上限?
- Interviewer: 就設為 10 GB。
- Candidate: 使用者可能會多次更新同一檔案,是否需要支援版本歷史 (file versioning)?
- Interviewer: 要,版本歷史是雲端儲存的重要功能。
- Candidate: 使用者常從多裝置存取 Google Drive,我們要設計單一使用者多裝置,還是多使用者 (含檔案分享)?
- Interviewer: 聚焦在單一使用者多裝置即可,暫不處理分享與權限管理。
- Candidate: 非功能需求方面,應包含大規模使用者的可擴展性,以及在網路不佳時的資料持久性?
- Interviewer: 是的。規模方面,設計支援約 1,000 萬 DAU。
- Candidate: 最後一個問題,Google Drive 的 Docs/Sheets 即時協作是否要涵蓋?
- Interviewer: 將 Docs/Sheets 當作一般檔案處理,不包含即時協作。
Requirements#
根據討論,以下是 Google Drive app 的核心功能需求:
- 使用者可上傳與下載檔案 (最大 10 GB),並可取消進行中的操作。
- 使用者可建立與刪除資料夾以組織內容。
- 使用者可檢視並還原檔案的歷史版本。
- 支援可恢復上傳 (resumable uploads)。
- 檔案可跨使用者的多個裝置同步。
非功能需求 (non-functional requirements):
- 可擴展性 (scalability):支援 1,000 萬 DAU、龐大資料量,並涵蓋各種網路條件與裝置類型。
- 資料持久性 (data durability):即使網路不佳,仍能可靠存取已下載的檔案。
範圍外 (out of scope):
- 即時協作 (Docs/Sheets 多人編輯)。
- 檔案分享與權限管理。
UI sketch#
Figure 2 展示 Google Drive app 的核心畫面:
- Files List screen (中):app 的主要入口,瀏覽與管理檔案與資料夾。
- Upload Modal (左):上傳檔案的專注介面。
- File Revisions screen (右):顯示特定檔案的版本歷史以供檢視與還原。
Step 2: API 設計#
定義好需求與 UI 後,接著設計連接行動客戶端與後端的 API。
網路協定 (Network protocol)#
Google Drive app 主要是客戶端驅動 (client-driven) 的操作,例如上傳、下載、組織資料夾等。對這類行為而言,HTTP + REST API 是直接且廣泛採用的方案。
但為了處理即時通知 (例如共享檔案被更新),我們會搭配 WebSockets 或 Server-Sent Events (SSE)。由於這類通知只是告知變更訊號、而非傳輸實際檔案內容,兩者皆可勝任。
本設計採用 REST,但在此規模下 gRPC + Protocol Buffers 也是不錯的選擇,具備以下優勢:
- 二進位編碼 (binary encoding) 提升效能。
- 強型別 (strong type safety) 保障 API 可靠性。
- 高效的雙向串流 (bidirectional streaming),適合即時檔案更新。
熟悉 gRPC 的讀者可自行替換協定,本章討論的模式不受協定選擇影響。
Endpoints 與資料模型#
接著定義核心 API 端點與資料結構,從最基本的檔案與資料夾開始。
Files and folders#
由於 Google Drive 的資料夾本質上就是帶有特殊類型的檔案,我們將兩者的管理統一在同一組端點下。
- List files:
GET /v1/files?parentId={fileId}&page={page}&limit={limit}- Response: 200 OK with
GetFilesResponse。 - 選擇性 query parameter:
parentId(預設為 root)。
- Response: 200 OK with
- Get file details:
GET /v1/files/{fileId}→ 200 OK withFile。 - Download file:
GET {downloadUrl}(由File物件提供)。 - Create file or folder:
POST /v1/files(Body: metadata,如 name、mimeType、parentId)。 - Delete file or folder:
DELETE /v1/files/{fileId}。 - Update file/folder metadata:
PATCH /v1/files/{fileId}。
資料模型 (Kotlin / Swift)
data class GetFilesResponse(
val files: List<File>,
val pagination: PaginationMetadata,
)
data class File(
val id: String,
val name: String,
val mimeType: String,
val size: Long,
val downloadUrl: String?,
val checksum: String?,
// ...
)struct GetFilesResponse {
let files: [File]
let pagination: PaginationMetadata
}
struct File {
let id: String
let name: String
let mimeType: String
let size: Int64
let downloadUrl: String?
let checksum: String?
// ...
}這些端點遵循標準 REST 慣例,所有請求都需要:
- JWT bearer token 於 Authorization header 中進行驗證。
- JSON content type (
application/json)。端點可能回傳 4xx (client error:驗證失敗、資源不存在、格式錯誤) 或 5xx (server error)。
File revisions#
為了支援檢視與還原檔案歷史,我們加入對應的版本端點:
- List revisions:
GET /v1/files/{fileId}/revisions?page={page}&limit={limit}。 - Get revision details:
GET /v1/files/{fileId}/revisions/{revisionId}。 - Restore revision:
POST /v1/files/{fileId}/revisions/{revisionId}/restore。
FileRevision 資料模型
data class FileRevision(
val revisionId: String,
val fileId: String,
val mimeType: String,
val size: Long,
val modifiedAt: String,
val downloadUrl: String,
val checksum: String,
)struct FileRevision {
let revisionId: String
let fileId: String
let mimeType: String
let size: Int64
let modifiedAt: String
let downloadUrl: String
let checksum: String
}這樣客戶端就能輕鬆檢視與還原先前版本。
Step 3: 高階客戶端架構#
定義好 API 後,接著聚焦於行動客戶端的架構。目標是打造模組化、可擴展的設計,既能支援現有功能,也為未來成長保留彈性。
以下先看外部依賴,再進入客戶端本身,如 Figure 3 所示。
External server-side components#
- Backend:核心系統,負責檔案儲存、metadata 與商業邏輯,提供 REST API (上傳/刪除) 與 SSE/WebSockets (即時更新)。
- CDN:將靜態檔案快取至離使用者更近的節點,降低下載延遲。
- API Gateway:客戶端進入後端的單一入口,處理驗證、路由、安全性。
Client architecture#
客戶端分為兩個核心層級:UI layer 與 Data layer,確保程式碼乾淨、可測試、易維護。
- UI layer 包含三個主要畫面:
- File List Screen:瀏覽內容。
- File Upload Modal:新增檔案。
- File Revisions Screen:檢視檔案歷史。
- Data layer 透過 Files 與 Revisions Repositories 管理核心功能,搭配:
- Network Dispatcher:處理與後端的 HTTP 通訊。
- Revisions Remote Data Source:透過 SSE/WebSockets 監聽即時更新。
這個架構為 Google Drive app 打下穩固基礎。接下來深入探討系統中較複雜的面向。
Step 4: Design deep dive#
以下聚焦於 Google Drive 系統中幾個值得關注的主題:資料儲存、可恢復上傳、版本歷史與資料加密。
Data storage#
在 Google Drive 這類雲端儲存 app 中,客戶端需同時管理 metadata (檔名、資料夾階層、版本資訊) 與已下載的檔案內容。以下說明如何在本機有效且安全地儲存兩者。
File metadata storage#
app 會頻繁處理兩類資料:
- 瀏覽目錄時的檔案與資料夾 metadata (
File)。 - 追蹤變更與即時更新的版本資訊 (
FileRevision)。
這類 metadata 需要頻繁查詢與更新,因此關聯式資料庫 (relational database) 是理想選擇:
- 結構化查詢:可快速執行「列出檔案」「尋找版本」等操作。
- 關聯性:透過
parentId維護檔案與資料夾的階層完整性。 - 可擴展性:metadata 增長時仍能維持效能。
Database design#
File 與 FileRevision 資料模型結構清晰,可自然對應到資料庫表格,便於跨階層查詢、追蹤版本關係與維持資料一致性。
Figure 4 顯示更新後的架構,每個資料表都有獨立的 DAO (Data Access Object) 元件。
Caching approach#
為了平衡速度與儲存空間,我們將 metadata DB 上限設為可調整值 (例如 200 MB),並採用 LRU (Least Recently Used) 驅逐策略:
- 保留最近存取過的 metadata。
- 清除較舊的項目。
- 單筆 metadata 很小,故可快取大量資料卻佔用極少空間。
Managing downloaded files#
為了支援離線存取,使用者可下載檔案,這些檔案需安全且便於管理地儲存在本機。
app 的私有內部儲存 (private internal storage) (iOS/Android 沙盒) 是最佳選擇:
- 檔案受保護,不被外部存取。
- app 可管理檔案 (刪除、更新) 而不影響其他 app 或使用者目錄。
- app 內直接存取,必要時可匯出至共享儲存。
How it works#
- 檔案儲存在特定資料夾 (例如 Android 的
/data/user/0/com.google.android.apps.drive/files/downloads/<fileId>),使用fileId作為唯一檔名避免衝突。 - 下載前 app 會檢查可用空間,若不足則提示使用者。
Industry insights:Box Drive 支援將資料夾標記為離線使用。被選取後,內容會於背景自動下載並儲存至本機供離線使用。
Resumable file uploads#
可恢復上傳 (resumable uploads) 是行動雲端儲存 app 的基石,尤其適合大檔案與不穩定的網路。它提供幾項關鍵優勢:
- 使用者可暫停並恢復上傳,無需重新開始。
- 連線中斷後可無縫復原。
- 可依網路或電量條件管理上傳,提升效率與體驗。
Upload types#
許多主流 app 使用 resumable uploads 處理大檔案,包含 Google Photos、X (Twitter)、Box、Dropbox、Microsoft OneDrive 等。
以 Google Drive 為例,其文件定義了三種上傳方式:
- Simple upload (
uploadType=media):傳輸小於 5 MB 的媒體檔,不含 metadata。 - Multipart upload (
uploadType=multipart):傳輸小於 5 MB 的檔案,並於單一 request 內包含 metadata。 - Resumable upload (
uploadType=resumable):適用於大於 5 MB 的大檔案,或網路不穩的行動情境。Google 也建議將其用於大多數應用,因為它同樣適用於小檔案,僅多出一次 HTTP request 的成本。
以下聚焦於可恢復上傳的實作。
High-level steps#
- Initiate:client 請求建立 upload session,取得唯一 upload ID 與 session 詳細資訊。
- Upload chunks:檔案以較小的 chunk 分次上傳。若連線中斷,從最後成功的 chunk 恢復。
- Complete:所有 chunk 上傳完成後,server 最終化檔案並可選擇性驗證完整性。
各家實作的主要差異在端點結構:
- 單一統一端點 (single unified endpoint):X (Twitter) 與 Google (Drive/Photos) 採此方式,透過 header 或 query parameter 管理 metadata。例如 Google Photos:
POST /files?uploadType=resumable&uploadId={uploadId}- Header:
X-Goog-Upload-Command=[start|upload|finalize]、X-Goog-Upload-Offset={bytes_offset}
- 多端點 (multiple endpoints):Dropbox、Box 將各步驟拆到不同端點,例如 Dropbox:
POST /files/upload_session/startPOST /files/upload_session/appendPOST /files/upload_session/finish
兩種方式皆可,以下以多端點為例詳細說明。
Implementation details#
Initiate upload#
POST /v1/upload?uploadType=resumable
UploadRequest / UploadResponse 資料模型
data class UploadRequest(
val requestId: String,
val name: String,
val mimeType: String,
val parentId: String,
val size: Long,
)
data class UploadResponse(
val uploadId: String,
val totalChunks: Int,
val chunksProcessed: Int,
val chunkSize: Long,
val uploadUrl: String,
val uploadExpiresAt: String,
)struct UploadRequest {
let requestId: String
let name: String
let mimeType: String
let parentId: String
let size: Int64
}
struct UploadResponse {
let uploadId: String
let totalChunks: Int
let chunksProcessed: Int
let chunkSize: Int64
let uploadUrl: String
let uploadExpiresAt: String
}UploadResponse 透過追蹤「檔案如何被切分」「已上傳了什麼」「下一個 chunk 要從哪開始」,指引整個上傳流程,無論是新上傳或恢復中的上傳。
Upload chunks and verify#
PUT /v1/upload/{uploadId}/chunk
Content-Range: bytes start-end/total
<binary chunk data>- Body:chunk 的原始二進位資料。
- Response:最後一個 chunk 回傳
201 Created,否則回傳308 Resume Incomplete搭配UploadResponsepayload。
Content-Range header 告知後端目前傳送哪一段,當最後一段抵達時,後端組合整個檔案並回傳完成訊息,client 依回應碼驗證成功。
Resume upload#
GET /v1/upload/{uploadId}- Description:取得 upload session 資訊。
- Response:
200 OKwithUploadResponse。
client 從 chunksProcessed 之後的下一個 chunk 繼續上傳。若 session 已過期,則需重新發起上傳。
Cancel upload#
DELETE /v1/upload/{uploadId}允許 client 中止上傳並釋放 server 資源。
Mobile implementation challenges#
實作 resumable uploads 時需要面對幾個平台特有的挑戰。
Managing background tasks#
- 上傳應在背景執行,避免阻塞 UI thread。
- 行動 OS 可能隨時終止背景任務,為此需將待完成的
UploadResponse持久化到磁碟。 - app 啟動時檢查未完成上傳並恢復,同時監控電量與網路狀態。
Limited storage and permissions#
- 儲存:上傳前會暫時快取檔案。空間不足時應讓使用者清除上傳快取、移除已下載的檔案或檢視儲存使用量。
- 檔案存取:使用系統 file picker 取得暫時讀取權限,或請求儲存權限。若使用者拒絕,應停用上傳功能並引導至系統設定。
Error handling and recovery#
後端處理了大部分複雜度 (如決定 chunkSize 與追蹤進度),但 client 仍需優雅處理錯誤:
- 連線問題:儲存當前狀態,恢復連線後從最後成功的 chunk 繼續,並顯示「Connection lost, attempting to resume」之類的訊息。
- 無效狀態:若偵測到
uploadUrl過期、回應不一致 (chunksProcessed > totalChunks)、或uploadExpiresAt已到期,應重新發起上傳。
使用指數退避 (exponential backoff) 重試,逐步增加重試間隔,避免短時間內大量重試壓垮 server。
User experience and feedback#
即使使用者重啟 app 也應清楚看到上傳進度。client 可以:
- 以
chunksProcessed / totalChunks計算進度。 - 顯示有意義的進度指示器。
- app 在背景時透過系統通知告知狀態。
Architecture changes in the diagram#
Figure 6 展示加入新元件後的架構:
- UI layer:新增 File Picker 與 File Permissions Helper,處理使用者與檔案系統的互動。
- Data layer:新增 File Splitter 切割大檔案,以及 File Uploader Repository 與 File Uploader Service 管理背景上傳流程。
Version history#
版本歷史讓使用者在需要時能還原至先前版本。以下討論如何上傳新版本,以及如何處理變更重疊時的衝突。
Uploading a new version of an existing file#
當使用者修改並上傳檔案時,app 需有效處理新版本。主流做法有兩種:full copy upload 與 block-level sync。
Full copy upload#
每次變更都上傳整個檔案。
- Client:無論改動大小,都傳送整份檔案。
- Backend:每個版本都完整獨立儲存。
- 優點:
- 實作簡單,無需追蹤細部變更。
- 每個版本完整,容易還原。
- 缺點:
- 儲存量大,大檔案多版本累積尤甚。
- 耗費頻寬。
Google Drive、Box、OneDrive、iCloud 等多採此法,因為簡單可靠。
Block-level sync#
Block-level sync 更為精巧,只上傳變更的部分。
- Client:將檔案切成 block,偵測差異,只傳送變動的 block。
- Backend:維護 block registry 並在需要時重組版本。
- 優點:節省頻寬與儲存,適合大檔案的小幅編輯。
- 缺點:
- 需進階邏輯管理並重組 block,複雜度高。
- 兩端皆需額外運算資源。
Dropbox 透過 Broccoli 編碼系統與 block sync 協定開創了此技術。
Managing file conflicts#
檔案衝突發生在多裝置同時修改同一檔案、版本不一致時。常見情境:
- 使用者在某裝置離線編輯,同時另一裝置修改了同一檔案。
- 兩裝置幾乎同時上傳同一檔案的變更。
- 同步中斷導致檔案處於不一致狀態。
處理方式分為兩部分:conflict detection (偵測) 與 conflict resolution (解決)。以下分別探討廣泛採用的偵測方式 optimistic concurrency control,以及常見的解決策略 Last-Write-Wins (LWW) 與 version forking。
Conflict detection#
Optimistic concurrency control 假設衝突罕見,只在更新時才驗證。運作流程如下:
- 每個檔案都有版本識別 (版本號或 hash),由 client 與 server 共同追蹤。
- Client 上傳新版本時,附上當前版本識別 (例如
If-Match: <version>header)。 - Server 比對:
- 相同:接受上傳,版本號遞增。
- 不同:回傳
409 Conflict。
- 衝突時,client:
- 透過
GET /v1/files/{fileId}取得最新版本。 - 自動或由使用者手動合併變更。
- 以更新後的版本重試上傳。
- 透過
優點:
- 確保上傳建立於最新版本之上,避免遺失更新。
- 可進行細粒度的衝突解決,提升資料完整性。
缺點:
- 衝突解決流程會增加延遲,在不穩定的行動網路上尤為明顯。
- 可能需要使用者手動合併,造成使用體驗摩擦。
Conflict resolution#
Google Drive 等雲端儲存常採最終一致性 (eventual consistency) 模型,以可用性為優先,允許更新先行、之後再解決衝突。兩種常見策略:
- Last-Write-Wins (LWW):自動以最新版本取代舊版本。
- Version forking:保留兩個衝突版本,通常透過檔名後綴區分 (如
filename.png與filename (1).png)。
Real-world examples#
策略選擇因 app 需求而異:
- Dropbox:預設使用 LWW,自動儲存最新版本,不詢問使用者。
- Google Drive:交由使用者決定,偵測到衝突時顯示對話框 (見 Figure 7),詢問是「同時保留」或「取代」。

Dropbox 的公開 API 透過上傳參數同時支援兩種策略:
strict_conflict:啟用嚴格驗證,防止併發上傳並回傳衝突錯誤。mode:控制衝突發生時的行為:add:自動重新命名,加上唯一計數器。overwrite:覆蓋既有檔案,即 LWW。
Data encryption#
面試官有時會追加需求,例如「若要支援企業客戶,需要將儲存在裝置上的檔案加密,該怎麼做?」
Files and metadata encryption#
若無加密,裝置遺失或被破解時,攻擊者可掛載儲存存取敏感檔案。我們使用業界標準 AES-256,兼顧安全性與行動硬體效能:
- Metadata 保護:檔案名稱、資料夾結構等存於加密資料庫。
- 檔案保護:下載後的檔案加密儲存在 app 私有空間。
即使儲存內容被存取,沒有解密金鑰也無法解讀。
Key management for offline access#
為了支援離線存取,加密金鑰必須在本機存放。善用平台安全機制:
- Android:Android Keystore 提供硬體支援的金鑰儲存,防止被提取。
- iOS:Keychain 提供類似保護,並於支援裝置整合 Secure Enclave。
為了額外保障,可以使用 PBKDF2 從使用者登入憑證派生金鑰,確保只有經過驗證的使用者才能解鎖資料,將加密與使用者身份緊密綁定。
Mobile implementation considerations#
加密雖提供安全,卻在行動裝置上帶來獨特挑戰。
Performance and resources#
- 加解密對 CPU 負擔重,尤其大檔案。應於背景執行緒處理以保持 UI 流暢。
- 加密檔案因 padding 與 metadata 而略微變大。
- 需留意記憶體消耗,特別是同時持有加密與解密版本時。
Memory management#
- 實作逾時清理 (timeout),自動釋放解密後的內容。
- 記憶體吃緊時釋放快取的解密資料。
- 留意加解密循環中的記憶體洩漏。
Error handling and recovery#
金鑰損毀、無法存取或加解密失敗時,需有明確的 fallback 路徑,並通知使用者採取正確行動。
Backup strategy#
加密檔案與裝置特定金鑰綁定,備份需特別處理:
- 預設將加密檔案排除在系統備份之外。
- 若需備份,實作自訂邏輯以備份專用金鑰重新加密。
Device compatibility#
並非所有裝置都有相同的安全硬體:
- 初始化時檢查安全硬體可用性。
- 在較舊裝置上優雅地 fallback 至 app sandbox 儲存。
- 告知使用者目前以降級的安全能力運行。
Step 5: Wrap-up#
本章我們設計了一個 Google Drive 客戶端,解決雲端儲存 app 的關鍵挑戰:
- 讓使用者安全地儲存、存取與同步檔案,並保留完整的版本歷史。
- 以 REST API 處理核心操作,並透過 SSE/WebSockets 傳遞即時更新。
- 透過清晰的 client 結構與後端互動,處理從基本檔案操作到 resumable uploads、版本歷史等進階功能。
若面試時間充裕,可考慮延伸以下題目:
- Selective sync:設計讓使用者可選擇哪些資料夾或檔案類型在本機儲存、哪些只留在雲端的系統。
- File sharing system:設計含細粒度權限與存取控管的完整檔案分享系統。
- Compression:以 Google 的 Brotli 等壓縮技術優化資料傳輸與儲存,降低頻寬與儲存成本。