Embedding Versus Referencing and One-to-Many left a gap: the post page wants its comments in one read, but comments are unbounded and also needed on their own. Two patterns close it by duplicating a little data on purpose. The subset pattern keeps the hot slice in the parent and the full set in its own collection; $push with $sort and $slice maintains that slice in one atomic update, so the array can never grow. The extended reference copies the few fields of a referenced document that the screen needs — an author's display name, not the bio and email — into the referring document, replacing a join with a projection.
const c = { _id: new ObjectId(), authorId: 4001, body: 'bounded by slice',
createdAt: new Date() };
db.posts_subset.updateOne({ _id: 28 }, { $inc: { commentCount: 1 },
$push: { recentComments: { $each: [c], $sort: { createdAt: -1 }, $slice: 3 } } });
const p = db.posts_subset.findOne({ _id: 28 });
print('kept ' + p.recentComments.length + ' of ' + p.commentCount + ' in ' + bsonsize(p)
+ ' B; collection ' + (db.posts_subset.stats().size / 1048576).toFixed(1) + ' MB');
cost('post page (subset)', ex(db.posts_subset.find({ slug: 'post-28' })));
cost('comments + $lookup', db.comments.explain('executionStats').aggregate([
{ $match: { postId: 28 } }, { $lookup: { from: 'authors', localField: 'authorId',
foreignField: '_id', as: 'author' } }]));
cost('comments_xref only', ex(db.comments_xref.find({ postId: 28 })));
print('rename fan-out: ' + db.comments_xref.updateMany({ 'author._id': 77 },
{ $set: { 'author.name': 'Author 77 (renamed)' } }).modifiedCount + ' documents');kept 3 of 51 in 1804 B; collection 3.7 MB post page (subset) keys=1 docs=1 ms=0 comments + $lookup keys=100 docs=100 ms=2 comments_xref only keys=50 docs=50 ms=1 rename fan-out: 11 documents
The subset shape keeps the single-document post page — docs=1 — at 1,804 bytes instead of 12,841, and the collection drops from 13.4 MB to 3.7 MB, a 72% cut. The array stays three elements long whatever happens to the post, so the 16 MB wall and the rewrite cost of One-to-Many are gone. The counter still reads 51: $inc tracks the truth while $slice discards the tail, and the rest are one indexed query away.
The extended reference halves the work of rendering that comment list — 50 documents instead of 100, since the $lookup reads an author document per comment. The price is 13% more storage and a write obligation when author 77 renames; measure that fan-out before you fear it, at eleven documents and 3 ms for one updateMany against an index on author._id. Copy only fields that rarely change and that the reader sees.