- บทความที่รวบรวมแพตเทิร์นเชิงปฏิบัติที่ช่วยให้ใช้ Postgres ได้อย่างมีประสิทธิภาพและปลอดภัยมากขึ้น
- แต่ละแพตเทิร์นอาจดูเล็กน้อย แต่เมื่อสะสมแล้วสร้างความแตกต่างได้มาก
ใช้ UUID เป็นคีย์หลัก
- UUID เป็นแบบสุ่ม จึงมีข้อเสียในแง่ของการเรียงลำดับและประสิทธิภาพของดัชนี
- ใช้พื้นที่มากกว่า ID แบบตัวเลข
- แต่ก็มีข้อดีดังนี้
- สร้าง UUID ได้โดยไม่ต้องเชื่อมต่อกับ DB
- เปิดเผยต่อภายนอกได้อย่างปลอดภัย
- สามารถใช้
gen_random_uuid() เพื่อสร้าง UUID เป็นคีย์หลักโดยอัตโนมัติได้
เพิ่มฟิลด์ created_at และ updated_at เสมอ
- ตอนดีบัก การรู้เวลาที่เรคคอร์ดถูกสร้างและแก้ไขนั้นมีประโยชน์มาก
- สามารถตั้งค่าให้
updated_at อัปเดตอัตโนมัติผ่าน trigger ได้
- ฟังก์ชันสร้างเพียงครั้งเดียวได้ แต่ต้องนำ trigger ไปใช้กับแต่ละตาราง
ตั้งค่า on update/delete restrict ให้กับ foreign key
- เมื่อตั้งค่าเงื่อนไข foreign key ควรใช้
on update restrict on delete restrict เสมอ
- ช่วยป้องกันไม่ให้เกิดการลบต่อเนื่องโดยไม่ตั้งใจเมื่อลบข้อมูล
- พื้นที่จัดเก็บมีราคาถูก แต่การกู้คืนข้อมูลทำได้ยากมาก จึงควรจัดการอย่างระมัดระวัง
แนะนำให้ใช้ schema
- schema เริ่มต้นคือ
public แต่เมื่อแอปพลิเคชันใหญ่ขึ้น ควรแยกออกเป็น schema เฉพาะ
- schema ทำงานคล้าย namespace และสามารถ join ข้าม schema กันได้
- ยิ่งมีจำนวนตารางมาก การใช้ schema จะยิ่งดีต่อความอ่านง่ายและการบำรุงรักษา
ใช้แพตเทิร์นตาราง Enum
- การใช้ตาราง enum แทน enum type หรือ check constraint ของ PostgreSQL มีความยืดหยุ่นมากกว่า
- เมื่อจัดการค่า enum ในตารางแยก จะสามารถเพิ่ม metadata หรือขยายค่า enum ได้ง่าย
- ใช้ foreign key อ้างอิงค่าจากตาราง enum เพื่อคงข้อจำกัดของข้อมูล
ตั้งชื่อตารางเป็นเอกพจน์
- ควรตั้งชื่อตารางเป็นเอกพจน์ ไม่ใช่พหูพจน์
- ตอนเขียนคิวรี ชื่อเอกพจน์ชัดเจนกว่า และชื่อพหูพจน์อาจทำให้เกิดความสับสนด้านความหมายหรือรูปเจ้าของ
ตั้งชื่อ join table แบบเป็นกลไก
- join table สำหรับความสัมพันธ์แบบ many-to-many ควรตั้งชื่อโดยนำชื่อตารางทั้งสองมาต่อกัน เพื่อความปลอดภัยและชัดเจน
- ตัวอย่าง:
person_pet
- เพิ่ม unique index ให้กับคู่ค่าผสมเพื่อป้องกันข้อมูลซ้ำ
ใช้ soft delete แทนการลบจริง
- แทนที่จะลบข้อมูลจริง ควรใช้ฟิลด์ timestamp เช่น
revoked_at เพื่อระบุเวลาที่ลบ
- ทำให้ติดตามได้ไม่ใช่แค่ว่าถูกลบหรือไม่ แต่ยังรู้ด้วยว่าถูกลบเมื่อไร
- timestamp ให้ข้อมูลมากกว่า Boolean
แทนสถานะ (Status) ด้วยตารางล็อก
- แทนที่จะแสดงสถานะด้วยคอลัมน์เดียว ให้เก็บประวัติการเปลี่ยนสถานะไว้ในตารางแยก
- ระบุเวลาที่สถานะเกิดขึ้นด้วยคอลัมน์
valid_at
- ตั้งค่าแฟล็ก
latest พร้อม unique index และ trigger เพื่อให้ดึงสถานะล่าสุดได้อย่างรวดเร็ว
- วิธีนี้ได้เปรียบในงานประมวลผลเหตุการณ์แบบอะซิงโครนัสหรือกรณีที่ลำดับเหตุการณ์อาจสลับกัน
เพิ่ม system_id ให้กับแถวพิเศษ
- นอกจากตาราง enum แล้ว บางครั้งยังต้องมี "แถวของระบบ" โดยเฉพาะ
- เพิ่มฟิลด์ข้อความ
system_id แบบ nullable และตั้งค่า unique index
- ใช้
system_id เพื่อค้นหาแถวเฉพาะนั้นได้อย่างชัดเจน
ใช้ View ให้น้อยที่สุด
- View มีประโยชน์ในการทำคิวรีซับซ้อนให้เป็นนามธรรม แต่ดูแลรักษายาก
- เมื่อลบคอลัมน์ ต้องสร้าง View ใหม่
- ถ้าสร้าง View ซ้อนบน View จะเกิดปัญหาทั้งด้านประสิทธิภาพและความอ่านง่าย
- จึงควรใช้อย่างระมัดระวังและเท่าที่จำเป็น
ใช้คิวรี JSON อย่างเต็มที่
- Postgres ไม่ได้เก่งแค่การเก็บ JSON แต่ยังทรงพลังมากในการคิวรีเพื่อคืนค่าเป็น JSON
- สามารถคืนค่าความสัมพันธ์แบบซ้อนเป็น JSON ได้ด้วยคิวรีเพียงครั้งเดียว
- ดึงข้อมูลที่ต้องใช้ทั้งหมดได้ในครั้งเดียวโดยไม่เจอปัญหา N+1
- ข้อเสีย: ข้อมูลชนิดสูญหาย ต้องโหลดข้อมูลทั้งหมดเข้าเมมโมรีพร้อมกัน
- แต่ในด้านประสิทธิภาพหรือโครงสร้างแล้ว ข้อดีมีมากกว่า
4 ความคิดเห็น
> ตั้งชื่อตาราง join แบบใช้กฎตายตัว
ผมว่าการมีหลักเกณฑ์แบบนี้ตอนตั้งชื่อก็ดีเหมือนกันครับ~
ถ้าพิจารณา UUID7 ก็น่าจะสามารถเรียงลำดับตามเวลาได้ไม่ใช่หรือ?
น่าจะลองอ่านบทความเกี่ยวกับ การใช้ UUID เป็นคีย์หลักใน PostgreSQL ด้วยเหมือนกันนะครับ
วิธีใส่ timestamp ตอนทำ soft delete นี่ดีเลย
ถ้าใช้ UUID เป็นคีย์หลักก็จะเรียงตามเวลาไม่ได้ เลยคิดว่าการใช้ snowflake id หรือ ulid ก็น่าจะดีเหมือนกันครับ เพียงแต่ในกรณีนี้แต่ละเซิร์ฟเวอร์ก็ต้องถือ sequence number ไว้ด้วย