ข้ามไปยังเนื้อหา

Background Tasks และ Webhooks กับ Functions

Database Webhook ทำให้ row ที่เปลี่ยนแปลงใน Postgres เรียก Edge Function ได้อัตโนมัติ ส่วน EdgeRuntime.waitUntil ทำให้ function นั้นตอบกลับทันทีในขณะที่งานที่ช้ากว่ายังทำต่อในเบื้องหลัง

Edge Function ทุกตัวที่ผ่านมาถูกเรียกโดย client อย่างชัดเจนด้วย supabase.functions.invoke() แต่งานจริงจำนวนมากควรเกิดขึ้นเพราะการเปลี่ยนแปลงในฐานข้อมูล ไม่ใช่เพราะ client จำได้ว่าต้องเรียก เช่น ส่ง welcome email ตอนมี row ใหม่ใน profiles หรือแจ้งระบบปลายทางตอนมีการสั่งซื้อ Database Webhooks ตอบโจทย์นี้ตรง ๆ ตั้งค่าบน table สำหรับ event insert, update หรือ delete แล้ว Supabase จะยิง HTTP request ให้ทุกครั้งที่ event นั้นเกิดขึ้น

เบื้องหลัง Database Webhook คือ wrapper สะดวกใช้ที่ครอบ Postgres trigger อยู่ สร้างบน extension pg_net pg_net ยิง HTTP request จากใน Postgres แบบasynchronous ดังนั้น network call ที่ช้าหรือ fail จะไม่บล็อกการเขียนข้อมูลที่ทำให้เกิด trigger นั้นเลย คำสั่ง insert commit ได้ไม่ว่า HTTP call ของ webhook จะใช้เวลานานแค่ไหน

-- Roughly what a Database Webhook configures under the hood:
-- a trigger that calls pg_net on insert into public.profiles
create trigger "profiles_insert_webhook"
after insert on public.profiles
for each row
execute function supabase_functions.http_request(
'https://<project-ref>.supabase.co/functions/v1/welcome-email',
'POST',
'{"Content-Type":"application/json"}',
'{}',
'5000'
);

ในทางปฏิบัติคุณตั้งค่านี้ผ่าน Dashboard ในส่วน Database Webhooks แทนที่จะเขียน trigger เอง แต่การรู้ว่าเป็น “trigger บวก pg_net” ช่วยอธิบายว่าทำไม webhook ถึง asynchronous และทำไมถึงชี้ไปที่ URL ของ Edge Function ได้เหมือน HTTP client ทั่วไป

งานบางอย่างที่ trigger แบบนี้ไม่ควรทำให้ response ช้าลง เช่น ส่ง confirmation email เขียน analytics log หรือเรียก third-party API ที่ใช้เวลาสักไม่กี่วินาที การให้ผู้เรียกเดิม (ซึ่งมักเป็นกลไก webhook ของฐานข้อมูลเอง) รอสิ่งเหล่านั้นทั้งหมดเป็นเรื่องเสียเวลาเปล่า EdgeRuntime.waitUntil(promise) แก้ปัญหานี้ ทำเครื่องหมาย promise ว่าเป็น background task ที่ยังรันต่อได้แม้ function จะส่ง response ไปแล้ว

Deno.serve(async (req) => {
const payload = await req.json()
// Do not await this — let it run after the response is sent
EdgeRuntime.waitUntil(sendConfirmationEmail(payload))
return new Response('ok', { status: 200 })
})
async function sendConfirmationEmail(payload: unknown) {
// Slow third-party API call, logging, etc.
}

จุดสำคัญคือไม่มี await อยู่หน้า sendConfirmationEmail(payload) ถ้า await ตรงนั้น response จะต้องรอจนกว่า email จะส่งเสร็จ การส่ง promise ที่ยังไม่ resolve เข้า waitUntil คือการบอก Deno runtime ให้เก็บ instance ของ function ไว้จนกว่า promise นั้นจะจบ โดยไม่ทำให้ HTTP response ต้องรอ

ตัวอย่างจริงแบบครบวงจร Database Webhook ยิงตอน insert เข้า orders เรียก Edge Function ที่ตอบกลับทันทีเพื่อยืนยันว่าเขียนข้อมูลสำเร็จ ในขณะที่ confirmation email ถูกส่งอยู่เบื้องหลัง

Deno.serve(async (req) => {
const { record } = await req.json() // the new orders row, from the webhook payload
EdgeRuntime.waitUntil(sendOrderConfirmationEmail(record))
return new Response(JSON.stringify({ received: true }), {
headers: { 'Content-Type': 'application/json' },
})
})
async function sendOrderConfirmationEmail(order: { id: string; email: string }) {
// Call an email provider here; this runs after the response is already sent
}

ฐานข้อมูลไม่ต้องรอ email provider เลย ผู้เรียก webhook ได้ 200 เร็ว ๆ และ email ก็ยังถูกส่งอยู่ดี เพียงแต่ส่งหลังจาก HTTP round trip จบไปแล้ว

flowchart LR
  insertRow["insert into orders"] --> webhook["Database Webhook (pg_net, async)"]
  webhook --> edge["Edge Function"]
  edge --> respond["Immediate response: 200 received"]
  edge -->|"EdgeRuntime.waitUntil"| bg["Background task: send confirmation email"]
Postgres insert trigger ไปยัง webhook, Edge Function และ background task
Database Webhooks ใช้ Postgres extension อะไรเป็นพื้นฐาน
ทำไม pg_net ถึงทำให้ HTTP call ของ webhook เป็นแบบ asynchronous
EdgeRuntime.waitUntil ทำให้ Edge Function ทำอะไรได้
ในตัวอย่างยืนยันคำสั่งซื้อแบบรวม ถ้า await promise ของการส่ง email แทนที่จะส่งเข้า waitUntil จะเกิดอะไรขึ้น