Connection Pooling และ Supavisor
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”Postgres มีขีดจำกัดของ concurrent connection ที่ต่อตรงเข้ามาได้ ดังนั้น Supabase จึงวาง Supavisor ไว้ด้านหน้า Postgres เป็น connection pooler ที่มีสองโหมด — transaction mode บน port 6543 สำหรับ client อายุสั้นจำนวนมากที่ serverless กับ edge environment สร้างขึ้นมา และ session mode บน port 5432 สำหรับ client อายุยืนจำนวนน้อยที่ต้องการ feature ของ Postgres แบบครบถ้วน
ทำไม connection ถึงเป็นทรัพยากรที่มีจำกัด
หัวข้อที่มีชื่อว่า “ทำไม connection ถึงเป็นทรัพยากรที่มีจำกัด”connection ของ Postgres แต่ละอันกิน memory และ backend process บนฝั่ง database server ดังนั้น Postgres จึงจำกัดจำนวนที่เปิดพร้อมกันได้ — โดย default อยู่ที่หลักร้อยต้น ๆ และ managed Postgres ของ Supabase ก็ไม่ต่างกัน ขีดจำกัดนี้อยู่ร่วมกับ server แบบดั้งเดิมที่รันยาว ๆ ได้ง่าย เพราะเปิด connection ไม่กี่อันแล้วใช้ซ้ำตลอดอายุการทำงานของตัวเอง
serverless กับ edge environment ทำลายสมมติฐานนั้น Edge Function, serverless API route หรือ framework ไหนก็ตามที่สร้าง instance อายุสั้นใหม่ทุกครั้งต่อ request ล้วนพยายามเปิด connection ตรงของตัวเองไปยัง Postgres ภายใต้ traffic จริง สิ่งนี้รวมกันอย่างรวดเร็วจนเกิน concurrent connection ที่ Postgres รับไหว แล้ว request ก็เริ่ม fail ด้วย connection error — ไม่ใช่เพราะ database ช้า แต่เพราะ connection slot หมดไปเฉย ๆ
Supavisor คือ transaction mode เทียบกับ session mode
หัวข้อที่มีชื่อว่า “Supavisor คือ transaction mode เทียบกับ session mode”Supavisor คือ connection pooler ของ Supabase ทำหน้าที่อยู่ระหว่างแอปของคุณกับ Postgres ถือ pool ของ connection จริงที่มีจำนวนน้อยกว่า แล้วแจกจ่ายให้กับ logical client จำนวนมากกว่าที่ Postgres จะรับตรง ๆ ได้ มีสองโหมด และเลือกโหมดจาก port ที่คุณต่อเข้าไป
- Transaction mode (port
6543) — connection หนึ่งจะถูกถือไว้แค่ช่วงของ transaction เดียวเท่านั้น แล้วคืนกลับเข้า pool ทันที ทำให้ pool เล็ก ๆ รองรับ client อายุสั้นจำนวนมากได้ ซึ่งตรงกับรูปแบบ traffic ของ serverless กับ Edge Function พอดี ข้อแลกเปลี่ยนคือโหมดนี้ไม่รองรับ feature ระดับ session ของ Postgres อย่าง prepared statement เพราะ connection ที่อยู่ข้างใต้อาจถูกส่งต่อไปให้ client คนอื่นระหว่าง transaction - Session mode (port
5432) — client หนึ่งจะได้ connection เฉพาะของตัวเองตลอดเวลาที่ต่ออยู่ เหมือนคุยกับ Postgres ตรง ๆ เป๊ะ ๆ สิ่งนี้ให้ feature ครบถ้วนทุกอย่าง รวมถึง prepared statement แต่ client ที่ต่ออยู่แต่ละตัวจะกิน slot หนึ่งใน pool ตลอดเวลาที่ยังต่ออยู่ จึงไม่ scale ไปถึง client อายุสั้นจำนวนมหาศาลพร้อมกันได้แบบที่ transaction mode ทำได้
# Transaction mode — best for serverless functions, Edge Functions, and other short-lived clientsexport DATABASE_URL="postgres://postgres.<project-ref>:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres"
# Session mode — for long-lived processes, or anything that specifically needs prepared statementsexport DIRECT_URL="postgres://postgres.<project-ref>:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:5432/postgres"ควรใช้โหมดไหน
หัวข้อที่มีชื่อว่า “ควรใช้โหมดไหน”ให้ default เป็น transaction mode สำหรับอะไรก็ตามที่เป็น serverless หรืออายุสั้น เช่น Edge Function, backend ของแอปส่วนใหญ่ที่รันบน serverless platform และ API route ที่เปิด connection ต่อ request เพราะเป็นโหมดที่ถูกสร้างมาสำหรับ traffic รูปแบบนั้นโดยตรง
ให้ใช้ session mode เฉพาะตอนที่คุณต้องการ feature ระดับ session จริง ๆ เช่น prepared statement, LISTEN/NOTIFY หรือ process ฝั่ง server อายุยืน (backend แบบดั้งเดิมที่มี connection pool ของตัวเองอยู่แล้ว หรือ migration tool) ที่ต่ออยู่ตลอดและได้ประโยชน์จาก connection เฉพาะตัวมากกว่า connection แบบ pool ถ้าไม่แน่ใจว่า framework หรือ ORM ของคุณต้องการอันไหน ให้เช็ก doc ของตัวเองเรื่อง pooler compatibility ก่อนเลือกโหมด — ORM บางตัวต้องการ session-mode URL สำหรับ migration และ transaction-mode URL สำหรับ query ตอน runtime
flowchart LR
subgraph short["Many short-lived clients"]
e1["Edge Function"]
e2["Serverless request"]
e3["Serverless request"]
end
subgraph long["Few long-lived clients"]
s1["Long-lived server process"]
s2["Tool needing prepared statements"]
end
e1 --> tx["Supavisor - transaction mode, port 6543"]
e2 --> tx
e3 --> tx
tx --> pool[("Small shared pool of Postgres connections")]
s1 --> sess["Supavisor - session mode, port 5432"]
s2 --> sess
sess --> dedicated[("One dedicated Postgres connection per client")]