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

คิดแบบ document

ถ้าคุณมา MongoDB จากพื้นฐาน relational นิสัยที่เลิกยากที่สุดคือสิ่งที่เคยช่วยคุณได้ดีที่สุดใน SQL นั่นคือการแยกข้อมูลออกเป็นตารางแคบ ๆ หลายตาราง แต่ละตารางเก็บของอย่างเดียว แล้วค่อย join กลับมารวมกันตอน query วินัยแบบนี้เรียกว่า normalization มีขึ้นเพื่อกำจัดความซ้ำซ้อน ให้ข้อเท็จจริงหนึ่งอยู่ในที่เดียว

MongoDB ขอให้คุณคิดกลับด้าน คุณไม่ได้เริ่มจาก “มี entity อะไรบ้าง และจะเลี่ยงการเก็บซ้ำได้อย่างไร” แต่เริ่มจาก “แอปถามหาอะไร และจะทำให้การอ่านครั้งเดียวคืนทุกอย่างที่หน้าจอหนึ่งต้องการได้อย่างไร”

หน่วยของการจัดเก็บคือ document — โครงสร้างแบบ JSON ที่ซ้อนกันได้ เก็บ array และ sub-object ได้ ไม่ใช่แค่ column แบน ๆ เมื่อ document เก็บข้อมูลที่เกี่ยวข้องไว้ในตัวเองได้ คำถามที่ครอบงำทุกการตัดสินใจในการ modeling จึงไม่ใช่ “column นี้ควรอยู่ตารางไหน” อีกต่อไป แต่เป็น “จะ nest ข้อมูลที่เกี่ยวข้องไว้ใน document หรือแยกเก็บแล้วใช้ reference ชี้ไปหา” ทางเลือกเดียวนี้ — embedding กับ referencing — คือแกนของทั้งโมดูล

ใน 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
การออกแบบเชิงสัมพันธ์ปรับให้เหมาะกับการจัดเก็บแล้วจ่ายตอนอ่าน ส่วนการออกแบบแบบ document ปรับให้เหมาะกับการอ่านและตัดสินใจเรื่องความซ้ำซ้อนไว้ล่วงหน้า

ไม่มีแนวทางไหน “ถูกต้อง” เสมอไป สัญชาตญาณแบบ 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 ที่เหลือของโมดูลนี้จะให้เครื่องมือคุณไปตัดสินใจ

บทเรียนต่าง ๆ ต่อยอดจากทางเลือกหลักออกไปสู่โครงสร้างที่นำกลับมาใช้ซ้ำได้:

  1. Embedding กับ referencing — สองวิธีในการเชื่อมโยง document เข้าด้วยกัน และข้อแลกเปลี่ยนเรื่อง read locality, ความซ้ำซ้อน และการเติบโตของ document
  2. Modeling relationships — one-to-one, one-to-many และ many-to-many รวมถึงข้อจำกัด document ขนาด 16 MB และอันตรายของ array ที่ไม่มีขอบเขต
  3. Schema design patterns — Subset, Computed, Bucket และ Extended Reference pattern ที่แก้ปัญหาที่เกิดซ้ำ ๆ
  4. Schema validation — ทำให้ schema เป็นแบบ optional-but-enforced ด้วยกฎ $jsonSchema
  5. Anti-patterns — ความผิดพลาดในการ modeling ที่ดูเหมือนไม่มีปัญหาตอนแรกแต่เจ็บตอนสเกล และวิธีแก้แต่ละข้อ
  • MongoDB เป็นแบบ schema-flexible ไม่ใช่ schema-less document ใน collection เดียวกันหน้าตาต่างกันได้ แต่รูปร่างที่ตั้งใจและสม่ำเสมอคือสิ่งที่ทำให้ collection เร็วและดูแลง่าย
  • “Model for your queries” แปลว่าคุณต้องรู้ access pattern หลักก่อนออกแบบ เพราะ schema ที่สมบูรณ์แบบสำหรับการอ่านรูปแบบหนึ่ง อาจผิดสนิทสำหรับอีกรูปแบบ
  • Embedding ไม่ได้ดีกว่า referencing เสมอ และ referencing ก็ไม่ได้ “ถูกต้อง” กว่าเสมอ ทุกบทเรียนที่นี่คือการอ่านข้อแลกเปลี่ยน ไม่ใช่การท่องกฎ
  • ความซ้ำซ้อนใน MongoDB ยอมรับได้ และบางครั้งก็จงใจให้เกิด ต้นทุนของสำเนาคือการคอยทำให้ทุกที่ตรงกัน แลกกับการอ่านที่เร็วและง่ายกว่า
ตัวเลือกBenefitCost
Embeddingอ่านครั้งเดียวได้ทุกอย่าง ไม่มี join, เขียนแบบ atomicdocument โตได้ไม่มีขอบเขตถ้าไม่ระวัง เสี่ยงชนเพดาน 16 MB และมีข้อมูลซ้ำซ้อน
Referencingdocument เล็กและคงที่ ไม่มีข้อมูลซ้ำซ้อน แชร์ข้าม 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 ที่เปลี่ยนแปลงบ่อยกว่า

คำถามหลักที่ขับเคลื่อนการ data modeling ของ MongoDB คืออะไร?
เทียบกับ normalization แบบ relational แล้ว การ modeling แบบ document ปรับให้เหมาะกับอะไรเป็นหลัก?
ทำไมการ embed รายการสินค้าไว้ใน document คำสั่งซื้อจึงน่าสนใจ?