Logo Wecommit
Về chúng tôiCộng đồngLời chứng thựcTài nguyên
Logo Wecommit

MongoDB từ đầu: mô hình dữ liệu, kiến trúc và Index

Học MongoDB từ đầu theo đúng thứ tự: thuật ngữ ánh xạ từ SQL, ba mô hình triển khai, storage engine WiredTiger và bài Index đo trên 5 triệu document.

Anh em từ SQL bước sang MongoDB thường vấp đúng ba chỗ. Chỗ thứ nhất là gọi tên sự vật: table, row, column giờ thành collection, document, field. Chỗ thứ hai là kiến trúc: cài xong rồi nhưng bên trong nó chạy bằng cái gì thì không biết. Chỗ thứ ba đắt nhất, là hiệu năng: dữ liệu ít thì chạy ngon, dữ liệu nhiều lên thì đứng hình.

Bài này tôi đi trọn cả ba chỗ đó theo đúng thứ tự một người mới nên đi. Toàn bộ số liệu trong bài lấy từ đúng buổi tôi ngồi làm trên máy, giả lập 5 triệu document rồi đo từng bước.

Video gốc dài 1 giờ 17 phút. Bài viết này gói lại phần cốt lõi, bổ sung mốc phiên bản và các giới hạn chính thức mà một video quay cuối 2023 chưa nói tới.

Bài này đi theo thứ tự nào?

  1. Ánh xạ thuật ngữ từ cơ sở dữ liệu quan hệ sang MongoDB.
  2. Ba mô hình triển khai và chọn cái nào.
  3. Kiến trúc bên trong, từ khóa duy nhất cần nhớ là storage engine.
  4. Thao tác dữ liệu, so với SQL thì khác chỗ nào.
  5. Tối ưu bằng Index, có số đo trên 5 triệu document.

Ai đang muốn hiểu vì sao MongoDB lúc nhanh lúc chậm ở tầng chọn chiến lược thực thi thì đọc riêng bài MongoDB lúc nhanh lúc chậm, nguyên nhân từ kiến trúc ít người để ý. Bài đó mổ tầng query planner. Bài này là bài nhập môn trọn vẹn.

MongoDB gọi tên mọi thứ khác SQL ở chỗ nào?

Bốn khái niệm cơ bản nhất ánh xạ một đổi một. Anh em nào đã làm với cơ sở dữ liệu quan hệ thì học MongoDB rất nhanh, vì chỉ đổi tên chứ tư tưởng vẫn thế.

Cơ sở dữ liệu quan hệMongoDBKhác biệt cần nhớ
DatabaseDatabaseGiống nhau
TableCollectionKhông phải khai báo trước
ColumnFieldKhông phải định nghĩa kiểu dữ liệu trước
RowDocumentLưu dạng BSON, hai document trong cùng một collection được phép khác cấu trúc
IndexIndexĐánh trên field thay vì trên cột

Điểm đáng nói nhất nằm ở dòng cuối bảng trên và ở chỗ này: MongoDB cho anh em nhúng dữ liệu.

Trong cơ sở dữ liệu quan hệ, anh em tách bảng user và bảng address, nối với nhau bằng một cột chung, muốn lấy đủ thông tin thì phải join. Sang MongoDB, cả hai cụm thông tin đó nằm gọn trong một document. Lấy ra là lấy trọn, không join gì cả.

Sơ đồ ánh xạ dữ liệu từ hai bảng user và address trong cơ sở dữ liệu quan hệ sang một document JSON duy nhất của MongoDB

Đấy là cái hay đầu tiên anh em gặp. Nhưng nó không miễn phí.

Nhúng dữ liệu vào một document thì được lợi gì và mất gì?

Được: đọc nhanh vì không join, dữ liệu đi cùng nhau thì nằm cùng nhau.

Mất: một document BSON tối đa 16 mebibyte, đây là giới hạn cứng của MongoDB, không cấu hình lên được (MongoDB Manual, Documents). Nhúng một mảng mà mảng đó cứ dài mãi ra theo thời gian thì sớm muộn document sẽ đụng trần. Chính hãng xếp cái này vào nhóm mẫu thiết kế sai, gọi tên là mảng không có biên (MongoDB Manual, Avoid Unbounded Arrays).

