คิดแบบ document
ถ้าคุณมา MongoDB จากพื้นฐาน relational นิสัยที่เลิกยากที่สุดคือสิ่งที่เคยช่วยคุณได้ดีที่สุดใน SQL นั่นคือการแยกข้อมูลออกเป็นตารางแคบ ๆ หลายตาราง แต่ละตารางเก็บของอย่างเดียว แล้วค่อย join กลับมารวมกันตอน query วินัยแบบนี้เรียกว่า normalization มีขึ้นเพื่อกำจัดความซ้ำซ้อน ให้ข้อเท็จจริงหนึ่งอยู่ในที่เดียว
MongoDB ขอให้คุณคิดกลับด้าน คุณไม่ได้เริ่มจาก “มี entity อะไรบ้าง และจะเลี่ยงการเก็บซ้ำได้อย่างไร” แต่เริ่มจาก “แอปถามหาอะไร และจะทำให้การอ่านครั้งเดียวคืนทุกอย่างที่หน้าจอหนึ่งต้องการได้อย่างไร”
หน่วยของการจัดเก็บคือ document — โครงสร้างแบบ JSON ที่ซ้อนกันได้ เก็บ array และ sub-object ได้ ไม่ใช่แค่ column แบน ๆ เมื่อ document เก็บข้อมูลที่เกี่ยวข้องไว้ในตัวเองได้ คำถามที่ครอบงำทุกการตัดสินใจในการ modeling จึงไม่ใช่ “column นี้ควรอยู่ตารางไหน” อีกต่อไป แต่เป็น “จะ nest ข้อมูลที่เกี่ยวข้องไว้ใน document หรือแยกเก็บแล้วใช้ reference ชี้ไปหา” ทางเลือกเดียวนี้ — embedding กับ referencing — คือแกนของทั้งโมดูล
นิสัยเชิงสัมพันธ์ กับการคิดแบบ document
หัวข้อที่มีชื่อว่า “นิสัยเชิงสัมพันธ์ กับการคิดแบบ document”ใน relational schema คุณออกแบบตามรูปร่างของข้อมูล แล้วปล่อยให้ query จ่ายต้นทุนตอนประกอบข้อมูลกลับคืน ส่วนใน document schema คุณออกแบบตาม query แล้วให้รูปร่างของข้อมูลรับต้นทุนไว้ล่วงหน้า สองแนวคิดนี้ดึงไปคนละทิศทาง:
flowchart TB
subgraph Relational["Relational — model for storage"]
R0["Start: list the entities"] --> R1["Split into normalized tables"]
R1 --> R2["No duplication — one fact, one place"]
R2 --> R3["Reassemble with JOINs at read time"]
end
subgraph Document["Document — model for access"]
D0["Start: list the queries"] --> D1["Group data that is read together"]
D1 --> D2["Embed or reference based on access"]
D2 --> D3["One read returns a whole screen"]
end ไม่มีแนวทางไหน “ถูกต้อง” เสมอไป สัญชาตญาณแบบ relational ยอดเยี่ยมเมื่อข้อมูลเกี่ยวพันกันสูงและมีการอัปเดตจากหลายทิศทาง ส่วนสัญชาตญาณแบบ document โดดเด่นเมื่อแอปดึงข้อมูลกลุ่มเดิมที่มีรูปร่างซ้ำ ๆ — ผู้ใช้กับการตั้งค่า คำสั่งซื้อกับรายการสินค้าในนั้น บทความกับ tag ตรงนี้เอง MongoDB ให้คุณเก็บข้อมูลกลุ่มนั้นในรูปแบบที่แอปต้องใช้จริง
ตัวอย่างจริง: คำสั่งซื้อ
หัวข้อที่มีชื่อว่า “ตัวอย่างจริง: คำสั่งซื้อ”ลองดูคำสั่งซื้อออนไลน์ ระบบ relational จะเก็บคำสั่งซื้อไว้ตารางหนึ่ง รายการสินค้าอีกตาราง ลูกค้าตารางที่สาม แล้วเย็บเข้าด้วยกันด้วย foreign key ส่วน MongoDB เก็บคำสั่งซื้อทั้งก้อนไว้ใน document เดียวได้เลย โดย embed รายการสินค้าเป็น array การอ่านคำสั่งซื้อจึงเหลือ operation เดียว แทนที่จะต้อง join สามครั้ง:
{ "_id": "order_5001", "customer": "Grace Hopper", "placedAt": "2026-03-14T09:12:00Z", "status": "shipped", "items": [ { "sku": "BK-204", "title": "Distributed Systems", "qty": 1, "price": 42.0 }, { "sku": "BK-991", "title": "The Mythical Man-Month", "qty": 2, "price": 18.5 } ], "total": 79.0}document ก้อนเดียวนั้นคือทุกอย่างที่หน้า “รายละเอียดคำสั่งซื้อ” ต้องการ ไม่มี join ไม่มี round trip รอบสอง เพราะข้อมูลมีรูปร่างเป็นคำตอบอยู่แล้ว
ข้อแลกเปลี่ยนคือชื่อและราคาสินค้ากลายเป็นสำเนาของข้อมูลที่อาจอยู่ใน products ด้วย และสำเนาก็คลาดเคลื่อนได้ การตัดสินว่าเมื่อไรการแลกนี้คุ้ม คือหัวใจของ data modeling ที่เหลือของโมดูลนี้จะให้เครื่องมือคุณไปตัดสินใจ
โมดูลนี้ครอบคลุมอะไรบ้าง
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไรบ้าง”บทเรียนต่าง ๆ ต่อยอดจากทางเลือกหลักออกไปสู่โครงสร้างที่นำกลับมาใช้ซ้ำได้:
- Embedding กับ referencing — สองวิธีในการเชื่อมโยง document เข้าด้วยกัน และข้อแลกเปลี่ยนเรื่อง read locality, ความซ้ำซ้อน และการเติบโตของ document
- Modeling relationships — one-to-one, one-to-many และ many-to-many รวมถึงข้อจำกัด document ขนาด 16 MB และอันตรายของ array ที่ไม่มีขอบเขต
- Schema design patterns — Subset, Computed, Bucket และ Extended Reference pattern ที่แก้ปัญหาที่เกิดซ้ำ ๆ
- Schema validation — ทำให้ schema เป็นแบบ optional-but-enforced ด้วยกฎ
$jsonSchema - Anti-patterns — ความผิดพลาดในการ modeling ที่ดูเหมือนไม่มีปัญหาตอนแรกแต่เจ็บตอนสเกล และวิธีแก้แต่ละข้อ
เคล็ดลับและข้อควรระวัง
หัวข้อที่มีชื่อว่า “เคล็ดลับและข้อควรระวัง”- MongoDB เป็นแบบ schema-flexible ไม่ใช่ schema-less document ใน collection เดียวกันหน้าตาต่างกันได้ แต่รูปร่างที่ตั้งใจและสม่ำเสมอคือสิ่งที่ทำให้ collection เร็วและดูแลง่าย
- “Model for your queries” แปลว่าคุณต้องรู้ access pattern หลักก่อนออกแบบ เพราะ schema ที่สมบูรณ์แบบสำหรับการอ่านรูปแบบหนึ่ง อาจผิดสนิทสำหรับอีกรูปแบบ
- Embedding ไม่ได้ดีกว่า referencing เสมอ และ referencing ก็ไม่ได้ “ถูกต้อง” กว่าเสมอ ทุกบทเรียนที่นี่คือการอ่านข้อแลกเปลี่ยน ไม่ใช่การท่องกฎ
- ความซ้ำซ้อนใน MongoDB ยอมรับได้ และบางครั้งก็จงใจให้เกิด ต้นทุนของสำเนาคือการคอยทำให้ทุกที่ตรงกัน แลกกับการอ่านที่เร็วและง่ายกว่า
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Embedding | อ่านครั้งเดียวได้ทุกอย่าง ไม่มี join, เขียนแบบ atomic | document โตได้ไม่มีขอบเขตถ้าไม่ระวัง เสี่ยงชนเพดาน 16 MB และมีข้อมูลซ้ำซ้อน |
| Referencing | document เล็กและคงที่ ไม่มีข้อมูลซ้ำซ้อน แชร์ข้าม parent ได้หลายตัว | ต้อง query เพิ่มหรือ $lookup เพื่อ resolve ข้อมูล เพิ่ม latency ในการอ่าน |
| Bucket pattern (สำหรับข้อมูลปริมาณสูงแบบ time-series) | ลดจำนวน document ต่อหน่วยเวลา ลด overhead ของ index | ต้องออกแบบขนาด bucket ล่วงหน้า เพิ่ม logic การจัดกลุ่มข้อมูล |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- คิดว่า MongoDB ไม่มี schema เลย — ทุก document มี shape อยู่แล้วโดยธรรมชาติจากวิธีที่แอปเขียนลงไป ต่างกันแค่ database ไม่บังคับ shape นั้นให้ เว้นแต่คุณจะเพิ่ม validation เอง
- คิดว่ามี “schema ที่ถูกต้องหนึ่งเดียว” ต่อ entity — schema ที่ดีขึ้นกับ access pattern ของแต่ละแอป entity เดียวกันจึง model ต่างกันคนละเรื่องได้ในสองโปรเจกต์
- คิดว่าเปลี่ยน schema ทีหลังไม่ได้เพราะ MongoDB ไม่มี migration tool built-in — เปลี่ยนได้เสมอ เพียงแต่ต้องเขียน migration script เอง และวางแผน rollout ด้วย
validationLevelที่เหมาะสม
💡 ตัวอย่างจากของจริง
Bosch — แพลตฟอร์ม IoT ของ Bosch เก็บข้อมูล sensor จากอุปกรณ์นับล้านชิ้นด้วย Bucket pattern แทนที่จะสร้างหนึ่ง document ต่อหนึ่ง reading เพื่อจำกัดการเติบโตของ document และลด overhead ของ index
ADEO (กลุ่มค้าปลีกที่เป็นเจ้าของ Leroy Merlin) — ใช้ MongoDB จัดการ product catalog ขนาดใหญ่ ที่ต้องตัดสินใจในทุกความสัมพันธ์ว่าจะ embed field ที่แสดงบ่อย หรือ reference ไปยัง source ที่เปลี่ยนแปลงบ่อยกว่า