Cloudflare D1 資料庫實作:在邊緣區域的高性能資料同步與管理
深入探討 DueWise 如何利用 Cloudflare D1 (SQLite on Edge) 實現全球資料極速同步,並分享 SQL 結構設計與性能優化心得。
Cloudflare D1 資料庫實作:在邊緣區域的高性能資料同步與管理
在分散式系統中,最難處理的通常不是計算邏輯,而是「狀態」。對於 DueWise 這樣一個需要全球存取的效期管理工具,如何讓身處紐約的用戶與身處台北的用戶都能享有極低的讀寫延遲?我們的答案是 Cloudflare D1。
D1 是 Cloudflare 基於 SQLite 構建的無伺服器 (Serverless) 關聯式資料庫。它強大的地方在於它直接嵌入在全球分佈的 Worker 中。這篇文章將分享我們在 DueWise 中使用 D1 的實戰經驗。
為什麼選擇 D1 而不是傳統 RDS 或 MongoDB?
- 零延遲存取: D1 運行在邊緣,與計算(Worker)同一個節點。
- SQLite 的簡潔性: 對於清單類的數據結構,關聯式資料庫的 SQL 查詢語法遠比 NoSQL 穩定且靈活。
- 無感擴展 (Serverless): 我們不需要配置主機規格、不用管理分片 (Shards),Cloudflare 幫我們處理了一切。
DueWise 的資料表結構設計
在 DueWise 中,我們設計了一個核心資料表 items,透過 owner (用戶 Email) 進行索引優化。
CREATE TABLE items (
id TEXT PRIMARY KEY,
owner TEXT NOT NULL,
name TEXT NOT NULL,
category TEXT,
expiry_date TEXT NOT NULL, -- 以 YYYY-MM-DD 儲存,方便排序
start_date TEXT,
note TEXT,
is_recurring INTEGER DEFAULT 0,
recurring_interval INTEGER,
recurring_unit TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_owner ON items(owner);
CREATE INDEX idx_expiry ON items(expiry_date);
優化要點:
- 索引優化: 我們為
owner建立了索引,確保每個用戶加載「我的清單」時都能在 10ms 內完成掃描。 - 類型選擇: 雖然 SQLite 沒有專用的日期類型,但我們堅持使用
TEXT (YYYY-MM-DD),這讓我們在 SQL 語法中可以直接進行大小比較,例如SELECT * FROM items WHERE expiry_date < date('now')。
技術實戰:Astro 內的 D1 呼叫
在 Astro 組件中,我們可以透過 Astro.locals 取得 Cloudflare 環境變數提供的 D1 實例:
const db = (Astro.locals as any).runtime?.env?.DB;
const userEmail = session.user.email;
const { results } = await db.prepare(
'SELECT * FROM items WHERE owner = ? ORDER BY expiry_date ASC'
).bind(userEmail).all();
這種整合方式非常乾淨,沒有連接字串(Connection Strings)的困擾,也沒有連接池(Connection Pooling)爆炸的問題,因為這本質上是 HTTP 型態的請求。
修復「過時數據」:D1 的最終一致性
在使用 D1 時需要注意,雖然讀寫很快,但在極少數分散式場景下,可能會遇到「最終一致性」的細微差異。為了確保 DueWise 的用戶體驗,我們在前端實作了 Optimistic UI (樂觀更新)。
當用戶點擊「刪除物品」時,Zustand Store 會立刻從本地列表中移除該項,同時發送後端請求。即使 D1 正在處理全球同步,用戶看到的介面依然是流暢、無延遲的。
如何處理離線模式與資料庫同步?
這也是 D1 展現價值的地方。由於 D1 與 IndexedDB 的結構高度相似(都是關聯式概念),我們開發了一組 Sync Logic:
- 離線操作: 數據寫入瀏覽器的 IndexedDB。
- 偵測連線: PWA 利用導航狀態偵測網路。
- 批次上傳: 將本地的新增/修改透過 D1 批次 API 一次性寫入雲端。
D1 Batch API 範例:
const statements = syncPayload.map(item =>
db.prepare("INSERT INTO items (id, ...) VALUES (?, ...)").bind(item.id, ...)
);
await db.batch(statements);
使用 batch 不僅節省了網路往返時間,還能保證操作的原子性 (Atomicity)。
結論
Cloudflare D1 徹底改變了我們看待資料庫的方式。它不再是一個沉重、需要遠程連接的「龐然大物」,而是一個輕巧且無處不在的「局部變數」。對於追求極速與全球化佈署的 PWA 應用程式,D1 無疑是當前最強大的後盾。
下一篇文章,我們將探討驗證的核心:「Auth.js (Auth-Astro) 認證架構:如何在邊緣端實現安全的 OAuth 整合」。