Nên nguyên tắc của tôi rất đơn giản. Cụm dữ liệu nào có số lượng chốt được thì nhúng. Cụm nào lớn không kiểm soát được thì tách ra collection riêng rồi tham chiếu, đúng như cách anh em vẫn làm với khóa ngoại.

Triển khai MongoDB có mấy mô hình và nên chọn cái nào?

Có ba mô hình kinh điển, và ba cái này thật ra là ba nấc của cùng một câu chuyện: một máy chủ, rồi nhiều bản sao, rồi chia nhỏ dữ liệu ra nhiều cụm.

Mô hìnhCách chạyDùng khi nàoNhược điểm
StandaloneMột máy chủ, một databaseHọc, dựng thử, môi trường phát triểnMáy chủ chết là ứng dụng chết theo
ReplicationMột primary nhận ghi, các secondary đồng bộ theoHệ thống cần chạy liên tụcVẫn chỉ một bản dữ liệu đầy đủ trên mỗi máy
ShardingDữ liệu chia ngang ra nhiều cụm, mỗi cụm lại là một replica setDữ liệu lớn, muốn mở rộng ngangVận hành phức tạp hơn hẳn

Mô hình standalone thì đúng như tên. Một con máy chủ, cài MongoDB lên đó, mọi thao tác đọc ghi đều đổ về đấy. Dựng vài phút là xong. Nhược điểm chí mạng là tính sẵn sàng. Database chết thì ứng dụng tính downtime ngay. Anh em làm ngân hàng hay chứng khoán thì không ai dám chịu rủi ro kiểu này.

Replication đẻ ra để chữa đúng chỗ đó. Một ông làm chủ, gọi là primary, chịu mọi thao tác sửa đổi dữ liệu. Ông primary đẩy oplog sang các con secondary để chúng nó cập nhật theo. Primary chết thì một secondary được bầu lên thay, ứng dụng chạy tiếp mà không đứt.

Sharding thì tư tưởng khác hẳn. Thay vì mỗi máy chứa trọn vẹn dữ liệu, dữ liệu bị chặt theo chiều ngang rồi rải ra nhiều cụm.

Sơ đồ mô hình sharding của MongoDB gồm router mongos đứng trước và ba shard, mỗi shard là một replica set có một primary và hai secondary

Nhìn sơ đồ anh em thấy ba tầng. Trên cùng là router, chính là tiến trình mongos, ứng dụng nói chuyện với nó chứ không nói thẳng với từng shard. Dưới đó là các shard. Và bên trong mỗi shard lại là một replica set đầy đủ, một primary kèm hai secondary.

Tôi hay lấy ví dụ dữ liệu cư dân cả nước. Hà Nội đông dân thì cho hẳn hai cụm máy chủ ôm riêng. Thái Bình ít hơn thì một cụm. Cụm chứa Hà Nội hoàn toàn không có dữ liệu tỉnh khác. Được cái gì? Được chỗ này: hết chỗ thì cắm thêm máy chủ vào, dữ liệu tràn ra, hiệu năng tăng theo chiều ngang. Không phải cứ chăm chăm cắm thêm RAM với CPU vào một con máy như cách kinh điển.

Con secondary có gánh việc đọc giúp primary không?

Mặc định là không. Ứng dụng gửi mọi lệnh đọc về primary, đây là chế độ primary của read preference. Muốn secondary phục vụ đọc thì phải đổi read preference một cách chủ động.

Và đổi rồi thì phải chấp nhận cái giá của nó. Mọi chế độ khác primary đều có thể trả về dữ liệu cũ, vì secondary đồng bộ theo kiểu bất đồng bộ (MongoDB Manual, Read Preference). Anh em nào tính dùng secondary để giảm tải cho primary thì phải hỏi trước một câu: nghiệp vụ của mình chịu được dữ liệu trễ mấy giây không?

Còn chuyện dựng thử, MongoDB Atlas cho một cụm miễn phí. Cụm này chạy sẵn replica set ba node, không cấu hình được bộ nhớ, không bật được backup, và hiện Atlas chạy MongoDB 8.0 cho gói miễn phí (MongoDB Atlas, Free Cluster Limits). Trong video tôi đăng ký gói này với dung lượng 512 MB. Học thì quá đủ, chạy thật thì không.

