Database Functions as RPC
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”Postgres function ถูก expose เป็น endpoint แบบ RPC ที่เรียกได้ให้อัตโนมัติ ดังนั้น logic ที่ต้องรันแบบ atomic หรือเป็นหลายขั้นตอนสามารถอยู่ในฐานข้อมูลแทนที่จะประกอบขึ้นฝั่ง client
เมื่อ query อย่างเดียวไม่พอ
หัวข้อที่มีชื่อว่า “เมื่อ query อย่างเดียวไม่พอ”query .from() เหมาะกับการเข้าถึง table และ view ตรง ๆ แต่บาง operation ไม่เข้ากับ select/insert/update เดี่ยว ๆ logic ที่มีหลายขั้นตอน logic ที่ต้องรันเป็น unit เดียวแบบ atomic หรือ logic ที่อยากเก็บไว้นอก application code เลย มักจะเขียนเป็น Postgres function ด้วยภาษา plpgsql หรือ sql ได้ง่ายและปลอดภัยกว่า
create function public.increment_view_count(post_id uuid)returns voidlanguage sqlas $$ update public.posts set view_count = view_count + 1 where id = post_id;$$;PostgREST expose function เป็น /rpc/<function_name>
หัวข้อที่มีชื่อว่า “PostgREST expose function เป็น /rpc/<function_name>”เหมือนที่ทำกับ table PostgREST introspect schema หา function แล้ว expose แต่ละตัวเป็น endpoint แบบ POST ภายใต้ /rpc
POST /rest/v1/rpc/increment_view_countจาก supabase-js เรียกผ่าน .rpc() โดยส่งชื่อ function พร้อม object ของชื่อ argument กับค่า
const { data, error } = await supabase.rpc('increment_view_count', { post_id: '3b8f1c1e-7f2e-4b7a-9c1a-8b2f6a2d9e10',});
if (error) { console.error('failed to increment view count:', error.message);}ทำไมวิธีนี้ดีกว่า read-then-write ฝั่ง client
หัวข้อที่มีชื่อว่า “ทำไมวิธีนี้ดีกว่า read-then-write ฝั่ง client”ลองนึกภาพว่าเพิ่ม counter ตัวเดียวกันนี้จากฝั่ง client แทน อ่านค่า view_count ปัจจุบัน บวกหนึ่ง แล้วเขียนกลับ ถ้ามี request พร้อมกัน client สองตัวอาจอ่านค่าตั้งต้นเดียวกันก่อนที่ตัวไหนจะเขียนกลับ ทำให้การเพิ่มค่าหนึ่งครั้งหายไปเงียบ ๆ — เป็น race condition แบบคลาสสิก ส่วน Postgres function ด้านบนทำทั้งอ่านและเขียนในคำสั่ง update เดียวแบบ atomic ที่ database รันเอง ทำให้ request พร้อมกันไม่มีทาง race กัน
นี่คือภาพรวมว่า RPC คุ้มค่าตอนไหน .from() query เหมาะกับการเข้าถึง table/view ตรง ๆ ส่วน .rpc() เหมาะกับ function — logic เฉพาะทางที่อาจมีหลาย statement หรือต้องเป็น transaction ซึ่งควรอยู่ในฐานข้อมูลจริง ๆ ใช้ RPC เมื่อ logic ต้องการ guarantee แบบนี้ ไม่ใช่ใช้แทน query ธรรมดาเป็น default
flowchart LR
fn["create function increment_view_count(post_id uuid)"] --> introspect["PostgREST introspects functions"]
introspect --> endpoint["POST /rest/v1/rpc/increment_view_count"]
endpoint --> call["supabase.rpc('increment_view_count', { post_id })"]
call --> atomic["Single atomic UPDATE in Postgres"]