WiredTiger & Compression: một collection nằm trên đĩa như thế nào
Các Phần trước của series nhìn MongoDB từ phía ứng dụng, và ở mọi chỗ ta đều gặp cùng một nhân vật đứng sau màn: WiredTiger. Bài Mental Model nhắc rằng dữ liệu có ba dạng (nén trên đĩa, nén trong cache của OS, chưa nén trong WiredTiger cache). Bài Index Fundamentals hứa rằng hình dạng page, cách tách page và cách nén key là chuyện của WiredTiger. Mấy bài về transaction nhắc "storage engine giữ nhiều phiên bản", "journal", "ticket" mà chưa mở ra.
Phần WiredTiger mở những cái hộp đó, mỗi bài một hộp. Bài này là bản đồ: một collection và một index thật sự là gì trên đĩa, một page trong RAM khác một page trên đĩa chỗ nào, và dữ liệu được nén ở đâu, bằng gì, với giá bao nhiêu, kèm lab 1 triệu document với bốn compressor để câu "nén tốn CPU nhưng tiết kiệm đĩa" có con số đi kèm.
Bài này nằm ở đâu
- Cần biết trước: Mental Model (storage engine, WiredTiger cache, working set), Index Fundamentals (B-tree, RecordId, kích thước index), BSON & ObjectId (document chiếm bao nhiêu byte)
- Giới thiệu: mỗi collection và mỗi index là một file
.wt, RecordId và clustered collection, B-tree gồm internal page và leaf page, page trong bộ nhớ và page trên đĩa (reconciliation, mức khái niệm), block compression (snappy, zstd, zlib, none) và prefix compression, cấu hình theo toàn cục, theo collection, theo index, đo dung lượng, CPU và tốc độ đọc theo compressor - Không dạy ở đây: cache, eviction và working set (bài Cache, Eviction & Working Set); journal và checkpoint (bài Journal & Checkpoint); update chain, history store (bài WiredTiger MVCC); lock, latch, ticket (bài Concurrency). Bài này chỉ chỉ đường tới chúng ở cuối
- Dẫn tới: Cache, Eviction & Working Set
Môi trường lab: MongoDB 8.3.11 trong Docker (image
mongo:8), bốn container standalone (không phải replica set)mongo-wt20-snappy,-zstd,-zlib,-none, mỗi container 2 CPU, 2 GB RAM, WiredTiger cache 1 GB và volume dữ liệu riêng, mới tinh, để dung lượng trên đĩa so sánh được. Host Apple M4, mongosh 2.12.0, databaselab20. Mỗi lần đo chỉ một container làm việc, nhưng máy host lúc đó còn chạy lab của bài khác và vài container không liên quan (load average của host có lúc lên tới 55), nên thời gian dao động; bài ghi median và khoảng dao động. Ổ đĩa là ổ ảo của Docker trên macOS. Mọi số đo trong bài đều đo thật và có file output thô kèm theo (thư mục lablab20/); riêng bảng tốc độ đĩa giả định ở Lab 4 là phép tính minh hoạ, có ghi rõ.Một lần chạy hỏng: lần nạp đầu vào container
nonebị giết vì hết bộ nhớ (exit 137,OOMKilled=true) sau khi build xong bốn index, trước khi lệnhfsynccuối trả về. Sau khi mongod khởi động lại, số đếm document trong thống kê của collection báo 870.000 thay vì 1.000.000 đã nạp (manual cảnh báo thống kê size và count có thể lệch sau khi tắt đột ngột; tôi không kiểm tra dữ liệu có thật sự mất không). Tôi bỏ lần đó, drop và nạp lại từ đầu; mọi số củanonetrong bài là của lần chạy lại.
Nhãn: [tài liệu] theo tài liệu chính thức (manual MongoDB hoặc tài liệu WiredTiger); [quan sát] đo trong lab này (8.3.11); [suy luận] rút ra từ quan sát, không kiểm tra riêng; [chi tiết cài đặt] cách server đang làm, không phải cam kết API; [hình dung] mô hình để dễ nhớ.
Giải thích trong 30 giây
Storage engine là phần của mongod chịu trách nhiệm cất và lấy dữ liệu; MongoDB dùng WiredTiger. Trong WiredTiger, mỗi collection và mỗi index là một table, và mỗi table là một B-tree nằm trong một file .wt. B-tree được cắt thành các page: page trên cao chỉ đường, page ở đáy giữ dữ liệu.
Một page tồn tại ở hai dạng: dạng làm việc trong RAM (đã mở ra, có thể đang mang các thay đổi chưa ghi) và dạng đóng gói trên đĩa. Khi ghi xuống đĩa, page được gói lại và (với collection) nén; khi đọc lên, nó được giải nén và mở ra. Block compression vì thế là chuyện của đĩa: page trong cache không nén, nên nó tiết kiệm dung lượng và I/O chứ không tiết kiệm RAM (prefix compression của index là ngoại lệ, sẽ gặp bên dưới). Cái giá là CPU lúc nén và giải nén, và lab sẽ cho thấy giá đó khác nhau rất nhiều giữa các compressor.
Hình dung trước: kho hàng
Một kho hàng khổng lồ. Mỗi loại hàng (mỗi collection, mỗi index) có một toà nhà kệ riêng. Bên trong, toà nhà chia thành các ngăn kệ (page), mỗi ngăn chứa cả chục thùng hàng. Ở đầu các dãy có bảng chỉ dẫn ("ngăn 1–40: mã A–C, ngăn 41–80: mã D–F"): muốn tìm một món, bạn đọc bảng tổng, rồi bảng chỉ dẫn, rồi đi thẳng tới đúng ngăn, không lục cả toà nhà.
Khi cần làm việc với một ngăn, nhân viên khiêng cả ngăn lên bàn làm việc (cache): mở các thùng, trải hàng ra để kiểm đếm, ghi chú những thay đổi mới bên cạnh. Khi trả ngăn về kho, họ đóng gói lại, hút chân không cho nhỏ, và xếp vào chỗ trống mới; chỗ cũ chỉ được giải phóng sau khi chỗ mới đã chắc chắn nằm yên.
Kho hàng WiredTiger
───────────────────────────────────── ───────────────────────────────────────────
một toà nhà kệ cho mỗi loại hàng một file .wt cho mỗi collection, mỗi index
ngăn kệ page (một nút của B-tree)
bảng chỉ dẫn đầu dãy internal page: chỉ có key và con trỏ tới page con
ngăn chứa hàng thật leaf page: key và value (document, hoặc RecordId)
khiêng ngăn lên bàn, mở thùng, trải hàng đọc page vào cache, dựng dạng trong bộ nhớ
đóng gói, hút chân không, cất lại reconciliation, rồi nén block khi ghi xuống đĩa
xếp vào chỗ trống mới, không đè chỗ cũ block manager không ghi đè block đã ghi[hình dung] Đây chỉ là cách hình dung. Kho thật không có chuyện "ngăn trên bàn và ngăn trong kho là cùng một dữ liệu ở hai dạng" mà ta phải tự giữ cho khớp; WiredTiger tự làm việc đó. "Hút chân không" cũng chỉ là block compression, và nó chỉ xảy ra lúc ghi xuống đĩa: ngăn đang nằm trên bàn không bao giờ ở dạng nén, nên bàn làm việc không rộng thêm chỉ vì kho dùng bao bì mỏng hơn. Phần lab sẽ đo đúng điểm này.
WiredTiger nằm ở đâu trong mongod
Ứng dụng
│
▼
mongod
├── query layer, planner, execution "tìm gì, bằng plan nào"
│ │ xin: "cho tôi document có RecordId này", "ghi record này"
│ ▼
├── storage engine API
│ │
│ ▼
└── WiredTiger
├── các table = B-tree (mỗi collection, mỗi index một cái) bài này
├── block manager + compression bài này
├── cache trong RAM, eviction bài về cache
├── journal (write-ahead log) và checkpoint bài về journal
├── transaction, snapshot, phiên bản (MVCC) bài về MVCC
└── lock, latch, ticket bài về concurrency
│
▼
các file trên đĩa: collection-*.wt, index-*.wt, WiredTiger.wt, journal/ ...[tài liệu] WiredTiger là storage engine mặc định (manual hiện chỉ còn WiredTiger và In-Memory, bản sau chỉ có ở Enterprise). [quan sát] File WiredTiger.turtle ghi WiredTiger 12.0.0: (November 15, 2024): phiên bản thư viện nằm bên trong mongod 8.3.11. Query layer không biết page hay block là gì; nó chỉ hỏi "document nào" và "key nào". Phần còn lại của bài đi xuống dưới ranh giới đó.
Lab 1: mỗi collection và index là một file
Sau khi nạp 1.000.000 đơn hàng vào lab20.orders (5 index: _id_ và bốn index phụ, chi tiết ở phần Setup bên dưới), nhìn thư mục dữ liệu của container snappy (e1-files.out, rút gọn):
-rw------- 176689152 collection-ce58c96d-3a7d-49ec-b820-c6974d3e9ce4.wt
-rw------- 40988672 index-eeb70adb-c344-4464-83e7-b8374d271e4c.wt
-rw------- 18726912 index-581981d0-7f95-4c93-a281-e0cf5f6d8676.wt
-rw------- 14540800 index-f0cd1cba-1bb5-4138-99f3-8200bbcfd9d6.wt
-rw------- 12832768 index-652a6bc8-c11e-4b3f-a3e1-8d17c1ea4e19.wt
-rw------- 6569984 index-5eb7f066-8e12-4cec-a611-f268274dd18c.wt
-rw------- 102400 WiredTiger.wt
-rw------- 4096 WiredTigerHS.wt
-rw------- 32768 _mdb_catalog.wt
-rw------- 32768 sizeStorer.wt
-rw------- 1576 WiredTiger.turtle
drwx------ journal/ (WiredTigerLog.0000000010: 104857600 byte)
drwx------ diagnostic.data/(Đã lược bớt các file nhỏ: collection hệ thống, file lock, storage.bson, journal/WiredTigerPreplog.*.) $collStats với storageStats cho wiredTiger.uri của collection (statistics:table:collection-ce58c96d-...) và indexDetails.<tên>.uri của từng index: tên sau table: chính là tên file bỏ đuôi .wt. storageSize của orders là 176.689.152 byte, bằng đúng kích thước file, và 5 file index-*.wt khớp từng byte với indexSizes. Collection có 4 index phụ nên là 1 + 5 = 6 file: mỗi index là một table riêng, không có file index chung.
[tài liệu] File .wt là data file của table; WiredTiger.wt là file metadata; WiredTigerHS.wt là history store (manual MongoDB: lịch sử snapshot nằm ở đó); journal nằm ở thư mục journal/. diagnostic.data/ là nơi mongod ghi dữ liệu chẩn đoán FTDC (manual: Full Time Diagnostic Data Capture), không phải dữ liệu của bạn. [chi tiết cài đặt] _mdb_catalog.wt và sizeStorer.wt không có trang manual mô tả; ta chỉ thấy tên và kích thước. Tên file là collection-<uuid>.wt chứ không phải orders.wt, nên server phải giữ bảng ánh xạ namespace sang tên file. [suy luận] Bảng đó nằm trong catalog, suy từ tên _mdb_catalog.wt và từ stage $listCatalog (chạy được trên 8.3.11 nhưng tôi không tìm thấy trang manual cho nó, nên bài không dựa vào nó).
RecordId: tên của một document trong table
[quan sát] Trong creationString, table collection có key_format=q (số nguyên 64 bit, tức RecordId) và value_format=u (chuỗi byte, tức document BSON); table index có key_format=u.
lab.orders.find({}, { ref: 1 }).showRecordId().limit(3) // quét collection
lab.orders.find({ userId: "u0007" }, { _id: 0, userId: 1, ref: 1 })
.hint({ userId: 1 }).showRecordId().limit(4) // đi qua index userId{ ref: 'orders/vn/2026/10/00000000', '$recordId': Long('1') }
{ ref: 'orders/vn/2026/10/00000001', '$recordId': Long('2') }
{ ref: 'orders/vn/2026/10/00000002', '$recordId': Long('3') }
{ userId: 'u0007', ref: 'orders/vn/2026/10/00000183', '$recordId': Long('184') }
{ userId: 'u0007', ref: 'orders/vn/2026/10/00000282', '$recordId': Long('283') }
{ userId: 'u0007', ref: 'orders/vn/2026/10/00000605', '$recordId': Long('606') }
{ userId: 'u0007', ref: 'orders/vn/2026/10/00000968', '$recordId': Long('969') }Quét collection cho document theo thứ tự RecordId, tức thứ tự nhập. Đi qua index userId, các key đứng cạnh nhau nhưng RecordId rải khắp collection: mỗi FETCH là một cú nhảy sang chỗ khác của B-tree collection, đó là lý do index chậm dần khi phải fetch nhiều (bài Index Fundamentals đã đo). Và _id_ không phải nơi document nằm: document nằm ở table collection theo RecordId; _id_ chỉ là một index như mọi index.
Clustered collection: bỏ một tầng
[tài liệu] Từ MongoDB 5.3, clustered collection lưu document ngay trong B-tree theo thứ tự _id, trong một file duy nhất, thay vì một table theo RecordId cộng một index _id_ riêng. Chỉ tạo được lúc createCollection, key phải là { _id: 1 }, không chuyển đổi qua lại được (phải copy bằng $out/$merge hoặc dump/restore); manual khuyên key nhỏ và tăng dần.
lab.createCollection("c_clustered", { clusteredIndex: { key: { _id: 1 }, unique: true, name: "orders clustered key" } })[quan sát] Cùng 200.000 đơn nạp vào một collection thường và một clustered (e6-clustered.out):
| Collection thường | Clustered | |
|---|---|---|
File collection + file _id_ | 34.275.328 + 3.538.944 byte (36,06 MiB) | 35.794.944 byte + không có file index (34,14 MiB, ít hơn 5,3%) |
key_format của table | q (RecordId) | u (chính _id, dạng nhị phân) |
Plan của find({ _id }) | EXPRESS_IXSCAN(_id_), keysExamined: 1 | EXPRESS_CLUSTERED_IXSCAN, keysExamined: 0 |
Với clustered collection "RecordId" chính là _id, và tra theo _id đi thẳng vào table dữ liệu. Manual nói thêm: một lần ghi thay vì hai cho insert/update/delete, một lần đọc thay vì hai cho query. Tôi không đo tốc độ của hai loại, nên chỉ nói điều đã thấy: ít một file, ít 5,3% dung lượng ở bộ dữ liệu này (chỉ có index _id), plan khác. [tài liệu] Manual cũng cảnh báo: khi clustered collection có index phụ, key clustered lớn làm các index phụ lớn hơn so với ở collection thường. Chọn clustered hay không là câu hỏi về cách truy cập (có hay tra theo _id, theo khoảng _id, có cần xoá theo TTL), không phải mặc định tốt hơn.
Page: B-tree trong RAM và trên đĩa
Cây và page
[tài liệu] Theo tài liệu WiredTiger, một table là một B-tree (chính xác là B+ tree) gồm các page. Root và internal page chỉ giữ key và tham chiếu tới page con; leaf page giữ key và value. Bản ghi luôn theo thứ tự key, và page tách đôi khi chạm giới hạn kích thước đã cấu hình. Đó là bức tranh B-tree của bài Index Fundamentals; điều mới là cùng cấu trúc đó chứa cả document của collection, không chỉ index.
[ root / internal page ] chỉ key + con trỏ
┌──────────┬──────────┬──────────┐
▼ ▼ ▼ ▼
[ leaf ] [ leaf ] [ leaf ] [ leaf ] ... key + value, sắp theo key
RecordId RecordId RecordId RecordId
1…48 ... 49…97 ... ... ...Tách ở đâu? [tài liệu] Khi page đóng gói vượt kích thước tối đa, WiredTiger cắt thành nhiều page; split_pct (mặc định 90) quyết định cắt ở mức bao nhiêu phần trăm. Tài liệu giải thích: cập nhật ngẫu nhiên hợp với quanh 66% (chừa chỗ cho thay đổi sau), chỉ thêm vào cuối hợp với 100%, 90% là dung hoà. [quan sát] split_pct=90 có trong creationString của cả collection lẫn index.
Hai dạng của một page
[tài liệu] Page có dạng trong bộ nhớ và dạng trên đĩa, và hai dạng này khác nhau. Trong RAM, page ở dạng thuận tiện cho tra cứu và sửa, và mang thêm một cấu trúc ghi các insert, update chưa ghi xuống (đó là update chain của bài WiredTiger MVCC). Biến page trong RAM thành page để ghi xuống đĩa gọi là reconciliation; chiều ngược lại xảy ra khi đọc page lên.
TRONG RAM (cache) TRÊN ĐĨA (file .wt)
┌─────────────────────────────┐ reconciliation ┌──────────────────────────────┐
│ page "làm việc" │ ─────────────────▶ │ page "đóng gói" │
│ • key/value đã mở ra │ │ • header + các cell │
│ • + các thay đổi chưa ghi │ ◀───────────────── │ • key có thể bị cắt prefix │
│ (insert, update) │ đọc + giải nén │ • nén cả block (nếu bật) │
└─────────────────────────────┘ │ • lưu thành block, kích thước │
collection: KHÔNG nén │ là bội số của 4 KB │
└──────────────────────────────┘Về block manager (tầng đọc ghi block trên file), tài liệu nói: mỗi block chứa một page cộng header, kích thước là bội của allocation_size, và WiredTiger không ghi đè: page đã sửa, sau reconciliation, được ghi vào vị trí mới, block cũ được tái sử dụng sau (chính là "chỗ trống mới" ở kho hàng). [quan sát] allocation_size=4KB; collection snappy có 229.376 byte (0,1% file) đang chờ tái sử dụng. Khi nào page rời RAM (eviction khi cache đầy, checkpoint định kỳ) là của bài về cache và bài về journal. Ở đây chỉ cần giữ: dạng đĩa là dạng đóng gói, dạng RAM là dạng làm việc.
Kích thước page: MongoDB đặt gì
[tài liệu] Thư viện WiredTiger có memory_page_max, internal_page_max, leaf_page_max, mặc định của thư viện là 5 MB, 4 KB, 32 KB. MongoDB không nhất thiết dùng mặc định đó. [quan sát] creationString trong $collStats của lab (e3-sizes-snappy.out):
| Tham số | Table collection | Table index | Mặc định thư viện (tài liệu WT) |
|---|---|---|---|
leaf_page_max | 32KB | 16k | 32 KB |
internal_page_max | 4KB | 16k | 4 KB |
memory_page_max | 10M | 5MB | 5 MB |
block_compressor | snappy | (trống) | không nén |
prefix_compression | false | true | tắt |
key_format / value_format | q / u | u / u |
[chi tiết cài đặt] Đây là cách MongoDB 8.3.11 đang tạo table, không phải cam kết và có thể khác giữa phiên bản. Điều cần nhớ: collection và index được cấu hình khác nhau ngay từ đầu; index không có block compressor và collection không có prefix compression. [suy luận] Lý do hợp lý của vế sau: khoá của collection là một số nguyên (RecordId), không có tiền tố chung nào để cắt.
Ba kiểu nén, ba chỗ khác nhau
[tài liệu] Tài liệu WiredTiger liệt kê ba cơ chế và nói rõ cái nào làm nhỏ cả RAM, cái nào chỉ làm nhỏ đĩa:
| Cơ chế | Nén cái gì | Nhỏ hơn trên đĩa | Nhỏ hơn trong RAM | Trong MongoDB |
|---|---|---|---|---|
| Prefix compression | phần đầu giống nhau của các key liền nhau, lưu một lần mỗi page | có | có | bật mặc định cho index, tắt cho collection |
| Dictionary | giá trị giống hệt nhau, lưu một lần mỗi page | có | có | không dùng: dictionary=0 [quan sát] |
| Block compression | cả block nhị phân (một page đã đóng gói) khi ghi xuống đĩa | có | không | snappy mặc định cho collection; không có cho index |
Manual MongoDB nói cùng ý: collection trong WiredTiger cache không nén; index trong cache có biểu diễn khác dạng đĩa nhưng vẫn hưởng lợi của prefix compression để giảm RAM. Hai câu này sẽ được thử bằng lab. Tài liệu WiredTiger cũng cảnh báo block compression "có thể tốn CPU đáng kể".
Cấu hình ở đâu
[tài liệu] Compressor mặc định của collection là snappy, đặt bằng storage.wiredTiger.collectionConfig.blockCompressor (hoặc cờ --wiredTigerCollectionBlockCompressor), giá trị none, snappy, zlib, zstd; với zstd còn có engineConfig.zstdCompressionLevel (mặc định 6, khoảng giá trị đổi ở 8.2: -7 đến 22). Prefix compression của index là storage.wiredTiger.indexConfig.prefixCompression (mặc định true). Journal có compressor riêng (engineConfig.journalCompressor, mặc định snappy), không liên quan tới compressor của collection; journal là chuyện của bài Journal & Checkpoint. Cả hai ghi đè được từng collection lúc createCollection và từng index lúc createIndex qua storageEngine:
db.createCollection("users", { storageEngine: { wiredTiger: { configString: "block_compressor=zlib" } } })
db.orders.createIndex({ ref: 1 }, { storageEngine: { wiredTiger: { configString: "prefix_compression=false" } } })Điều tài liệu nhấn mạnh, và lab 6 kiểm: đổi giá trị toàn cục chỉ ảnh hưởng collection và index tạo sau đó; cái đã có "tiếp tục dùng compressor đã chọn khi được tạo".
Setup và dataset
MongoDB version : 8.3.11 (mongo:8), WiredTiger 12.0.0 (theo WiredTiger.turtle), mongosh 2.12.0
Hardware : Apple M4 host, Docker VM ~7,7 GiB; mỗi container --memory=2g --cpus=2
Configuration : mongod --wiredTigerCacheSizeGB 1 --wiredTigerCollectionBlockCompressor <snappy|zstd|zlib|none>
bốn container standalone, mỗi container một volume mới
Dataset : lab20.orders, 1.000.000 document, trung bình 461 byte (440,4 MiB BSON)
Indexes : _id_, { tenantId: 1, createdAt: -1 }, { userId: 1 }, { traceId: 1 }, { ref: 1 }Schema là đơn hàng quen thuộc của series, thêm vài field để dữ liệu có cả phần lặp lại lẫn phần ngẫu nhiên:
{
_id, // ObjectId tăng dần (12 byte)
tenantId: "t0042", userId: "u0007", // 500 và 200 giá trị khác nhau
status: "completed", createdAt, total, // status: 4 giá trị
tags: ["gift", "cod"], items: [{ sku, qty, price }, ...],
shipping: { city: "Đà Nẵng", street: "123 Lê Lợi 4" }, // 12 thành phố, 60 tên đường
note: "Giao giờ hành chính. Gọi trước khi giao.", // ghép từ 12 câu cố định
traceId: "9f3a...", // 32 ký tự hex NGẪU NHIÊN (không nén được nhiều)
ref: "orders/vn/2026/10/00000183" // tăng dần, tiền tố chung dài
}Tỉ lệ nén là tính chất của dữ liệu, không phải của compressor. Bộ dữ liệu này nằm giữa hai thái cực: phần lớn field lặp lại (dễ nén), traceId thì ngẫu nhiên (khó nén). Dữ liệu của bạn sẽ cho tỉ lệ khác, ở cuối bài có cách đo trên dữ liệu thật. Cả bốn container nạp đúng cùng một dãy document (sinh bằng seed cố định, _id cũng cố định), 100 lần insertMany mỗi lần 10.000 document, để thời gian chỉ cộng phần insertMany, không cộng thời gian sinh dữ liệu ở mongosh. Index tạo sau khi nạp xong.
Lab 2: dung lượng trên đĩa
Sau khi nạp và fsync (ép checkpoint), đọc $collStats với storageStats (e3-sizes-*.out; MiB = 1.048.576 byte):
| Compressor | dataSize (BSON) | storageSize (file) | dataSize / storageSize | Tổng index |
|---|---|---|---|---|
| snappy (mặc định) | 440,4 MiB | 168,5 MiB | 2,61 | 89,3 MiB |
| zstd | 440,4 MiB | 93,8 MiB | 4,70 | 89,3 MiB |
| zlib | 440,4 MiB | 109,1 MiB | 4,04 | 89,7 MiB |
| none | 440,4 MiB | 457,6 MiB | 0,96 | 89,3 MiB |
dataSizelà tổng kích thước BSON, giống hệt ở cả bốn container (461.796.477 byte). Chênh lệch ởstorageSizelà toàn bộ tác dụng của compressor: zstd nhỏ hơn snappy 44%. [quan sát] Ở bộ dữ liệu này zstd (mức mặc định 6) vừa nhỏ hơn zlib vừa rẻ hơn về CPU (lab 3); đừng khái quát sang dữ liệu khác.nonelớn hơn cảdataSize(thêm 3,9%): page vẫn có header, căn theo block 4 KB và có chỗ trống chờ tái sử dụng (4,6 MiB). "Không nén" không có nghĩa là "đĩa bằng dữ liệu".- Index không đổi: bốn index phụ có kích thước trùng từng byte giữa bốn container (13,87 / 6,27 / 39,09 / 12,24 MiB); chỉ
_id_dao động (17,86 đến 18,24 MiB). Đúng nhưcreationString: compressor của collection không chạm vào index. Và với snappy, index (89,3 MiB) bằng 53% collection (168,5 MiB): tối ưu collection mà quên index là tối ưu một nửa bức tranh.
[quan sát] Bộ đếm nén của WiredTiger: snappy ghi 9.208 lần page, tất cả ở nhóm tỉ lệ nén "smaller than 4"; zstd và zlib ghi khoảng 4.040 lần, tất cả ở nhóm "smaller than 8"; không page nào nén thất bại. Số lần ghi chỉ bằng nửa không phải ngẫu nhiên: xem lab 4.
Lab 3: cái giá lúc ghi
Nạp 1.000.000 document ba lần liên tiếp mỗi container (drop rồi nạp lại), ghi median của tổng thời gian các lệnh insertMany (e2-load-*.out). Rồi ba lần nạp nữa vào một collection tạm chỉ có index _id_, đo CPU của mongod (user_time_us cộng system_time_us trong serverStatus) và ghi median (e2b-cpu.out):
| Compressor | Thời gian insert 1 triệu (median 3 lần) | CPU của mongod cho lần nạp + checkpoint (median 3 lần) |
|---|---|---|
| snappy | 9,1 s (khoảng 110.000 doc/s) | 2,76 s |
| zstd | 9,3 s | 5,86 s (2,1 lần snappy) |
| zlib | 9,5 s | 8,35 s (3,0 lần snappy) |
| none | 9,2 s | 2,82 s |
Thời gian insert gần như không đổi (9,1 đến 9,5 s). Ba lần nạp thêm cho khoảng rộng hơn (median 9,2 / 11,3 / 10,1 / 9,6 s, máy bận), nên chỉ kết luận được: trên máy này, ở workload này, compressor không làm latency insert đổi một cách ổn định. CPU thì khác rõ: zlib gấp ba snappy, zstd gấp đôi, snappy và none ngang nhau. Chỉ có 3 lần đo, nhưng khoảng của chúng không chồng lên nhau giữa các nhóm: snappy 2,6–3,0 s, none 2,3–2,9 s, zstd 5,0–6,9 s, zlib 8,2–9,8 s.
[suy luận] Cách giải thích hợp lý: nén diễn ra khi page được ghi xuống (thread nền của WiredTiger, lúc eviction hoặc checkpoint), và cache 1 GB chưa bị áp lực nên thread của client không phải tự đi nén; chi phí hiện ở CPU chứ không ở latency. Tôi không kiểm tra riêng giả thuyết này. Nó hàm ý rằng khi máy thiếu CPU hoặc cache bị ép, compressor đắt có thể lộ ra thành latency, và đó là chuyện của bài về cache. Bộ đếm bytes written for leaf pages cho thấy lượng việc nén: snappy 470,5 MiB thành 172,5; zstd 470,1 thành 106,6; zlib 468,5 thành 111,6; none 471,6 thành 471,6.
Lab 4: cái giá lúc đọc
- COLLSCAN với filter không khớp gì (
{ "shipping.city": "Nowhere" },hint({ $natural: 1 })): mọi document phải được giải nén và đọc, không có gì gửi về client. - Point read:
findOne({ _id })với_idngẫu nhiên (5.000 lần cho trạng thái nguội, 20.000 lần × 5 pass cho nóng; thời gian gồm cả vòng đi về của mongosh). - Nguội = ngay sau
docker restart: cache WiredTiger trống, nhưng page cache của OS không bị xoá (xem giới hạn bên dưới). Nóng = dữ liệu đã nằm trong WiredTiger cache.
Giới hạn quan trọng: tôi không xoá page cache của hệ điều hành (nó ảnh hưởng cả VM Docker mà các lab khác đang dùng), nên "nguội" chỉ nghĩa là cache WiredTiger trống. File dữ liệu vẫn nằm trong RAM của VM, "đọc đĩa" thực chất là sao chép từ RAM. Lab thấy được chi phí giải nén và số byte phải lấy từ file, nhưng không thấy thời gian chờ đĩa thật.
Median của 3 lần (nguội) hoặc 7 lần và 5 pass (nóng) (e4-*.out):
| snappy | zstd | zlib | none | |
|---|---|---|---|---|
| COLLSCAN nguội: thời gian | 455 ms | 684 ms | 1.098 ms | 452 ms |
| COLLSCAN nguội: byte đọc từ file | 168,5 MiB | 93,3 MiB | 108,5 MiB | 453,5 MiB |
| COLLSCAN nguội: byte đưa vào cache | 448,6 MiB | 448,2 MiB | 448,2 MiB | 449,1 MiB |
| 5.000 point read nguội: µs mỗi lần | 190 | 240 | 349 | 168 |
cùng 5.000 _id, lần 2 (đã nóng): µs | 134 | 138 | 147 | 135 |
| COLLSCAN nóng: thời gian | 193 ms | 204 ms | 191 ms | 222 ms |
| 20.000 point read nóng: µs mỗi lần | 136 | 142 | 140 | 129 |
Dao động của COLLSCAN nguội (3 lần): snappy 401–661 ms, none 386–860 ms, zlib 1.081–1.175 ms.
1. Khi dữ liệu đã ở trong cache, compressor không còn liên quan. COLLSCAN nóng 191 đến 222 ms, point read nóng 129 đến 142 µs: nằm trong dao động của máy (con số 222 ms của none tôi không giải thích được, nên không gán cho none chậm hơn). Page trong cache không nén, nên đọc nó giống nhau bất kể từng được nén bằng gì.
2. Khi page phải vào cache từ file, compressor trả giá bằng CPU theo thứ tự nhẹ đến nặng: none ≈ snappy (455 so với 452 ms) < zstd (1,5 lần none) < zlib (2,4 lần none). Point read cũng vậy: zlib 349 µs, none 168 µs. Byte đưa vào cache gần như bằng nhau (448 đến 449 MiB): giải nén cho ra cùng một dạng.
3. Cái được là số byte lấy từ file: 168,5 / 93,3 / 108,5 / 453,5 MiB. Trên máy này chúng gần như miễn phí vì đến từ RAM của VM; trên đĩa thật thì không. Ghép số byte đo được với hai tốc độ đĩa giả định (phép tính minh hoạ, không đo trên đĩa thật: cộng thời gian I/O với thời gian scan đã đo, coi như không chồng lên nhau):
| Tốc độ đọc giả định | snappy | zstd | zlib | none |
|---|---|---|---|---|
| 200 MiB/s (đĩa chậm hoặc nhiều tải) | 1,30 s | 1,15 s | 1,64 s | 2,72 s |
| 2.000 MiB/s (NVMe nhanh) | 0,54 s | 0,73 s | 1,15 s | 0,68 s |
Bài học nằm ở hình dạng chứ không ở từng ô: đĩa càng chậm thì nén mạnh càng có lợi, đĩa càng nhanh thì CPU giải nén càng lộ ra.
Nén mạnh hơn thì page trong cache to hơn. Chia byte đưa vào cache cho số page đọc: trung bình khoảng 27 KiB mỗi page với none (16.842 page), 50 KiB với snappy (9.274), và 114 KiB với zstd và zlib (khoảng 4.040). Trên đĩa, mỗi page chỉ khoảng 19 KiB (snappy) đến 28 KiB (none, zlib). [tài liệu] Khi bật block compression, "kích thước page đã cấu hình sẽ không khớp với kích thước thật trên đĩa", và lượng dữ liệu đưa vào nén dựa trên kết quả nén trước đó. [quan sát] leaf_page_max=32KB vẫn nằm trong creationString; page trên đĩa nằm dưới mức đó, nhưng một page với zstd hay zlib mở ra trong cache lớn gấp khoảng bốn lần none. Hệ quả thấy được ở point read nguội: cùng 5.000 lần đọc đưa vào cache 134 MiB với none, 246 MiB với snappy, 336 và 340 MiB với zstd và zlib. Mỗi point read ngẫu nhiên vào page nén mạnh phải giải nén cả page lớn để lấy một document, và [suy luận] đó là một phần lý do zlib 349 µs, zstd 240 µs, none 168 µs (tôi không tách riêng yếu tố này).
Nén không làm cache nhỏ đi
Đây là hiểu nhầm đắt nhất, và lab đã có bằng chứng. Sau khi mọi thứ nằm trong cache, bytes currently in the cache của collection orders:
| snappy | zstd | zlib | none | |
|---|---|---|---|---|
Trên đĩa (storageSize) | 168,5 MiB | 93,8 MiB | 109,1 MiB | 457,6 MiB |
| Trong WiredTiger cache | 494,1 MiB | 492,9 MiB | 492,9 MiB | 495,8 MiB |
Cache của collection gần như không đổi: 492,9 đến 495,8 MiB, trong khi trên đĩa none (457,6 MiB) lớn gấp 4,9 lần zstd (93,8 MiB). Với zstd, một collection chiếm 93,8 MiB trên đĩa phải có 492,9 MiB trong cache, gấp 5,3 lần; với snappy là 2,9 lần, và cũng lớn hơn 12% so với dataSize BSON (494,1 so với 440,4 MiB, [suy luận] phần chênh là cấu trúc quản lý của page trong bộ nhớ). Cache 1 GB của lab chứa trọn collection này cộng index _id_ (khoảng 26,5 MiB trong cache) mà vẫn còn dư, nhưng chỉ vì collection nhỏ. [tài liệu] Manual MongoDB nói đúng điều này: dữ liệu collection trong WiredTiger cache không nén và có biểu diễn khác dạng trên đĩa.
Hệ quả: dung lượng trên đĩa, nén hay không, không cho biết cần bao nhiêu RAM. Compressor mạnh làm nhỏ đĩa, I/O và bản backup, nhưng không làm working set vừa cache dễ hơn; câu "working set có vừa cache không" là chủ đề của bài Cache, Eviction & Working Set.
Lab 5: prefix compression cho index
Hai collection refs_on và refs_off chứa cùng 1.000.000 document nhỏ (_id, ref, traceId, tenantId, n) với ba index giống nhau; ở refs_off mỗi index tạo với configString: "prefix_compression=false" (e5-prefix-build.out). Ba loại key cho ba đầu của phổ:
ref:"https://shop.example.vn/api/v2/orders/2026/10/"(46 ký tự giống nhau) cộng 8 chữ số tăng dần.traceId: 32 ký tự hex ngẫu nhiên, gần như không có prefix chung.tenantId:"t0000"đến"t0499", 500 giá trị lặp lại 2.000 lần mỗi giá trị.
Kích thước trên đĩa, và lượng cache index chiếm sau khi quét hết nó một lần từ cache trống (e5b-prefix-cache.out, container snappy vừa restart):
| Index | Đĩa, prefix bật | Đĩa, prefix tắt | Tắt / bật | Cache, bật | Cache, tắt |
|---|---|---|---|---|---|
ref_1 (prefix dài) | 12,28 MiB | 65,96 MiB | 5,4 lần | 20,28 MiB | 72,99 MiB |
tenantId_1 (500 giá trị lặp) | 7,82 MiB | 13,57 MiB | 1,7 lần | 15,91 MiB | 21,56 MiB |
traceId_1 (ngẫu nhiên) | 39,09 MiB | 42,34 MiB | 1,08 lần | 46,58 MiB | 49,78 MiB |
- Tác dụng khác nhau rất xa tuỳ key. Key có tiền tố chung dài (URL, đường dẫn, mã có tiền tố, dãy tăng dần) co 5,4 lần; key lặp lại co 42%; key ngẫu nhiên chỉ co 8%.
- Nó làm nhỏ cả cache, đúng như tài liệu (
ref_1: 20,3 so với 73,0 MiB), khác với block compression ở lab trước. - Dạng trong cache lớn hơn dạng trên đĩa ngay cả khi có prefix compression (20,3 so với 12,3 MiB): hai dạng của page là hai dạng thật.
Tốc độ. Tài liệu WiredTiger cảnh báo prefix compression làm duyệt ngược và tra key ngẫu nhiên tốn thêm CPU và bộ nhớ. Lab không thấy khác biệt ổn định: quét covered toàn index khi cache nóng, 9 lần xen kẽ, median 228 ms (bật) so với 226 ms (tắt) cho ref_1, 224 so với 224 ms cho traceId_1; quét ngược 227 so với 230 ms và 234 so với 223 ms (e5c-prefix-time.out, e5d-prefix-reverse.out). 20.000 lần tra ngẫu nhiên theo ref cho 2,998 s (bật) so với 3,789 s (tắt) nhưng dao động giữa các pass từ 2,7 đến 6,3 s (máy bận), nên không kết luận được gì. Tôi không có bằng chứng prefix compression chậm hơn ở quy mô này, và cũng không có bằng chứng ngược lại.
Lab 6: đổi compressor thì dữ liệu cũ thế nào
Hai thí nghiệm cho hai câu hỏi hay gặp.
Đổi cấu hình toàn cục có nén lại dữ liệu cũ không? Dừng container zlib, mở lại cùng volume bằng một mongod mới với --wiredTigerCollectionBlockCompressor none, rồi tạo collection mới và nạp 200.000 document (e7b-global-change.out):
setting now: {"blockCompressor":"none"}
{"coll":"orders","block_compressor_in_creationString":"zlib","count":1000000,"storageSizeBytes":114372608}
{"coll":"after_change","block_compressor_in_creationString":"none","count":200000,"storageSizeBytes":94892032}
old zlib blocks still readable: {...} count 99507 (countDocuments({ status: "pending" }) trên collection zlib)orders vẫn mang block_compressor=zlib và vẫn 109,1 MiB; collection mới mang none. Dữ liệu cũ đọc được bình thường: [tài liệu] manual nói collection cũ "tiếp tục dùng compressor đã chọn khi được tạo"; tài liệu WiredTiger nói block trong file không ghi compressor nào đã nén nó, nên compressor phải được lấy từ cấu hình của table. [quan sát] đúng như vậy: creationString của orders vẫn là zlib.
Muốn nén lại dữ liệu cũ thì làm gì? Tạo collection mới với compressor mong muốn rồi copy dữ liệu sang. Từ collection snappy 200.000 document sang một collection tạo với configString: "block_compressor=zstd", bằng $merge (e7-override.out):
storageSize | |
|---|---|
c_regular (snappy) | 32,73 MiB |
c_zstd (copy của nó, tạo với block_compressor=zstd) | 18,64 MiB |
Copy 200.000 document mất khoảng 0,86 giây ở lab này. Với collection cỡ trăm GB đây là một việc vận hành lớn (thêm đĩa tạm, thêm ghi, chuyển ứng dụng sang tên mới), không phải "đổi một cờ". Đó là lý do nên đo trên mẫu trước: copy 1–5% dữ liệu thật sang bốn collection nhỏ, mỗi collection một compressor, so storageSize và CPU; ở quy mô nhỏ, đây là cách rẻ nhất để biết tỉ lệ nén của dữ liệu của bạn.
Chọn compressor thế nào
Từ lab 1 triệu đơn trên một máy (đều là [quan sát] ở bộ dữ liệu này):
- snappy (mặc định) là điểm cân bằng: nén 2,6 lần với gần như không tốn CPU thêm so với không nén.
- zstd cho đĩa nhỏ nhất ở đây, đổi lại CPU gấp đôi lúc ghi và COLLSCAN chậm gấp rưỡi khi page phải nạp từ file vào cache. Hợp với dữ liệu ít đọc, nơi đĩa hoặc backup là chi phí chính và CPU còn dư. [tài liệu] Manual đặt zstd làm mặc định cho time-series collection.
- zlib ở đây vừa lớn hơn zstd vừa tốn CPU hơn. Lab này không cho lý do để chọn nó, nhưng đó là kết quả của dữ liệu này, đừng khái quát.
- none chỉ hợp khi dữ liệu vốn đã nén hoặc đĩa cực nhanh và CPU cực hiếm; nhớ rằng nó lớn hơn
dataSize.
Quy trình đáng tin: (1) copy mẫu dữ liệu thật vào bốn collection, mỗi collection một compressor; (2) so storageSize; (3) đo CPU khi nạp và khi quét nguội; (4) tính lại với tốc độ đĩa của bạn; (5) chỉ khi đó đổi cấu hình, biết rằng nó chỉ áp cho collection mới.
So với PostgreSQL
Cùng bài toán: lưu 1 triệu đơn hàng. [tài liệu] PostgreSQL lưu mỗi bảng (gọi là heap) và mỗi index như một dãy page kích thước cố định (thường 8 kB, chọn lúc biên dịch server); một dòng có thể nằm ở bất kỳ page nào của bảng (mọi page của bảng tương đương nhau về logic), và index là cấu trúc riêng trỏ tới vị trí dòng. Giá trị lớn đi đường TOAST: khi một dòng vượt khoảng 2 kB, server nén và/hoặc đẩy giá trị lớn sang bảng phụ; kiểu nén chọn theo từng cột. Durability đến từ WAL: thay đổi vào log trước, vào file dữ liệu sau. Với index B-tree, tài liệu mô tả deduplication cho các key trùng nhau.
So với WiredTiger: collection không phải heap mà là một B-tree theo RecordId (hoặc theo _id nếu clustered), page không cố định kích thước và cấu hình được theo từng table, nén là cả block của page lúc ghi xuống (collection) cộng prefix compression (index). Hai thiết kế giải cùng một nhu cầu bằng những đường khác nhau: TOAST nén từng giá trị lớn, block compression nén từng page, deduplication và prefix compression làm nhỏ index bằng hai kỹ thuật không tương đương. Bài này không đo PostgreSQL, nên không có kết luận "bên nào nhỏ hơn hay nhanh hơn".
Bản đồ: bốn cơ chế còn lại của Phần này
page trong RAM
│
┌───────────────────┼───────────────────┬──────────────────────┐
▼ ▼ ▼ ▼
cache đầy, ghi log trước, nhiều phiên bản nhiều thread
page nào đi? checkpoint sau của cùng một dữ liệu cùng chạm một page
[bài về cache] [bài về journal] [bài về MVCC] [bài về concurrency]- Cache, Eviction & Working Set: cache chứa gì, page sạch và bẩn, khi nào page bị đẩy ra, vì sao working set quan trọng hơn kích thước database.
- Journal & Checkpoint: làm sao thay đổi chỉ nằm trong RAM mà sống sót qua mất điện: write-ahead log, checkpoint, phục hồi sau crash.
- WiredTiger MVCC: update chain trên page, history store (
WiredTigerHS.wttrong danh sách file), vì sao snapshot cũ giữ lại các phiên bản. - Concurrency: Locks, Latches & Tickets: những thứ chặn nhau thật sự ở từng tầng, và ticket là gì.
Những lỗi thường gặp
- "Nén làm MongoDB chậm" hoặc "nhanh", không đo. Lab cho cả hai chiều: khi dữ liệu nằm trong cache, compressor không còn liên quan (129 đến 142 µs mỗi point read nóng ở cả bốn); khi page phải vào từ file, zlib chậm gấp 2,4 lần none ở COLLSCAN nguội (cache WiredTiger trống, file vẫn trong page cache của OS). Chiều thứ ba là đĩa thật, không đo ở đây. Câu trả lời đúng là "tuỳ working set có nằm trong cache không".
- Nhìn
dataSizehoặcstorageSizeđể đoán RAM cần có. Page trong cache không nén: collection 93,8 MiB trên đĩa (zstd) chiếm 492,9 MiB trong cache. - Chỉ nhìn collection, quên index. Index (89,3 MiB) bằng 53% collection snappy, không compressor nào chạm vào, và prefix compression làm nó đổi nhiều (5,4 lần cho key có tiền tố dài) hoặc gần như không (8% cho key ngẫu nhiên).
- Đổi
blockCompressorrồi mong dữ liệu cũ nhỏ lại. Cấu hình toàn cục chỉ ảnh hưởng collection và index tạo sau; muốn nén lại phải copy sang collection mới. - Lấy tỉ lệ nén của người khác cho dữ liệu của mình. 2,61 lần của snappy là của bộ dữ liệu này (nhiều field lặp, một field hex ngẫu nhiên). Đo trên mẫu.
- Coi
nonelà "không tốn gì". Nó lớn hơndataSize3,9% và đọc 453,5 MiB từ file thay vì 168,5 MiB cho cùng một COLLSCAN. - Tin số đo trên Docker laptop như số production. Lab không có độ trễ đĩa thật, chạy chung máy với lab khác, và có lần container bị OOM. Cái đáng tin là hình dạng (CPU tăng theo thứ tự nào, byte từ file giảm bao nhiêu), không phải từng mili giây.
Tóm tắt: mang theo gì sang bài sau
- Collection và index là table riêng, mỗi table một B-tree trong một file
.wt:ordersvới 5 index là 6 file,storageSizebằng đúng kích thước file. Document nằm ở table collection theo RecordId,_id_chỉ là một index; clustered collection lưu document theo_idvà bỏ file_id_. - Page có hai dạng: làm việc trong RAM (không nén, mang cả thay đổi chưa ghi) và đóng gói trên đĩa (reconciliation), nén theo block, ghi sang chỗ mới, không đè.
- Collection: block compression, snappy mặc định. Index: prefix compression, không có block compression.
- Lab (1 triệu đơn, một máy, 2 CPU):
storageSize168,5 / 93,8 / 109,1 / 457,6 MiB (snappy / zstd / zlib / none) cho 440,4 MiB BSON; CPU của mongod khi nạp 2,8 / 5,9 / 8,4 / 2,8 s; COLLSCAN nguội (cache WiredTiger trống, file vẫn trong page cache của OS) 455 / 684 / 1.098 / 452 ms; đọc nóng không khác nhau. - Cache không nhỏ đi vì nén: 493 đến 496 MiB cho cả bốn, gấp 2,9 đến 5,3 lần dung lượng đĩa. Prefix compression thì có làm nhỏ cache (
ref_1: 20,3 so với 73,0 MiB). - Cấu hình mới chỉ áp cho collection và index tạo sau. Nén lại = copy sang collection mới.
- Quyết định bằng đo trên mẫu dữ liệu thật, với tốc độ đĩa và CPU của bạn.
Hỏi & đáp
Collection orders (không clustered) có index _id_ và 4 index phụ. Chúng nằm trên đĩa ở đâu?
Cùng 1.000.000 đơn, nạp vào hai container: một dùng zstd (93,8 MiB trên đĩa), một dùng snappy (168,5 MiB). Sau khi quét hết, collection chiếm bao nhiêu trong WiredTiger cache?
Bạn cần chọn compressor cho một collection 500 GB, và không biết dữ liệu của mình nén được bao nhiêu. Cách đáng tin cậy nhất?
Bạn đổi storage.wiredTiger.collectionConfig.blockCompressor từ snappy sang zstd trong file cấu hình rồi restart mongod. Collection orders 500 GB đã có thì sao?
Một thư viện cất sách trong hộp ép chặt dưới tầng hầm; muốn đọc, nhân viên mang hộp lên bàn rồi mở sách ra. Thư viện đổi sang loại hộp ép chặt hơn, hầm chứa được gấp đôi số sách. Số sách để vừa trên bàn làm việc cùng lúc thay đổi ra sao?
Index { ref: 1 } trên 1.000.000 document, mọi key bắt đầu bằng cùng một URL dài 46 ký tự. Tạo nó với prefix_compression=false thay vì mặc định thì điều gì xảy ra ở lab?
Lab nạp 1 triệu document: thời gian insert chỉ 9,1 đến 9,5 s ở cả snappy, zstd, zlib và none, nhưng CPU của mongod là 2,8 / 5,9 / 8,4 / 2,8 s. Cách giải thích hợp lý nhất?
Nếu bỏ hết thuật ngữ: một kho hàng cất đồ trong thùng, mỗi loại hàng một toà nhà, mỗi toà chia thành các ngăn kệ có bảng chỉ dẫn. Làm việc thì phải khiêng ngăn lên bàn và mở thùng ra; trả về thì đóng thùng lại và cất vào chỗ trống mới. Hút chân không chặt hơn thì kho chứa được nhiều hơn và tiền thuê kho rẻ hơn, nhưng bàn làm việc không rộng ra chút nào, và mỗi lần mở thùng tốn thêm công. Muốn biết nên đóng gói thế nào, đừng nghe lời khuyên chung: lấy một ít hàng thật của mình ra thử.
Bài tiếp theo
Bài này nói WiredTiger làm gì với một page, chưa nói chuyện gì xảy ra khi RAM không đủ. Ta đã thấy 493 MiB trong cache cho một collection chỉ 94 MiB trên đĩa; nếu working set to gấp ba cache thì sao? Page nào bị đẩy ra? Page "bẩn" (đã sửa, chưa ghi xuống) khác page sạch thế nào, và vì sao thread của chính ứng dụng đôi khi bị kéo đi dọn cache?
Cache, Eviction & Working Set trả lời: kích thước cache, page sạch và bẩn, ngưỡng eviction, thread nền và thread ứng dụng, và một lab với working set lớn hơn cache để thấy độ trễ tăng ra sao khi hit chuyển thành miss.
Tài liệu tham khảo
- MongoDB: WiredTiger Storage Engine (compression, memory use, snapshots and checkpoints, journal)
- MongoDB: Configuration File Options (
storage.wiredTiger.collectionConfig.blockCompressor,indexConfig.prefixCompression,engineConfig.zstdCompressionLevel) - MongoDB: Clustered Collections
- MongoDB:
$collStats(storageStats, accuracy after unclean shutdown) - MongoDB:
db.createCollection()(storageEngine,indexOptionDefaults, Specify Storage Engine Options) - MongoDB:
cursor.showRecordId() - WiredTiger: B-Trees
- WiredTiger: Data File Format
- WiredTiger: Block Manager
- WiredTiger: Tuning page size and compression (
leaf_page_max,internal_page_max,memory_page_max,split_pct, key-prefix, dictionary, block compression) - WiredTiger: Row Store and Column Store
- WiredTiger: Compressors
- WiredTiger: Schema (data files,
.wtand metadata) - PostgreSQL: Database Page Layout (fixed-size pages, usually 8 kB)
- PostgreSQL: TOAST
- PostgreSQL: B-Tree Indexes (deduplication)
- PostgreSQL: Write-Ahead Logging (WAL)