Bên trong MongoDB có những gì?

Kiến trúc của mọi database đều gồm ba mảng: một vùng bộ nhớ, một tập file nằm trên đĩa, và các tiến trình chuyển dữ liệu qua lại giữa hai chỗ đó. MongoDB cũng vậy thôi.

Người dùng không bao giờ chọc thẳng vào file. Bao giờ cũng phải đi qua bộ nhớ. Nhiều phiên cùng làm việc trên một phần dữ liệu thì lấy luôn trên bộ nhớ, khỏi phải xuống đĩa. Cái việc chạm vào file liên tục mới là thứ giết hiệu năng.

Vậy thứ gì quyết định chuyện đó? Giải thuật. Khi nào đẩy dữ liệu xuống file, khi nào kéo lên, giữ bao lâu thì dọn, ghi với tần suất bao nhiêu. Toàn bộ chuyện đó gói trong một từ khóa duy nhất anh em cần nhớ: storage engine.

Tôi hay ví database như cái xe. Vỏ xe màu gì thì không quan trọng. Quan trọng là cái máy bên trong. Storage engine chính là cái máy đó, và MongoDB cho phép cắm nhiều loại máy khác nhau.

Sơ đồ kiến trúc MongoDB với WiredTiger, vùng Host Memory chứa cache và write ahead journal, vùng Disk Storage chứa datafiles, journal và temporary files

Sơ đồ trên chia đúng hai tầng. Tầng Host Memory là bộ nhớ của máy chủ, trong đó tiến trình mongod giữ các kết nối, còn WiredTiger giữ vùng cache, vùng snapshots và vùng write ahead journal. Bên cạnh còn hai vùng riêng cho việc gộp dữ liệu và sắp xếp dữ liệu. Tầng Disk Storage bên dưới là ba nhóm file: datafiles, journal và temporary files.

Ba cái tên anh em sẽ đọc thấy trong tài liệu:

  • WiredTiger: mặc định, và là loại được khuyến nghị cho mọi triển khai mới (MongoDB Manual, Storage Engines). Nó chống chịu tốt với đa dạng loại ứng dụng nên phần lớn anh em cứ để mặc định là ổn.
  • In-Memory: giữ dữ liệu trên bộ nhớ để độ trễ ổn định hơn. Chỉ có trong bản Enterprise, bản Community không có (MongoDB Manual, In-Memory Storage Engine). Và nó không giữ dữ liệu sau khi tiến trình tắt.
  • MMAPv1: loại cũ. Trong video tôi gọi nó là loại cũ, giờ nói cho rõ mốc: MongoDB đã gỡ hẳn MMAPv1 từ phiên bản 4.2 (MongoDB Manual, Change a Standalone to WiredTiger). Anh em đọc tài liệu cũ thấy nhắc tên này thì biết đó là chuyện của quá khứ, đừng đi tìm cách bật nó lên.

Dưới đĩa, WiredTiger giữ tập data file cho collection và index, thêm file journal đóng vai nhật ký ghi trước, thêm file tạm. File journal chính là thứ tương đương redo log của Oracle hay WAL của PostgreSQL. Database tắt đột ngột thì nó dựa vào đó khôi phục lại.

Còn tham số bộ nhớ thì lời khuyên của tôi vẫn thế: chưa hiểu sâu thì để mặc định. Kéo bộ nhớ của database lên thật to, chiếm hết bộ nhớ của máy chủ, chưa chắc đã tốt.

Làm sao biết database đang chạy storage engine nào?

Có hai cách, cách nào cũng mất chưa tới một phút.

Cách chính thống là hỏi thẳng server. Cách thứ hai là nhìn tên file trong thư mục data, vì mỗi storage engine đẻ ra bộ file mang tên khác nhau.

// Cach 1: hoi thang server dang chay engine nao
db.serverStatus().storageEngine

// Cach 2: xem thu muc data, thay file mang ten WiredTiger la biet ngay
// Windows:  dir  E:\MongoDB\data
// Linux:    ls -l /var/lib/mongodb

