0Pricing
MongoDB Academy · บทเรียน

รูปแบบการอ้างอิงแบบขยายและชุดย่อย

ผู้เรียนจะฝังชุดย่อยที่คัดสรรแล้วของฟิลด์ที่เข้าถึงบ่อยจากเอกสารที่ถูกอ้างอิง เพื่อกำจัดการเชื่อมตารางด้วย $lookup ในเส้นทางการอ่านที่มีการใช้งานสูง

รูปแบบการอ้างอิงแบบขยายและชุดย่อย เป็นบทเรียน MongoDB Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน MongoDB Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส MongoDB Academy มีบทเรียนทั้งหมด 4 บทเรียน

บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ

The $lookup Performance Problem

MongoDB's $lookup stage joins collections at query time by performing an in-memory hash join. For frequent, high-traffic queries, this join cost can dominate latency. If your order list endpoint performs a $lookup to fetch customer names and emails for every order in every request, you pay the join cost every single time. The Extended Reference Pattern eliminates this cost by embedding a targeted subset of the referenced document's fields.

The Extended Reference Pattern

The Extended Reference Pattern embeds a carefully selected subset of fields from a referenced document directly inside the referencing document. Instead of storing only a customer _id in an order and looking up the customer for every order display, embed the customer's name and email — the fields needed to render the order — directly in the order document. The full customer record still exists separately for updates.

// Without Extended Reference: requires $lookup on every order read
{ orderId: 'ORD-1234', customerId: ObjectId('...'), total: 99.99 }

// With Extended Reference: order carries the fields it needs
{
  orderId: 'ORD-1234',
  customer: {
    _id: ObjectId('...'),       // reference for updates
    name: 'Alice Smith',        // duplicated for fast reads
    email: 'alice@example.com'  // duplicated for fast reads
  },
  total: 99.99,
  status: 'shipped'
}

Choosing Which Fields to Duplicate

Only duplicate fields that are read frequently alongside the referencing document and change rarely. A customer's name and email address are good candidates — they are needed to display orders and rarely change. A customer's billing address or loyalty points balance are poor candidates — they change often and would require updating all embedded copies whenever they change. The goal is to eliminate joins on the hot read path without creating an impossible sync burden.

Handling Updates to Duplicated Fields

When a customer changes their name (an infrequent event), you must update the customer's main document and all order documents that embed their name. This is called a write fan-out. Use updateMany() to propagate the change. The key insight is that this infrequent write cost is worth paying if it eliminates join overhead from thousands of daily reads.

// Update the source document first
await db.collection('customers').updateOne(
  { _id: customerId },
  { $set: { name: 'Alice Johnson' } }
)

// Then propagate to all embedded references
await db.collection('orders').updateMany(
  { 'customer._id': customerId },
  { $set: { 'customer.name': 'Alice Johnson' } }
)

The Subset Pattern: Trimming Large Arrays

The Subset Pattern addresses a different challenge: when a document embeds an array that grows very large over time, every read fetches the entire array — even if the application only ever displays the first N elements. A product with 5,000 reviews embeds all 5,000 in a single document, but the UI shows only the top 10. The Subset Pattern splits the array: keep the most recent or most relevant N items in the main document and move the rest to a separate collection.

// Anti-pattern: all 5000 reviews in the product document
{
  _id: productId,
  name: 'Widget Pro',
  reviews: [ /* 5000 review objects */ ]  // fetched on every product read
}

// Subset Pattern: only recent 10 reviews in main document
{
  _id: productId,
  name: 'Widget Pro',
  recentReviews: [ /* 10 most recent */ ],  // fast, always-displayed
  reviewCount: 5000,
  avgRating: 4.3
  // full reviews in separate 'reviews' collection
}

Maintaining the Subset With $push and $slice

Keep the embedded subset up to date using a $push with the $slice modifier. After adding a new review to the recentReviews array with $push, apply $slice: -10 to trim the array to the last 10 elements. This is a single atomic operation — MongoDB atomically pushes and slices in the same update.

const newReview = { userId: ObjectId('...'), rating: 5, comment: 'Great product!', date: new Date() }

// Add to main reviews collection
await db.collection('reviews').insertOne({ productId, ...newReview })

// Update subset in product document — keep last 10
await db.collection('products').updateOne(
  { _id: productId },
  {
    $push: {
      recentReviews: {
        $each: [newReview],
        $sort: { date: -1 },
        $slice: 10
      }
    },
    $inc: { reviewCount: 1 }
  }
)

