Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026
MongoDB
MongoDB là document database lưu dữ liệu dạng BSON document trong collections. Nó phù hợp khi domain có cấu trúc document tự nhiên, schema thay đổi nhanh, cần scale ngang, hoặc dữ liệu thường được đọc/ghi theo aggregate document.
Không nên chọn MongoDB chỉ để né schema design. Nếu dữ liệu có quan hệ chặt, cần join phức tạp, transaction nhiều bảng và constraint mạnh, PostgreSQL có thể phù hợp hơn.
Document là đơn vị dữ liệu dạng BSON tương tự JSON object. Collection là nhóm documents, tương tự bảng nhưng không bắt buộc mọi document có cùng shape. Database là namespace chứa nhiều collections.
Dù MongoDB linh hoạt schema, production vẫn cần quy ước schema rõ ràng ở app hoặc bằng JSON Schema validation để tránh dữ liệu bẩn và khó query về sau.
BSON là binary serialization format MongoDB dùng để lưu documents. Nó hỗ trợ thêm types như ObjectId, Date, Decimal128, binary, int32/int64 và nested structures. JSON là text format phổ biến để trao đổi dữ liệu.
Điểm phỏng vấn quan trọng: MongoDB document không chỉ là JSON text. Type mismatch giữa string date và Date, number precision, ObjectId string và ObjectId thật có thể làm query/index không hoạt động như mong muốn.
Các thao tác CRUD cơ bản làm việc với collection: findOne lấy một document, find trả cursor, insertOne thêm document, updateOne cập nhật document match đầu tiên.
Ví dụ:
await users.insertOne({ email: "a@example.com", name: "Ada" })
await users.updateOne({ email: "a@example.com" }, { $set: { name: "Ada Lovelace" } })
const user = await users.findOne({ email: "a@example.com" })Cần luôn hiểu filter nào đang dùng index, nếu không collection scan sẽ làm chậm khi dữ liệu lớn.
Index giúp query tìm document nhanh hơn và hỗ trợ sort. Trade-off là tốn disk/RAM và làm write chậm hơn vì insert/update/delete phải cập nhật index.
Ví dụ:
db.users.createIndex({ email: 1 }, { unique: true })Không index mọi field.
Hãy dựa vào query shape thật, selectivity, sort pattern và explain("executionStats").
Multikey index được tạo khi index field chứa array. MongoDB index từng element trong array, giúp query match element nhanh hơn.
Ví dụ:
db.posts.createIndex({ tags: 1 })
db.posts.find({ tags: "mongodb" })Cần cẩn thận với compound multikey indexes vì array nhiều chiều hoặc nhiều array fields có thể tạo index entries lớn, ảnh hưởng write performance và storage.
ObjectId là kiểu dữ liệu thường dùng cho field _id mặc định trong MongoDB. Nó có kích thước 12 bytes và được sinh sao cho gần như unique, có chứa phần timestamp nên có thể suy ra thời điểm tạo tương đối.
Không nên nhầm ObjectId("...") với string thường. Nếu app lưu id dạng string nhưng query bằng ObjectId hoặc ngược lại, query có thể không match và index cũng không hoạt động như mong muốn.
Projection giới hạn fields trả về, giảm network payload, memory và serialization cost. Nó cũng giúp tránh lộ fields nhạy cảm nếu dùng cẩn thận.
Ví dụ:
db.users.find({ active: true }, { email: 1, name: 1, _id: 0 })Projection không thay thế authorization hay response schema ở app layer, nhưng là tối ưu quan trọng cho documents lớn.
Có. MongoDB hỗ trợ multi-document transactions, kể cả trên replica sets và sharded clusters. Nhưng transaction không nên là mặc định cho mọi workflow vì nó tăng overhead và complexity.
Dùng transaction khi cần atomicity qua nhiều documents/collections. Nếu có thể thiết kế aggregate sao cho invariant nằm trong một document, atomic single-document update thường đơn giản và hiệu quả hơn.
Replica set là nhóm mongod processes giữ cùng một dataset để cung cấp redundancy và high availability. Một primary nhận writes, secondaries replicate oplog và có thể phục vụ reads tùy read preference.
Khi primary fail, election chọn primary mới. App cần dùng connection string đúng replica set và xử lý retryable writes/reads vì failover có thể gây lỗi tạm thời.
Embed khi dữ liệu con thuộc aggregate cha, thường được đọc cùng nhau, kích thước bounded và không cần update độc lập nhiều. Reference khi dữ liệu lớn, quan hệ many-to-many, cần query độc lập, hoặc document có thể vượt size limit.
Ví dụ embed addresses trong user nếu số lượng nhỏ:
{
_id: ObjectId("..."),
email: "a@example.com",
addresses: [{ city: "HCM", type: "shipping" }]
}Thiết kế MongoDB nên dựa vào query pattern, không phải chuyển 1-1 từ ERD relational.
MongoDB có giới hạn kích thước document, nên array unbounded như comments, events, logs, transactions không nên embed vô hạn trong một document cha.
Nếu collection có thể tăng không giới hạn, dùng reference hoặc bucket pattern. Ví dụ order có line items bounded có thể embed, nhưng user có hàng triệu events thì events nên là collection riêng, có index theo userId và createdAt.
Compound index order phụ thuộc query shape. Quy tắc thực tế thường là equality fields trước, sort fields tiếp theo, range fields sau cùng. Index { tenantId: 1, status: 1, createdAt: -1 } tốt cho query lọc tenant/status và sort createdAt.
Ví dụ:
db.orders.createIndex({ tenantId: 1, status: 1, createdAt: -1 })Không có một thứ tự đúng cho mọi query.
Nếu query khác nhau nhiều, cần cân nhắc index riêng hoặc đổi access pattern.
TTL index tự động xóa documents sau một thời gian dựa trên date field. Nó phù hợp cho session, token, temporary verification, logs ngắn hạn hoặc cache documents.
Ví dụ:
db.sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 })TTL deletion không realtime tuyệt đối; background process chạy định kỳ.
Không dùng TTL nếu cần xóa đúng chính xác từng giây cho business critical workflow.
Aggregation pipeline xử lý data qua nhiều stages như $match, $group, $project, $sort, $lookup, $unwind. Nó phù hợp reporting, transformation, analytics nhẹ và server-side data shaping.
Ví dụ tính revenue theo ngày:
db.orders.aggregate([
{ $match: { status: "paid" } },
{ $group: { _id: "$day", revenue: { $sum: "$total" } } },
{ $sort: { _id: 1 } }
])Đặt $match sớm để giảm dữ liệu đi qua pipeline và tận dụng index.
$lookup join documents giữa collections trong aggregation. Nó hữu ích nhưng không nên biến MongoDB thành relational database. Nếu query thường xuyên cần join sâu/nhiều collection, schema có thể đang sai cho MongoDB.
Dùng $lookup cho lookup có kiểm soát, data nhỏ hoặc admin/reporting. Với high-traffic read path, cân nhắc embed, denormalize có kiểm soát, hoặc materialized view để tránh join đắt.
MongoDB đảm bảo atomicity ở mức một document. Các update operators như $set, $inc, $push, $addToSet giúp cập nhật field/array mà không read-modify-write ở app.
Ví dụ tăng counter an toàn:
db.products.updateOne(
{ _id: productId, stock: { $gt: 0 } },
{ $inc: { stock: -1 } }
)Nếu invariant nằm trong một document, thiết kế document đúng có thể tránh transaction phức tạp.
Read concern quyết định mức consistency/visibility của reads. Write concern quyết định mức acknowledgement/durability của writes, ví dụ majority yêu cầu đa số replica xác nhận.
Trade-off: concern càng mạnh thì consistency/durability tốt hơn nhưng latency có thể cao hơn. Với dữ liệu quan trọng như payment/order, thường cần write concern majority; với analytics/event không critical, có thể chọn nhẹ hơn tùy rủi ro.
Sharding chia dữ liệu ngang qua nhiều shards để scale storage và throughput. MongoDB route query qua shard key. Shard key quyết định data distribution và query routing.
Chọn shard key sai có thể gây hot shard, scatter-gather query hoặc chunk migration nhiều. Shard key tốt có cardinality cao, phân phối đều, ổn định và xuất hiện trong query pattern chính.
Denormalization copy một số data sang document khác để tối ưu read path và tránh join. Nó phù hợp khi data được đọc nhiều, thay đổi ít hoặc có thể chấp nhận eventual consistency.
Ví dụ order lưu snapshot customerName tại thời điểm mua. Khi customer đổi tên, order lịch sử có thể không cần update. Nhưng nếu data copy phải luôn đồng bộ, cần update strategy, event handler hoặc transaction, nếu không sẽ tạo inconsistency.
Bucket pattern gom nhiều measurements/events nhỏ vào một document bucket theo time range hoặc count, giảm số documents và index overhead. Nó phù hợp time-series, logs, sensor readings hoặc activity events.
Ví dụ bucket theo giờ cho device metrics. Cần giới hạn số entries mỗi bucket để tránh document quá lớn và thiết kế query theo bucket key như deviceId + hour.
Schema validation dùng JSON Schema để enforce shape/rules ở database level. Nó hữu ích khi nhiều services ghi cùng collection hoặc cần bảo vệ khỏi dữ liệu bẩn ngoài app validation.
Ví dụ:
db.createCollection("users", {
validator: { $jsonSchema: { required: ["email"], properties: { email: { bsonType: "string" } } } }
})Validation không thay thế app-level DTO, nhưng là lớp phòng thủ tốt cho dữ liệu quan trọng.
explain("executionStats") cho biết query dùng index nào, docs/keys examined, docs returned, stage tree và thời gian thực thi. Mục tiêu là keys/docs examined gần với số docs returned, không phải scan quá rộng.
Ví dụ:
db.orders.find({ tenantId: "t1", status: "paid" }).explain("executionStats")Nếu thấy COLLSCAN, docs examined rất lớn hoặc sort in memory, cần xem lại index/query shape/projection.
MongoDB driver duy trì connection pool. App nên tạo một client singleton per process và reuse, không tạo client mới mỗi request. Tạo client liên tục gây connection storm và latency cao.
Cần cấu hình pool size theo số app replicas, workload và cluster capacity. Tổng connections = replicas x pool size, nên nếu scale Kubernetes replicas mà không giảm pool size, database có thể bị quá tải.
Backup cần nhất quán với replica set/sharded cluster và phải được test restore. Với Atlas có managed backups/PITR; self-managed thường dùng filesystem snapshots, mongodump cho logical backup nhỏ hơn hoặc ops tooling chuyên dụng.
Cần xác định RPO/RTO, mã hóa backup, lưu ngoài cluster, test restore định kỳ và runbook failover. Backup không test restore thì chỉ là giả định.
Oplog là capped collection đặc biệt ghi lại các thay đổi trên primary để secondaries replicate theo thứ tự. Nó là nền tảng cho replication và cũng liên quan tới change streams.
Nếu secondary lag quá xa và oplog đã xoay mất phần nó cần, node có thể phải resync. Vì vậy production cần monitor replication lag, oplog window và write volume, đặc biệt khi có batch writes lớn hoặc secondary bị down lâu.
Production cần authentication bật, TLS, network allowlist/private network, least-privilege users, secrets manager, audit logs khi cần, backup encryption và không expose MongoDB trực tiếp ra internet.
Ở app layer, cần validate input, projection/response schema để tránh lộ field nhạy cảm, rate limit endpoints nguy hiểm và không log connection string chứa credentials.
$unwind có rủi ro gì trong aggregation pipeline?$unwind tách array thành nhiều documents, có thể làm số rows trong pipeline tăng rất mạnh. Nếu unwind trước khi filter, memory và latency có thể tăng đột biến.
Pattern tốt: $match sớm, $project bỏ field không cần, chỉ $unwind array cần thiết và kiểm soát cardinality. Với array rất lớn, cân nhắc schema khác hoặc collection riêng thay vì embed unbounded array.
Transaction nên ngắn, ít documents, có retry cho transient errors và commit unknown result. Driver thường có helper withTransaction để xử lý retry pattern tốt hơn.
Ví dụ Node.js rút gọn:
await session.withTransaction(async () => {
await accounts.updateOne({ _id: from }, { $inc: { balance: -amount } }, { session })
await accounts.updateOne({ _id: to }, { $inc: { balance: amount } }, { session })
})Không gọi external API trong transaction.
Giữ transaction lâu làm tăng lock/conflict và ảnh hưởng performance.
Shard key nên có cardinality cao, phân phối write đều, hỗ trợ query targeting và không thay đổi. Nếu key tăng đơn điệu như timestamp hoặc ObjectId prefix theo thời gian, writes có thể dồn vào một shard.
Không có shard key hoàn hảo cho mọi query. Cần xem workload: read path chính, write volume, tenant distribution, range queries, zone sharding requirements và khả năng resharding nếu workload đổi.
Covered query là query có thể được trả lời hoàn toàn từ index mà không đọc document. Điều kiện: filter và projection đều nằm trong index, và projection không cần fields ngoài index.
Ví dụ:
db.users.createIndex({ email: 1, name: 1 })
db.users.find({ email: "a@example.com" }, { email: 1, name: 1, _id: 0 })Covered query giảm I/O, nhưng thêm fields vào index cũng tăng storage/write cost.
Không nên tạo index quá rộng chỉ để covered mọi thứ.
Sparse index chỉ index documents có field được index. Partial index linh hoạt hơn, chỉ index documents match filter expression bất kỳ.
Ví dụ partial index cho active users:
db.users.createIndex(
{ email: 1 },
{ unique: true, partialFilterExpression: { deletedAt: { $exists: false } } }
)Partial index thường rõ business rule hơn sparse index.
Cần đảm bảo query có predicate tương thích để planner dùng được index.
skip/limit đơn giản nhưng chậm khi offset lớn vì database vẫn phải bỏ qua nhiều documents. Cursor/keyset pagination dùng sort key ổn định như createdAt + _id, hiệu quả hơn cho feed lớn.
Ví dụ:
db.posts.find({ createdAt: { $lt: lastCreatedAt } })
.sort({ createdAt: -1, _id: -1 })
.limit(20)Cần index khớp sort/filter.
Với sort key trùng nhau, thêm _id làm tie-breaker để tránh mất/trùng records.
Materialized view pattern lưu sẵn kết quả tổng hợp vào collection khác để phục vụ read path nhanh hơn. Nó phù hợp dashboard, counters, feed projection hoặc report cần query nhiều lần.
Trade-off là consistency và update complexity. Có thể cập nhật bằng change streams, scheduled jobs hoặc trong cùng transaction với write chính. Cần theo dõi lag và có job rebuild khi projection bị lệch.
Change streams cho phép app subscribe changes từ collection/database/cluster để build realtime features, cache invalidation, audit pipeline, sync sang search index hoặc event-driven projection.
Ví dụ Node.js:
const stream = db.collection("orders").watch()
for await (const change of stream) {
console.log(change.operationType, change.documentKey)
}Production cần resume token, error handling, idempotent consumers và monitoring lag.
Không nên coi change stream là magic queue nếu chưa thiết kế retry/dedup.
Anti-patterns phổ biến: unbounded arrays, document quá lớn, thiếu index cho query chính, dùng $lookup quá nhiều như relational joins, schema quá linh hoạt không validate, skip pagination offset lớn, lưu mọi thứ trong một collection, và bỏ qua write concern cho dữ liệu quan trọng.
Cách sửa thường bắt đầu từ query pattern: thiết kế lại aggregate boundary, thêm index đúng, tách collection khi growth unbounded, dùng cursor pagination và thêm schema validation cho fields quan trọng.
Optimistic concurrency dùng version field hoặc updatedAt trong filter update. Nếu document đã bị update bởi request khác, update count bằng 0 và app báo conflict/retry.
Ví dụ:
const result = await users.updateOne(
{ _id, version },
{ $set: changes, $inc: { version: 1 } }
)
if (result.matchedCount === 0) throw new Error("Conflict")Pattern này hữu ích cho edit form, inventory nhẹ hoặc aggregate update không muốn lock lâu.
Các lựa chọn: shared collections với tenantId, database per tenant, hoặc cluster/project per tenant. Shared collections tiết kiệm và dễ vận hành nhưng cần index prefix tenantId và authorization chặt. Database/cluster per tenant cách ly tốt hơn nhưng operations phức tạp và tốn chi phí hơn.
Ví dụ index shared collection:
db.orders.createIndex({ tenantId: 1, createdAt: -1 })Cần đảm bảo mọi query đều filter tenant, tránh data leak, và có backup/restore strategy theo tenant nếu business yêu cầu.
Bắt đầu bằng slow query logs/profiler và explain("executionStats"). Kiểm query shape, index used, docs examined vs returned, sort stage, projection, document size, network payload và lock/replication lag nếu write-heavy.
Sau đó xem schema có chống lại query pattern không: unbounded array, $lookup đắt, thiếu tenant prefix, skip offset lớn hoặc shard key làm scatter-gather. Tối ưu MongoDB thường là tối ưu schema + index + query cùng lúc.
Không nên dùng MongoDB khi app phụ thuộc nhiều vào relational joins, cross-entity constraints, ad-hoc SQL reporting, transaction phức tạp qua nhiều aggregates, hoặc team chưa có kinh nghiệm thiết kế document schema.
MongoDB mạnh khi document aggregate và query pattern rõ. Nếu chỉ lưu JSON vì chưa muốn thiết kế schema, hệ thống thường sẽ trả giá bằng index khó, dữ liệu bẩn và query/reporting phức tạp về sau.