// Doi engine thi sua file cau hinh mongod.cfg, phan storage:
// storage:
//   dbPath: E:\MongoDB\data-inmemory
//   engine: inMemory

Muốn đổi sang In-Memory thì tạo một thư mục dữ liệu mới, thêm dòng engine vào file cấu hình, bật lại service rồi kiểm tra lại bằng đúng câu lệnh trên. Nhắc lại một lần nữa cho chắc: đường này chỉ mở với bản Enterprise.

Câu lệnh trong MongoDB viết khác SQL thế nào?

Khác ở một điểm gốc: MongoDB không bắt anh em tạo trước. Bên PostgreSQL hay Oracle, muốn insert thì phải có bảng, mà muốn có bảng thì phải khai đủ cột và kiểu dữ liệu. Bên MongoDB, cứ gõ lệnh insert. Chưa có collection thì nó tự tạo. Chưa có database thì cũng tự tạo luôn.

Đây là bảng đối chiếu để anh em tra nhanh.

Việc cần làmSQLMongoDB
Thêm một bản ghiINSERT INTO ... VALUESinsertOne()
Thêm nhiều bản ghiINSERT nhiều dònginsertMany()
Lấy tất cảSELECT *find()
Giới hạn số dòngLIMIT n.limit(n)
Lọc theo điều kiệnWHEREtham số thứ nhất của find()
Lớn hơn, nhỏ hơn> <$gt $lt
Kết hợp điều kiệnAND OR$and $or
Sắp xếpORDER BY.sort({ field: 1 })
Sửa một dòngUPDATE có điều kiện hẹpupdateOne()
Sửa nhiều dòngUPDATEupdateMany()
XóaDELETEdeleteOne(), deleteMany()
Xóa cả bảngDROP TABLE.drop()

Nhìn cột phải anh em thấy quy luật chưa? Thêm, sửa, xóa đều có cặp One và Many. Nhớ đúng một quy luật đó là dùng được cả bộ.

Chỗ đáng để dừng lại lâu nhất là chuyện cấu trúc document. Trong buổi demo tôi thêm một lúc bốn document vào cùng collection mycollection, và cố tình cho chúng nó khác nhau. Ông Trang có đủ name, age, city. Ông Long thì không có age, không có city, lại có thêm trường phone. MongoDB nhận hết, không kêu một câu.

Màn hình mongosh chạy lệnh insertMany thêm bốn document có cấu trúc khác nhau vào cùng collection mycollection của MongoDB

Thêm một chi tiết dễ bỏ qua. Anh em không hề khai trường _id, nhưng document nào cũng có nó, giá trị là một ObjectId do hệ thống tự sinh. Đó chính là mã duy nhất của document, và nó được đánh index sẵn.

use wecommit

// them mot document
db.mycollection.insertOne({ name: "Tran Quoc Huy", age: 25, city: "Ha noi" })

// loc, sap xep, gioi han
db.mycollection.find({ age: { $gt: 30 } }).sort({ age: -1 }).limit(2)

// ket hop dieu kien
db.mycollection.find({ $and: [ { name: "Tran Quoc Huy" }, { age: 25 } ] })

// sua nhieu ban ghi thoa man dieu kien
db.mycollection.updateMany({ name: "Tran Quoc Huy" }, { $set: { city: "Hue" } })

// xoa nhieu ban ghi
db.mycollection.deleteMany({ age: { $gt: 29 } })

Sự tự do này là con dao hai lưỡi. Nó giúp anh em thay đổi mô hình dữ liệu mà không phải dừng hệ thống. Nhưng nó cũng có nghĩa là không ai chặn giúp anh em khi ứng dụng ghi sai tên trường. Chốt lại, chốt lại: MongoDB không có schema cứng, nên kỷ luật phải nằm ở tầng ứng dụng.

Vì sao MongoDB chậm khi dữ liệu lớn, và Index cứu được đến đâu?

Database nào cũng có thể chậm, MongoDB không phải ngoại lệ. Muốn biết vì sao chậm thì phải xem chiến lược thực thi của câu lệnh, tức là xem nó định lấy dữ liệu bằng giải thuật gì.

