PostgreSQL JSONB: ใช้อย่างไรให้ยืดหยุ่นโดยไม่ทำให้ schema เละ
JSONB ใน PostgreSQL ช่วยเก็บข้อมูลที่ยืดหยุ่นได้ดี แต่หากใช้อย่างไม่มีหลักการก็อาจทำให้ schema ควบคุมยากในระยะยาว บทความนี้สรุปแนวทางออกแบบที่ช่วยให้ได้ทั้งความยืดหยุ่นและความเป็นระเบียบของฐานข้อมูลเชิงสัมพันธ์

PostgreSQL JSONB: ใช้อย่างไรให้ยืดหยุ่นโดยไม่ทำให้ schema เละ
หลายทีมเริ่มต้นออกแบบฐานข้อมูลด้วยตารางที่เป็นระเบียบ แยกคอลัมน์ชัดเจน และดูแลง่าย แต่เมื่อระบบเริ่มเติบโต ความต้องการทางธุรกิจก็มักเพิ่มฟิลด์พิเศษเข้ามาเรื่อย ๆ จนเกิดปัญหาคอลัมน์พองตัว โตเร็ว และยากต่อการดูแล
ในสถานการณ์แบบนี้ JSONB ของ PostgreSQL มักกลายเป็นตัวเลือกยอดนิยม เพราะเหมาะกับข้อมูลที่มีโครงสร้างยืดหยุ่น เช่น metadata, settings, event payload, attributes ของสินค้า หรือข้อมูลที่รับมาจากหลายแหล่ง
อย่างไรก็ตาม ความยืดหยุ่นที่ไม่มีขอบเขตอาจกลายเป็นปัญหาใหญ่ได้ หากใช้ JSONB แบบปล่อยไหล สุดท้ายจากที่ตั้งใจให้ยืดหยุ่น อาจกลายเป็นข้อมูลที่ควบคุมไม่ได้ ค้นหายาก และดูแลลำบากกว่าเดิม
หลักคิดสำคัญ: แยกข้อมูลแกนธุรกิจกับข้อมูลเสริม
แนวคิดที่ใช้งานได้จริงคือ:
- เก็บข้อมูลที่เป็นแกนธุรกิจไว้ในคอลัมน์ปกติ
- เก็บข้อมูลที่เปลี่ยนบ่อย หรือมีเฉพาะบางกรณีไว้ใน
JSONB
ตัวอย่างเช่นตาราง orders
order_id,customer_id,status,created_atควรเป็นคอลัมน์ปกติmetadataควรเป็นJSONBสำหรับข้อมูลเสริม เช่น source campaign, shipping note หรือ device info
การแยกแบบนี้ช่วยให้:
- ค้นหาข้อมูลได้ง่ายกว่า
- ออกแบบ index ได้แม่นยำกว่า
- บังคับกฎธุรกิจได้ชัดเจนกว่า
- เขียน query เพื่อทำรายงานได้ง่ายขึ้นมาก
สิ่งที่ไม่ควรทำกับ JSONB
ข้อผิดพลาดที่พบบ่อยคือเอาทุกอย่างไปเก็บใน JSONB แม้แต่ข้อมูลสำคัญอย่าง status, price หรือ created_at
เมื่อทำแบบนั้น ปัญหาที่ตามมามักมีลักษณะนี้:
- ชื่อ key ไม่ตรงกัน เช่น
userId,user_id,uid - ชนิดข้อมูลไม่นิ่ง เช่น
"10",10, หรือ"สิบ" - ค่าที่ควรเป็นมาตรฐานเดียวกันกลับไม่สม่ำเสมอ เช่น
THBกับthb - วันเวลาเก็บคนละรูปแบบจน query ยุ่งยาก
JSONB จึงไม่ควรถูกใช้แทนการออกแบบ schema แต่ควรใช้เพื่อรองรับข้อมูลที่ยืดหยุ่นอย่างมีขอบเขต
วิธีคุม schema ของ JSONB ให้อยู่หมัด
1) ตั้ง contract ของ key ให้ชัด
แม้ PostgreSQL จะไม่บังคับ schema ของ JSONB แบบเต็มรูปแบบ แต่ทีมควรมีเอกสารกลางที่ระบุให้ชัดว่า:
- ใช้ key อะไรบ้าง
- แต่ละ key มีชนิดข้อมูลอะไร
- field ไหนเป็น optional
ตัวอย่างเช่น:
metadata.sourceต้องเป็น textmetadata.device.osต้องเป็น textmetadata.tagsต้องเป็น array ของ text
การมี contract ชัดเจนช่วยลดความคลุมเครือ และทำให้ทั้งทีมเขียนข้อมูลได้สอดคล้องกัน
2) แยก version ของ payload
หากโครงสร้างข้อมูลมีโอกาสเปลี่ยน ควรเก็บ schema_version ไว้ใน JSONB หรือแยกเป็นอีกคอลัมน์หนึ่ง
ข้อดีคือ:
- รองรับข้อมูลหลายรุ่นได้ง่าย
- migration ข้อมูลทำได้เป็นระบบ
- ไม่ต้องเดาโครงสร้างย้อนหลังเมื่อระบบโตขึ้น
วิธีนี้สำคัญมากในระบบที่ payload จากภายนอกเปลี่ยนแปลงบ่อย หรือมีการพัฒนาฟีเจอร์อย่างต่อเนื่อง
3) ใช้ CHECK constraint เพื่อบังคับกฎบางส่วน
แม้ JSONB จะยืดหยุ่น แต่ PostgreSQL ก็ยังช่วยตรวจสอบเงื่อนไขบางอย่างได้ผ่าน CHECK constraint
ตัวอย่างแนวคิด เช่น:
metadataต้องมี key ชื่อsource- ถ้ามี
priorityค่าต้องเป็นตัวเลข
วิธีนี้ช่วยกันข้อมูลผิดรูปแบบตั้งแต่ระดับฐานข้อมูล ไม่ปล่อยให้ภาระทั้งหมดไปตกอยู่ที่แอปพลิเคชัน
4) ใช้ generated column เมื่อมี field ที่ถูก query บ่อย
ถ้ามี field ใน JSONB ที่ถูกใช้ filter เป็นประจำ เช่น metadata->>'source' การแยกค่าออกมาเป็น generated column แล้วทำ index จะช่วยได้มาก
ข้อดีคือ:
- query เร็วขึ้น
- โค้ดอ่านง่ายขึ้น
- ลดการดึงค่าจาก JSONB ซ้ำ ๆ ในทุก query
แนวทางนี้เหมาะมากกับข้อมูลที่ยังอยากเก็บต้นทางไว้ใน JSONB แต่มีบาง field ที่เริ่มถูกใช้งานแบบกึ่งโครงสร้างถาวรแล้ว
5) ทำ index เท่าที่จำเป็น
JSONB index มีความสามารถสูง แต่ไม่ใช่ว่ายิ่งมีมากยิ่งดี การสร้าง index ควรอ้างอิงจากรูปแบบ query ที่ใช้งานจริง เช่น:
- ค้นหาว่ามี key ที่ต้องการหรือไม่
- ค้นหาค่าภายในบาง field
- filter จาก attribute ที่ใช้บ่อย
ถ้าสร้าง index แบบไม่วางแผน อาจทำให้ต้นทุนการเขียนข้อมูลสูงขึ้นโดยไม่คุ้มค่า
6) normalize เมื่อข้อมูลเริ่มมีความสัมพันธ์ชัด
เมื่อข้อมูลใน JSONB เริ่มมีลักษณะดังนี้:
- ถูก join บ่อย
- ถูกอัปเดตแยกส่วนบ่อย
- กลายเป็นแกนธุรกิจของระบบ
นั่นคือสัญญาณว่าควรย้ายข้อมูลกลับมาเป็นตารางปกติ
JSONB เหมาะกับข้อมูลเสริมที่ยังไม่คงรูป แต่เมื่อข้อมูลเริ่มนิ่งและมีความสัมพันธ์ชัด การ normalize จะช่วยให้ระบบแข็งแรงและดูแลง่ายกว่าในระยะยาว
สัญญาณเตือนว่า JSONB เริ่มถูกใช้เกินพอดี
หากทีมเริ่มเจอสถานการณ์ต่อไปนี้ ควรกลับมาทบทวนการออกแบบ:
- ทีมเดียวกันเขียน query คนละแบบสำหรับข้อมูลเดียวกัน
- ต้อง cast ค่าเดิมซ้ำ ๆ ทุกครั้ง
- มี index เพิ่มขึ้นเรื่อย ๆ แต่ query ยังช้า
- logic ตรวจสอบข้อมูลไปกองอยู่ในแอปทั้งหมด
สัญญาณเหล่านี้มักแปลว่า JSONB กำลังถูกใช้แทน schema ที่ควรออกแบบให้ชัดตั้งแต่ต้น
แนวทางออกแบบที่ใช้งานได้จริง
เหมาะจะใช้คอลัมน์ปกติสำหรับ
idstatustypeamountcreated_atupdated_at
ข้อมูลเหล่านี้มักเป็นแกนธุรกิจ ถูกใช้อ้างอิงบ่อย และควรมีชนิดข้อมูลที่ชัดเจน
เหมาะจะใช้ JSONB สำหรับ
- display preferences
- raw webhook payload
- feature flags ราย user
- ข้อมูลเสริมที่ยังไม่คงรูป
ข้อมูลกลุ่มนี้มักเปลี่ยนแปลงง่าย มีโครงสร้างไม่แน่นอน หรือมีเฉพาะบางกรณี
JSONB ควรให้ความยืดหยุ่น ไม่ใช่เพิ่มความคลุมเครือ
อีกจุดที่หลายคนมองข้ามคือ อย่าใช้ JSONB เป็นข้ออ้างในการไม่ออกแบบ schema
JSONB เหมาะกับการรองรับความเปลี่ยนแปลง แต่ไม่ได้หมายความว่าควรปล่อยให้ข้อมูลไม่มีมาตรฐาน หากเริ่มต้นดี PostgreSQL สามารถให้ทั้งสองอย่างพร้อมกันได้ คือ:
- ความยืดหยุ่นแบบ document
- ความเข้มแข็งของ relational database
สรุป
การใช้ JSONB อย่างมีวินัยช่วยให้ฐานข้อมูลรองรับความยืดหยุ่นได้โดยไม่สูญเสียความเป็นระเบียบ
แนวทางสำคัญคือ:
- เก็บข้อมูลแกนธุรกิจไว้ในคอลัมน์ปกติ
- ใช้ JSONB สำหรับข้อมูลเสริมที่เปลี่ยนบ่อย
- กำหนด contract ของ key ให้ชัด
- แยก version เมื่อโครงสร้างมีโอกาสเปลี่ยน
- ใช้ constraint และ index เท่าที่จำเป็น
- รีบ normalize เมื่อข้อมูลเริ่มนิ่งและมีความสัมพันธ์ชัด
หากออกแบบตั้งแต่วันแรกอย่างมีหลักคิด ฐานข้อมูลจะเติบโตได้อย่างเป็นระเบียบ ไม่กลายเป็นกล่องสุ่มใน production