async database processing
background job processing
bulk update webhooks
cdc webhooks
change data capture
database automation
database cdc
database event architecture
database event listeners
database event overload
database event queueing
database event routing
database event streaming
database migration safety
database row triggers
database sync
database triggers
database webhooks
database webhooks for background jobs
data layer webhooks
event driven architecture
handling bulk database updates
InstaWebhook
microservices webhooks
postgres change data capture
postgres event streaming
postgresql cdc
postgres webhooks
prisma ORM
prisma pulse
prisma pulse cdc
realtime database events
realtime database triggers
real time data pipeline
realtime webhooks
safe database webhook delivery
scaling database webhooks
serverless webhooks
supabase backend
supabase cdc
supabase change data capture
supabase database triggers
supabase webhooks
webhook buffer
webhook load balancing
webhook payload processing
webhook queueing
webhook rate limiting
webhook retry mechanism
webhook throttling
webhook traffic spikes
worker queue protection
2026 年的變更資料擷取:Supabase Webhooks、Prisma Pulse 與「雷鳴羊群」問題
資料庫曾經是被動的:你寫入它,你查詢它。它們正日益成為應用程式架構中的主動參與者——發出每筆插入、更新和刪除的即時饋送,讓其他系統能即時反應,而非輪詢變更。
這種模式稱為變更資料擷取(CDC),而在 Postgres 生態系中,Supabase Database Webhooks 與 Prisma Pulse 是經常出現的兩種工具。兩者都能將資料列層級的變更轉為事件。兩者都 genuinely 有用。但兩者也共享一個只有在執行大量更新時才會顯現的架構盲點——有時被稱為「雷鳴羊群」問題。
以下是對每種工具運作方式、大量更新問題的成因,以及具韌性設定的實務探討。
通往即時 Postgres 的兩條路徑
Supabase Database Webhooks
Supabase 的 Database Webhooks 實際上是 Postgres 觸發器加上 pg_net 擴充功能的便利層,pg_net 讓 Postgres 能直接從 SQL 發出非同步 HTTP 請求。當資料列被插入、更新或刪除時,觸發器會啟動,pg_net 會在背景發出 POST(或 GET)請求,因此網路呼叫不會阻塞觸發該請求的交易。
Supabase 傳送的酬載很直接——它會告訴你事件類型(INSERT、UPDATE 或 DELETE)、資料表與結構描述,以及新/舊資料列資料:
Code example
Copy code
type UpdatePayload = {
type: 'UPDATE'
table: string
schema: string
record: TableRecord
old_record: TableRecord
}
值得注意的是:酬載不包含專用的事件 ID 欄位——只有上述的資料列資料與中繼資料。這對等冪性很重要,我們稍後會再討論。
Supabase 官方文件中有兩個與 pg_net 相關的細節,直接與下文討論的大量更新問題有關:此擴充功能設定為可靠處理每秒最多 200 個請求,而回應資料僅保留六小時後就會被 Supabase 清除。兩者對一般流量而言都是合理的預設值——但當你一次發出數萬個 webhook 時,就會成為限制。
Prisma Pulse
Prisma Pulse 採取不同的做法:它不是推播式的 HTTP webhook,而是一種受管理的 CDC 服務,讓你能直接從 Prisma Client 訂閱資料庫變更,並以 Postgres 的預寫式日誌(透過邏輯複寫)作為真實來源。由於 Pulse 是建構在你的 Prisma 結構描述之上,因此你收到的事件會有型別:
Code example
Copy code
const stream = await prisma.user.stream()
for await (const event of stream) {
console.log(event.action) // 'create' | 'update' | 'delete'
}
如果你重新命名欄位,TypeScript 會標記仍在參照舊名稱的程式碼——這避免了 webhook 使用者常見的一類錯誤:悄悄假設 JSON 結構未曾變更。
值得注意的狀態說明,因為這種細節很快就會過時:Prisma 在 2025 年初暫停了 Pulse,以便根據使用者回饋重新設計,而截至 2026 年年中,官方 @prisma/extension-pulse 套件頁面仍顯示「已暫停,正在重新設計」的訊息。另一方面,Prisma 自己的文件仍會說明如何為 Prisma Postgres 託管的資料庫啟用 Pulse 的即時功能,因此情況是混雜的,而非明確的「啟用」或「停用」。如果你正在評估新專案是否要使用 Pulse,請將此視為開放問題,而非既定事實——在將生產架構提交給它之前,請務必查看 Prisma 目前的說明文件與更新日誌,因為一般可用的版本可能與 2023–2024 年推出的版本不同。
大量更新問題
無論你使用哪一種路徑,webhook 都是針對每一筆資料列變更觸發,而不是針對每一個 SQL 陳述式。這對一般流量而言很好——甚至很優雅:使用者註冊、資料列被插入、webhook 觸發、工作程式傳送歡迎郵件。
但當有人執行大量操作時,情況就不再如此。假設你的行銷團隊想要為在特定日期前註冊的每位使用者加值點數:
Code example
Copy code
UPDATE users
SET credit_balance = credit_balance + 10
WHERE created_at < '2025-01-01';
如果這影響了 50,000 筆資料列,而你對 users 的 UPDATE 事件有 webhook,Postgres 就會發出 50,000 個個別的 HTTP 請求。根據 Supabase 文件記載的 pg_net 大約每秒 200 個請求的上限,即使在最佳情況下,傳遞全部 50,000 個請求也需要超過四分鐘——而如果你的接收端點緩慢或短暫停機,pg_net 會將這些請求排隊,而不是立即傳遞。加上回應資料的六小時保留期限,緩慢或不穩定的接收端可能會遺漏事件,而非只是延遲接收。
下游團隊實際遇到的實務失敗模式:
第三方速率限制。CRM、電子郵件供應商以及其他你轉送事件的 API 會開始回傳 429 Too Many Requests。
資源耗盡。Node.js 伺服器或 serverless 函式若每個請求都要執行實際工作(剖析、資料庫查詢、呼叫其他服務),在突然的流量尖峰下可能耗盡記憶體或達到並行限制。
連線池耗盡。如果你的 webhook 處理程式在處理前會查詢資料庫以取得更多內容,大量同時啟動的處理程式可能耗盡你的連線池,並拖慢不相關的查詢。
系統間不同步。如果某些請求失敗且未無限重試,你的真實來源資料庫與下游系統(CRM、搜尋索引、快取)就會悄悄地分歧。
這不是 Supabase 或 Prisma 獨有的缺陷——這是任何將「每筆資料列變更對應一個 HTTP 請求」的架構所固有的問題。Postgres 處理變更的速度遠快於任何 HTTP 接收端實際能吸收的速度。
建置韌性層
標準的解決方法是停止將資料庫直接指向你的應用程式,而是在兩者之間放入一個持久的緩衝區:它能立即吸收大量事件,然後以工作程式實際能處理的速度將事件交給工作程式,並提供重試機制,以及在無法傳遞時讓事件落地的位置。
你可以自行使用佇列(BullMQ、SQS、Inngest 等)建置,放在輕量級的接收端點之後。也有專門為此工作打造的服務。InstaWebhook 就是一個例子:它是一個 webhook 接收與傳遞服務——在持久的端點接受酬載、將傳遞工作排隊到請求路徑之外、追蹤每個事件從 received → queued → attempted → retried → delivered/dead-lettered 的狀態,並根據可設定的退避排程重試失敗的傳遞。它還提供「自帶資料庫」模式,酬載會儲存在你控制的 Postgres 執行個體中,而不是儲存在廠商的基礎設施上——如果你要移動受 HIPAA、SOC 2 或類似合規要求保護的資料,這點就很重要。值得清楚說明的是:它是這個領域中較新、規模較小的產品,而不是已建立的領導廠商,因此值得依據你自己的可靠性與支援需求來評估,就像評估任何早期階段的基礎設施廠商一樣,同時也要比較自行託管的佇列加 DLQ 模式以及其他 webhook 基礎設施供應商。
無論你選擇什麼——自行建置或使用廠商服務——修正的架構都是一樣的:快速接受、將實際傳遞排隊、以退避方式重試,並為失敗的事件提供落腳處(dead-letter queue),而不是讓它們消失。
CDC 與資料庫 Webhook 的最佳實務
讓你的工作程式具等冪性——並選擇真正的等冪金鑰。重試意味著至少一次傳遞,而非正好一次,因此不具等冪性的工作程式可能會傳送重複的歡迎郵件或重複套用點數。針對常見假設的一個更正:Supabase 原生的資料庫 webhook 酬載不包含獨立的事件 ID 欄位——它只提供資料列的類型、資料表、結構描述、record 與 old_record。通常可行的等冪金鑰是資料列本身的主鍵加上 updated_at 時間戳記,或是酬載的雜湊值,儲存在 Redis 或專用資料表中,並在處理前檢查。(相較之下,Prisma Pulse 的 stream() API 會提供傳遞保證與事件排序,作為受管理服務的一部分,這是它在可用時的優勢之一。)
快速確認,非同步處理。不要在 webhook 請求本身執行緩慢的同步工作——例如產生 PDF、呼叫不穩定的第三方 API。如果傳送端等待回應逾時,它可能會將請求視為失敗並重試,而你最終會做兩次工作。驗證簽章、將工作推送到內部佇列,然後快速回應。
在每個請求上驗證簽章。webhook 接收端實質上是在信任任何擊中端點的東西確實來自你的資料庫。如果有人找到 URL,他們可能偽造酬載來觸發不必要的副作用。在對任何內容採取行動之前,先驗證 HMAC 或 JWT 簽章:
Code example
Copy code
const crypto = require('crypto')
function verifySignature(payload, signatureHeader, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(signatureHeader)
)
}
- 如果你自行託管,請監控 pg_net 的工作程式池。透過 pg_net 發出數千個請求會消耗背景 Postgres 工作程式。如果你的目的地回應緩慢,那些連線會維持開啟的時間比預期長,這正是上述大量更新問題背後的機制。Supabase 的文件指出,可以直接在 SQL 中檢查 pg_net 的健康狀態(select pid from pg_stat_activity where backend_type ilike '%pg_net%'),如果 webhook 停止觸發,這是 runbook 中很有用的資訊。
結論
CDC——透過 Supabase 以 pg_net 驅動的 webhook 或 Prisma 的結構描述感知事件串流——是一種真正好的方式,能讓資料庫感覺像是應用程式的活躍中心,而非被動儲存體。但將每筆資料列變更視為獨立的輸出 HTTP 請求有實際的上限,而大量操作正是找到這個上限的困難時刻。具等冪性的處理程式、快速確認、簽章驗證,以及在資料庫與應用程式程式碼之間設置持久緩衝區,是即時架構能擴展與首次遇到大型移轉就悄悄倒下的差別所在。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.