Tôi giả lập một collection tên customers trong database wecommit, gồm 5 triệu document, mỗi document có name, age, address, email, phone và nghề nghiệp. Đẩy vào theo lô 10.000 document một lần, vì nhồi cả 5 triệu bằng một lệnh insertMany là lỗi bộ nhớ ngay, còn insertOne từng cái thì chậm chết.

Trước khi tối ưu, phải biết mình đang đứng ở đâu. Chạy db.runCommand({ collStats: "customers" }) và chỉ cần nhìn đúng hai con số. Số count là 5.000.000 document. Số size là 762.109.559 byte, tức khoảng 727 MiB nếu nạp trọn lên bộ nhớ. Trên đĩa thì storageSize chỉ 166.916.096 byte, vì WiredTiger nén dữ liệu lại. Các thông số còn lại tạm bỏ qua, nhìn nhiều quá thành loãng.

Câu lệnh cần chạy là tìm những người tên Huy và có age bằng 35. Thêm .explain("executionStats") vào cuối là thấy hết.

db.customers.find({ name: "Huy", age: 35 }).explain("executionStats")

// Ba cho can nhin trong ket qua tra ve:
// [1] executionStats.totalDocsExamined  -> quet bao nhieu document
// [2] executionStats.nReturned          -> tra ve bao nhieu document
// [3] winningPlan stage                 -> COLLSCAN hay IXSCAN
Kết quả explain executionStats của MongoDB khi chưa có Index, totalDocsExamined bằng 5.000.000 và executionTimeMillis bằng 1767

Kết quả lần chạy đầu tiên rất xấu. Giải thuật là COLLSCAN, tức là quét cả collection. Hệ thống lục hết 5.000.000 document để trả về đúng 8.169 cái thỏa mãn, totalKeysExamined bằng 0 vì chẳng có Index nào để đọc. Thời gian thực thi 1.767 mili giây.

Anh em thấy vấn đề chưa? Nó làm thừa gấp sáu trăm lần lượng việc cần làm. Và collection càng phình thì càng chậm.

Bây giờ tạo Index trên đúng một trường age, đặt tên là IDX_AGE, rồi chạy lại. Giải thuật đổi thành IXSCAN, tức là nó chịu dùng Index. Số document phải quét rơi từ 5 triệu xuống còn 100.296. Thời gian rơi từ 1.767 xuống 134 mili giây.

Sơ đồ đối chiếu trước và sau khi tạo Index IDX_AGE trên MongoDB, cột trái ghi COLLSCAN 5.000.000 document, cột phải ghi IXSCAN 100.296 document và 134 mili giây

Đánh Index rồi mà database vẫn không dùng thì vì sao?

Vì thứ tự cột trong Index. Đây là chỗ tôi muốn anh em nhớ nhất trong cả bài.

Tôi thử tiếp một Index ghép hai trường, đặt tên IDX_EMAIL_AGE, với email đứng trước và age đứng sau. Trường age vẫn nằm trong Index đấy chứ, có mất đi đâu. Vậy mà chạy lại thì giải thuật quay về COLLSCAN, quét lại đủ 5.000.000 document, thời gian vọt lên 1.535 mili giây. Index nằm đó mà vô dụng.

Đổi ngược thứ tự hai trường, tạo IDX_AGE_EMAIL cho age lên trước, mọi thứ trở lại bình thường ngay: IXSCAN, quét 100.296 document, thời gian 182 mili giây.

Lý do nằm trong tài liệu chính chủ. MongoDB chỉ dùng được compound index cho các truy vấn chạm vào tiền tố của Index, tức là nhóm trường đứng đầu tính từ trái sang. Trường không nằm ở tiền tố thì Index coi như không giúp gì (MongoDB Manual, Compound Indexes).

Bước cuối, tôi tạo IDX_NAME_AGE đúng lên hai trường mà câu lệnh đang tìm, là name rồi age. Lần này totalDocsExamined rơi xuống 8.169, đúng bằng nReturned. Quét bao nhiêu trả bấy nhiêu, không thừa một cái. Thời gian còn 16 mili giây.

Kết quả explain của MongoDB sau khi tạo Index IDX_NAME_AGE, giải thuật IXSCAN với totalDocsExamined và nReturned cùng bằng 8.169 và thời gian 16 mili giây

Đây là đúng bộ lệnh tôi chạy, anh em chép về đổi tên collection với tên trường là dùng được.

