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

Database Functions as RPC

Postgres function ถูก expose เป็น endpoint แบบ RPC ที่เรียกได้ให้อัตโนมัติ ดังนั้น logic ที่ต้องรันแบบ atomic หรือเป็นหลายขั้นตอนสามารถอยู่ในฐานข้อมูลแทนที่จะประกอบขึ้นฝั่ง client

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 void
language sql
as $$
update public.posts
set view_count = view_count + 1
where id = post_id;
$$;

เหมือนที่ทำกับ table PostgREST introspect schema หา function แล้ว expose แต่ละตัวเป็น endpoint แบบ POST ภายใต้ /rpc

Terminal window
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);
}

ลองนึกภาพว่าเพิ่ม 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"]
Postgres function ที่ expose เป็น RPC endpoint ที่เรียกได้
Postgres function กลายเป็นเรียกได้ผ่าน Data API ได้อย่างไร
ทำไมการเพิ่ม view-count แบบ atomic ถึงเหมาะกับ RPC function มากกว่า read-then-write ฝั่ง client
ความต่างเชิงแนวคิดระหว่าง query .from() กับการเรียก .rpc() คืออะไร
จาก supabase-js เรียก Postgres function ชื่อ increment_view_count พร้อม argument อย่างไร