Querying Beyond the Subset

For the common case — displaying a product page with recent reviews — read the main product document and use the embedded recentReviews. For 'load more' or pagination, query the separate reviews collection with the product's _id. This two-tier approach gives fast common-case performance without sacrificing the ability to access all reviews when needed.

// Fast product page: use embedded subset
const product = await db.collection('products').findOne(
  { _id: productId },
  { projection: { name: 1, price: 1, avgRating: 1, recentReviews: 1 } }
)

// Load more reviews: query the full reviews collection
const moreReviews = await db.collection('reviews')
  .find({ productId })
  .sort({ date: -1 })
  .skip(10)
  .limit(10)
  .toArray()

Comparing Extended Reference and Subset

Extended Reference duplicates fields from a referenced document into the referencing document to eliminate joins on read. It handles the case of one-to-many relationships where the 'one' side has fields needed alongside every 'many' record. Subset Pattern duplicates the most relevant N elements of a large array into the main document to avoid loading thousands of items on every read. Both trade write complexity for read performance.

When NOT to Use These Patterns

Avoid these patterns when the duplicated data changes frequently — the write fan-out cost becomes prohibitive. If customer emails change daily, propagating to millions of orders is impractical. Also avoid them when data consistency is critical and eventual propagation is unacceptable — in the window between a name change and the fan-out completing, some orders will show stale data. For strict consistency requirements, stick with references and accept the join cost.

Real-World Example: E-Commerce Orders

In an e-commerce platform, orders are read far more often than customers update their profiles. Embedding the customer's shipping address snapshot at order time (Extended Reference) is actually correct business behaviour — orders should remember the address used at purchase, even if the customer moves later. This is a case where the pattern aligns with domain logic, not just performance, making it doubly appropriate.

// Order with Extended Reference — snapshot of address at purchase time
{
  orderId: 'ORD-5678',
  customer: {
    _id: ObjectId('...'),
    name: 'Bob Chen',
    shippingAddress: {
      street: '42 Elm Street',
      city: 'Istanbul',
      country: 'TR'
    }  // snapshot at order time — correct even if customer moves later
  },
  items: [{ sku: 'WGT-001', qty: 2, price: 49.99 }],
  total: 99.98
}

Performance Measurement and Validation

Before applying these patterns, measure baseline performance with explain('executionStats') to identify where read latency is actually coming from. After applying a pattern, remeasure to confirm the improvement. Use db.collection.stats() to compare average document sizes and check that the embedded fields are not pushing documents close to the 16 MB limit. Patterns should be justified by data, not assumed to always help.

// Measure before and after applying Extended Reference
db.orders.find({ 'customer._id': someId }).explain('executionStats')

// Check average document size after embedding
db.orders.stats().avgObjSize  // in bytes

Quick Check

Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.

Lesson Recap

In this lesson you learned: the Extended Reference Pattern embeds selected fields from a referenced document to eliminate joins on hot read paths — best for fields that are read often and change rarely, the Subset Pattern keeps the most relevant N array elements in the main document while archiving the rest in a separate collection, and both patterns trade infrequent write fan-out for dramatically faster reads. Next up we explore the Polymorphic and Schema Versioning Patterns.

คำถามที่พบบ่อย

บทเรียน “รูปแบบการอ้างอิงแบบขยายและชุดย่อย” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “รูปแบบการอ้างอิงแบบขยายและชุดย่อย” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส MongoDB Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส MongoDB Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “รูปแบบการอ้างอิงแบบขยายและชุดย่อย”

ผู้เรียนจะฝังชุดย่อยที่คัดสรรแล้วของฟิลด์ที่เข้าถึงบ่อยจากเอกสารที่ถูกอ้างอิง เพื่อกำจัดการเชื่อมตารางด้วย $lookup ในเส้นทางการอ่านที่มีการใช้งานสูง คุณปฏิบัติ MongoDB Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน MongoDB Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน MongoDB Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “รูปแบบการอ้างอิงแบบขยายและชุดย่อย” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน MongoDB Academy นี้ได้ไหม

ได้ บทเรียน MongoDB Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. รูปแบบบักเก็ตและค่าที่คำนวณล่วงหน้า
  2. รูปแบบการอ้างอิงแบบขยายและชุดย่อย
  3. รูปแบบโพลีมอร์ฟิกและการกำหนดเวอร์ชันสคีมา
  4. รูปแบบค่าผิดปกติและโครงสร้างต้นไม้
← กลับไปที่ MongoDB Academy