// tao index tren mot truong
db.customers.createIndex({ age: 1 }, { name: "IDX_AGE" })

// xem cac index hien co
db.customers.getIndexes()

// bo index cu di truoc khi thu phuong an khac
db.customers.dropIndex("IDX_AGE")

// index ghep, thu tu truong quyet dinh tat ca
db.customers.createIndex({ email: 1, age: 1 }, { name: "IDX_EMAIL_AGE" })
db.customers.createIndex({ age: 1, email: 1 }, { name: "IDX_AGE_EMAIL" })
db.customers.createIndex({ name: 1, age: 1 }, { name: "IDX_NAME_AGE" })

// chay lai explain sau moi lan doi index
db.customers.find({ name: "Huy", age: 35 }).explain("executionStats")

Gói cả năm kịch bản vào một bảng cho dễ nhìn.

Index đang cóGiải thuậtDocument phải quétDocument trả vềThời gian
Chưa có IndexCOLLSCAN5.000.0008.1691.767 ms
IDX_AGE trên ageIXSCAN100.2968.169134 ms
IDX_EMAIL_AGE trên email rồi ageCOLLSCAN5.000.0008.1691.535 ms
IDX_AGE_EMAIL trên age rồi emailIXSCAN100.2968.169182 ms
IDX_NAME_AGE trên name rồi ageIXSCAN8.1698.16916 ms

Từ 1.767 mili giây xuống 16 mili giây, hơn một trăm lần, mà không đụng một dòng code ứng dụng nào. Đó là sức của Index khi đánh đúng.

Nhưng nhìn dòng thứ ba trong bảng đi. Cũng là Index, cũng hai cột đó, chỉ đảo thứ tự thôi mà tệ ngang lúc chưa có gì. Nên tư duy "cứ đánh Index là nhanh" là tư duy hỏng. Câu chuyện thứ tự cột này không riêng gì MongoDB, tôi có phân tích chuyện tương tự trên PostgreSQL ở bài Tầm quan trọng của thứ tự các cột trong Index PostgreSQL, và trên Oracle với SQL Server ở bài Vì sao có Index mà câu SQL vẫn chậm.

Giới hạn thật

Phần này là những thứ tài liệu quảng cáo ít nói, còn người đã vận hành thì gặp suốt.

Một document tối đa 16 mebibyte. Không nới được. Muốn chứa file lớn hơn thì phải dùng GridFS. Nhúng thoải mái nhưng nhúng có biên.

Một collection tối đa 64 Index, một compound index tối đa 32 field (MongoDB Manual, Limits and Thresholds). Con số nghe rộng, nhưng mỗi Index thêm vào là một lần ghi thêm mỗi khi insert hay update. Index không miễn phí.

Schema linh hoạt không có nghĩa là không cần thiết kế. Không ai chặn giúp anh em khi ứng dụng ghi nhầm tên trường. Lỗi kiểu này không nổ lúc ghi, nó nổ lúc đọc, mấy tháng sau.

Giao dịch nhiều document có, nhưng đắt. MongoDB hỗ trợ transaction phân tán trên replica set và sharded cluster. Chính hãng nói thẳng: trong phần lớn trường hợp, transaction phân tán tốn hiệu năng hơn hẳn so với ghi một document, và nó không phải thứ để thay cho việc thiết kế mô hình dữ liệu cho tử tế (MongoDB Manual, Transactions). Ai định bê nguyên thói quen viết transaction dài như bên RDBMS sang đây thì cân nhắc lại.

Secondary trả dữ liệu cũ. Mọi read preference khác primary đều có thể đọc phải dữ liệu chưa đồng bộ kịp. Dùng để giảm tải thì được, dùng cho nghiệp vụ đòi số liệu chuẩn từng giây thì chết.

Cụm miễn phí của Atlas không phải môi trường chạy thật. Không backup, không cấu hình được bộ nhớ, không test được failover.

Số đo trong bài là số của một máy cụ thể. Cùng một câu lệnh chạy hai thời điểm khác nhau trên cùng hệ thống, thời gian đã xê dịch được rồi. Cái đáng mang đi không phải con số 16 mili giây, mà là tỉ lệ giữa số document phải quét và số document trả về.

Video quay cuối 2023, trên MongoDB 7.0.4 bản Community, hệ điều hành Windows. Bản mới nhất đã khác vài chỗ, tôi ghi mốc phiên bản ở từng chỗ có thay đổi trong bài.

Bảy ngày tới anh em làm được gì?

  1. Dựng một cụm Atlas miễn phí, nạp bộ dữ liệu mẫu, kết nối bằng mongosh. Mất khoảng 15 phút.
  2. Chạy db.serverStatus().storageEngine xem hệ thống mình đang chạy engine nào.
  3. Lấy đúng một câu lệnh đang chậm trong dự án, thêm .explain("executionStats"), ghi lại ba con số: totalDocsExamined, nReturned, stage.
  4. Tỉ lệ giữa hai con số đầu lệch quá mười lần thì đó là ứng viên tối ưu.
  5. Đánh thử Index theo đúng thứ tự các trường trong mệnh đề lọc, chạy lại explain, so số trước và sau.
  6. Rà lại các Index đang có, cái nào không bao giờ xuất hiện trong IXSCAN thì cân nhắc bỏ.

Câu hỏi hay gặp

MongoDB có thay thế được cơ sở dữ liệu quan hệ không?

Không thay thế, nó giải bài toán khác. MongoDB hợp khi dữ liệu đi theo cụm, cấu trúc thay đổi thường xuyên, và cần mở rộng ngang. Nghiệp vụ mà quan hệ giữa các thực thể chằng chịt và đòi ràng buộc chặt thì cơ sở dữ liệu quan hệ vẫn là lựa chọn đúng.

Storage engine nào nên dùng?

Cứ để WiredTiger. Đó là mặc định và là loại hãng khuyến nghị cho triển khai mới. In-Memory chỉ có ở bản Enterprise và không giữ dữ liệu sau khi tắt. MMAPv1 thì MongoDB đã gỡ từ phiên bản 4.2.

Vì sao đánh Index rồi mà câu lệnh vẫn quét toàn bộ collection?

Phần lớn là do thứ tự trường trong compound index. MongoDB chỉ dùng được Index khi truy vấn chạm vào tiền tố của nó, tức nhóm trường đứng đầu. Kiểm bằng cách chạy explain và nhìn stage. Thấy COLLSCAN nghĩa là Index không được dùng.

Cần bao nhiêu Index cho một collection?

Càng ít càng tốt, miễn đủ cho các câu lệnh chạy thường xuyên. Giới hạn cứng là 64 Index cho một collection, nhưng mỗi Index đều làm chậm thao tác ghi. Đánh theo câu lệnh thật, đừng đánh theo cảm giác.

Khi nào cần đến sharding?

Khi một máy chủ không còn ôm nổi dữ liệu hoặc không còn gánh nổi lượng truy cập, và việc cắm thêm RAM với CPU đã tới hạn. Trước bước đó, replication kèm Index đánh đúng đã giải quyết được phần lớn bài toán rồi.

Muốn nhúng dữ liệu tới mức nào là đủ?

Nhúng khi cụm dữ liệu con có số lượng chốt được và luôn được đọc cùng document cha. Tách ra collection riêng khi nó lớn không kiểm soát được, hoặc khi nó được đọc và cập nhật độc lập.

Đúc kết

Học một database mới nên đi theo đúng bốn nấc: thuật ngữ, mô hình triển khai, kiến trúc, rồi mới tới tối ưu. Nhảy cóc lên nấc cuối thì chỉ chép được câu lệnh chứ không hiểu vì sao nó nhanh.

Với MongoDB, hai thứ đáng nhớ nhất là từ khóa storage engine và thói quen chạy explain trước khi phán. Còn Index thì nhớ đúng một câu: không phải cứ có Index là ngon, Index phải đúng trường và đúng thứ tự.

Anh em cứ dựng thử một cụm miễn phí rồi chạy lại đúng các bước trong bài. Làm đi sai lại sửa, có chết đâu. Ai đang lo phần AI đọc ghi vào database thì đọc thêm bài AI Agent với database, vì sao nó trả sai và cách chữa. Tùy anh em, phần nào thấy hợp thì lấy.

Nguồn tham khảo