Cache (P3/3): Dirty page, eviction và chọn kích thước cache
Ở phần trước: một COLLSCAN quét cả collection làm thao tác/s của hot set giảm gần một nửa, từ 3.724 xuống 1.928. Dữ liệu vượt cache thì page đang dùng bị bỏ rồi đọc lại.
- Cần đọc trước: Lab: hit, miss và working set
- Dẫn tới: Journal & Checkpoint. Với bài này, phần Cache, Eviction & Working Set khép lại tại đây.
Lab 5: dirty page và áp lực eviction
Hot reader (61.000 document, đã warm-up) chạy 75 giây; từ giây 25, các writer chạy 40 giây trên cùng 1.000.000 document: updateOne ngẫu nhiên đặt lại body 2 KB (mỗi lần sửa một document ở page ngẫu nhiên, phải đọc page lên rồi biến nó thành dirty page), hoặc insertOne 2 KB nối đuôi. Mỗi giây ta ghi lại độ trễ của luồng đọc và các counter (e4-dirty.out, e4-dirty-run1-nocounters.out, e4-dirty-rerun.out). Độ trễ lấy trong cửa sổ giây 30–64:
| Tải ghi | p50 đọc (trước → trong) | Thao tác đọc/s | Dirty tối đa | Cache đầy tối đa | Application thread tự evict | Application thread tự đọc page |
|---|---|---|---|---|---|---|
| 1 writer, 160 sửa/s | 0,16–0,23 → 0,17–0,23 ms | ≈ không đổi | 4,1–6,5% | 72–81% | 0 | 1,6–2,1 s tổng |
| 4 writer sửa ngẫu nhiên (lần 1) | 0,17 → 0,77 | 4.970 → 740 | 16,7% | 90% | 149 ms tổng | 45 s |
| 4 writer sửa ngẫu nhiên (lần 2) | 0,19 → 1,22 | 3.791 → 186 | 6,3% | 85% | 0 | 76 s |
| 8 writer sửa ngẫu nhiên (lần chạy lại) | 0,15 → 0,97 | 5.061 → 652 | 15,9% | 95% | 0 | 110 s |
| 4 writer insert nối đuôi | 0,12–0,26 → 0,41–0,49 | 2.519–5.908 → 1.554–1.753 | 4,4–6,3% | 85–91% | 0 | 1,7–3,5 s |
Cảnh báo về độ nhiễu: hai lần chạy cùng cấu hình "4 writer sửa" cách nhau gấp 4 lần về thao tác/s đọc ([suy luận] do VM dùng chung đĩa với các lab khác). Hình dạng thì ổn định:
- Dirty bám quanh
eviction_dirty_target. Với một writer nhẹ, dirty tối đa 4,1–6,5% cache, quanh mốc 5%; [suy luận] eviction worker liên tục ghi dirty page xuống để giữ dirty ở mức đó. Dirty chỉ lên 16–17% khi tải ghi ngẫu nhiên nặng, rồi bị kéo xuống. Ở hai lần còn counter cuối, "number of times dirty trigger was reached" là 0, và ở mọi lần dirty tối đa 16,7% chưa tới 20%: lab này không tái hiện được vách đá củaeviction_dirty_trigger, nên phần mô tả mốc 20% ở phần trước (WiredTiger cache là gì) chỉ dựa vào tài liệu. - Thời gian application thread bị kéo đi evict gần như bằng 0 (tối đa 149 ms trong 35 giây), trong khi thời gian chúng tự đọc page lên cache là 45–110 giây cộng dồn (bộ đếm gộp cả luồng đọc lẫn các writer). [suy luận] Ở lab này, thứ làm chậm luồng đọc không phải "bị kéo vào eviction" mà là tranh nhau CPU và đĩa (hai CPU, 8 writer, mỗi lần sửa kéo cả page vào) và chờ đọc page. Đây là điểm cần tách khi đọc counter ở production:
application thread time evictingtăng mới là dấu hiệu bị kéo vào eviction; cònapplication threads page read from disk to cache timetăng là cache miss. - Ghi nặng còn có thể làm mongod hết bộ nhớ. Ở container 2 GB, lần chạy lại 8 writer sửa và 4 writer insert bị OOM-kill ở cuối (
docker inspect:OOMKilled=true, exit 137). Số liệu theo từng giây vẫn còn nên vẫn nằm trong bảng, nhưng counter cuối (như số lần chạm dirty trigger) bị mất. Cache 0,5 GB không phải trần bộ nhớ của mongod: [tài liệu WiredTiger] cursor, session, data handle và bộ nhớ tạm khi đọc ghi page nằm ngoài cache, và page bị evict chỉ trả bộ nhớ về allocator, không làm tiến trình nhỏ lại. Lab không xác định phần nào đã đẩy mongod qua 2 GB. - Ghi nối đuôi dễ hơn ghi ngẫu nhiên nhiều. 4 writer insert đẩy 570–710 MB xuống đĩa mà dirty chỉ 4–6%, trong khi sửa ngẫu nhiên đẩy dirty lên 16–17% ở hai lần chạy. [suy luận] Page cuối B-tree luôn nằm sẵn trong cache và dùng lại được; sửa ngẫu nhiên thì mỗi lần biến một page khác thành dirty, và phải đọc page đó lên trước.
Giới hạn 20% và TransactionTooLargeForCache
Bài Transactions & Atomicity nói phần dirty bị giới hạn 20%. Thử lại trên replica set một node, cache 0,256 GB (20% = 51,2 MB), một transaction insert liên tục document 0,5 MB (e7-ttlfc.out):
lần 1: 168 insert (≈ 84 MB), dirty 90,8 MB (35%) trước insert thất bại
lần 2: 50 insert (≈ 25 MB), dirty 27,0 MB (10,5%)
lần 3: 49 insert (≈ 25 MB), dirty 26,5 MB
lỗi: code 388, labels [], "transaction is too large and will not fit in the storage engine cache"
sau abort: dirty 0 MB[tài liệu] Tham số transactionTooLargeForCacheThreshold (từ 6.2, mặc định 0,75) là tỉ lệ của phần dirty (20%) mà một transaction được phép chiếm để server còn tự thử lại: 15% tổng cache; transaction lớn hơn bị rollback vì áp lực cache thì nhận TransactionTooLargeForCache và không được thử lại. [quan sát] Điểm thất bại không phải một vạch sắc nét: 25 MB, 25 MB và 84 MB trên cùng cấu hình. Lần 2 và 3 thất bại ở 10,5% cache, dưới mốc 15%; lần 1 để dirty lên tới 35%, quá mốc 20%. Con số dùng được là: transaction chiếm quá một phần nhỏ cache có thể bị abort, không retry được. Import lớn thì chia lô, đúng như bài đó khuyên.
Chọn kích thước cache và theo dõi
Hit ratio không có sẵn, nhưng suy ra được
serverStatus không cho "cache hit ratio". Cái suy ra được và thật: miss mỗi thao tác = delta pages read into cache chia số thao tác. Trong Lab 1 ở phần trước (Lab: hit, miss và working set), mỗi lần tra _id luôn xin đúng 4 page, nên chia cho pages requested cho tỉ lệ miss theo page; ở workload khác số page mỗi thao tác khác nhau và con số chỉ là xấp xỉ. Hai chỉ số theo dõi theo thời gian, lấy delta chứ không lấy giá trị tuyệt đối:
pages read into cache / giây ↑ kéo dài → working set không vừa cache (hoặc vừa bị quét)
bytes currently in the cache ≈ 80-90% → bình thường, KHÔNG phải sự cố
tracked dirty bytes (% cache) > 5% kéo dài, tiến về 20% → ghi nhanh hơn eviction
application thread time evicting > 0 và tăng → application thread bị kéo vào làm eviction
application threads page read ... time tăng → application thread chờ đọc pageCache đầy 80% không phải dấu hiệu xấu: Lab 1 ở phần trước (Lab: hit, miss và working set) cho thấy nó là trạng thái bình thường. Dấu hiệu xấu là miss rate cao kéo dài cùng với latency tăng.
Thêm RAM hay sửa mô hình
Trước khi tăng cache, trả lời theo thứ tự:
1. Working set là gì? Phần nào ứng dụng thật sự chạm?
(đừng lấy kích thước database)
2. Có phần nào trong cache không nên ở đó?
→ index thừa (mỗi index được dùng là working set), document rộng quá mức cần
(projection không giảm byte vào cache), COLLSCAN định kỳ (đẩy hot set ra khỏi cache)
3. Sửa mô hình/index có đủ không?
→ subset pattern, index đúng, bỏ index chết, phân trang theo range
4. Vẫn không vừa? → thêm RAM (cache là phần của RAM; xem lại mặc định trước)
hoặc shard để chia working setKhông khuyến nghị đổi cacheSizeGB như thuốc chữa bệnh. [tài liệu] Tài liệu MongoDB khuyên tránh tăng cache vượt mặc định, vì RAM còn lại là cache OS cho dữ liệu nén; Lab 5 cho thấy mongod còn cần bộ nhớ ngoài cache và có thể bị OOM-kill. Nếu buộc phải, dùng cacheSizePct và kiểm tra hostInfo trong container. Về Atlas hoặc môi trường managed: chưa kiểm tra trong lab này, hãy đọc tài liệu của dịch vụ đó.
So với PostgreSQL
| MongoDB (WiredTiger) | PostgreSQL | |
|---|---|---|
| Bộ đệm riêng của engine | WiredTiger cache, mặc định lớn hơn của 50% × (RAM − 1 GB) và 0,256 GB; dữ liệu collection chưa nén | shared_buffers, mặc định thường 128 MB; tài liệu gợi ý khởi đầu 25% RAM cho máy chuyên dụng từ 1 GB RAM |
| Phần còn lại của RAM | filesystem cache của OS, giữ dữ liệu nén | PostgreSQL "cũng dựa vào cache của hệ điều hành" (tài liệu shared_buffers) |
| Đơn vị | page của B-tree (cache) | page BLCKSZ, thường 8 KB |
| Lời khuyên về cỡ | tránh tăng quá mặc định | khó có lợi khi quá khoảng 40% RAM, vì còn cache của OS |
[tài liệu] Cả hai hệ thống đều xếp một bộ đệm riêng của engine trên cache của OS, nên cả hai có "hai tầng". Điều đó không làm cơ chế eviction hay hiệu năng của chúng tương đương; bài không so cơ chế thay thế page của PostgreSQL và không đo PostgreSQL.
Những lỗi thường gặp
- Đo hiệu năng bằng kích thước database. Lab 1 ở phần trước (Lab: hit, miss và working set): cùng 2 GB, p50 0,12 ms ở 0,25x và 0,14 ms ở 4,3x, miss mỗi thao tác từ 0 lên 0,81; lệch 90/10 giữ miss ở 0,08.
- Lo vì cache đầy 85%. Đó là trạng thái bình thường (target 80%, trigger 95%).
- Đo cache miss bằng đĩa nhanh rồi kết luận miss rẻ. Lab 1 ở phần trước (Lab: hit, miss và working set): ở
mainmiss rẻ vì cache OS che; cùng workload ởslowmất thêm 28% thao tác/s. - Thêm index "cho chắc". Index được dùng là working set; Lab 2: 8 index luân phiên làm query không liên quan chậm 32% ở
main(5% ởslow). - Chạy COLLSCAN định kỳ cạnh dữ liệu ít được đọc. Lab 3: sau lần quét, dữ liệu đó phải đọc lại 128 page/giây trong 10 giây.
- Một transaction hay một import cho cả lô. Lab 5: abort ở 25–84 MB trên cache 0,256 GB, không retry được.
- Tăng cache sát RAM. Cache không phải trần bộ nhớ của mongod; lab bị OOM-kill ở 2 GB khi ghi nặng, dù cache chỉ 0,5 GB.
Cột mốc: Bạn đã có thể giải thích dirty page và eviction, biết dirty page được ghi xuống trước khi bị bỏ, và biết cache đầy 80% không phải lỗi. Bài này khép lại tại đây.
Hỏi & đáp
Trong lúc ghi nặng, application thread time evicting (usecs) gần 0 nhưng application threads page read from disk to cache time rất lớn. Chẩn đoán đúng?
Một dashboard có p95 tăng, pages read into cache cao kéo dài, working set ước tính gấp 3 lần cache. Việc đầu tiên nên làm?
Nếu bỏ hết thuật ngữ: tốc độ phục vụ của một quầy phụ thuộc vào việc những thứ khách hay gọi có vừa trên quầy hay không, không phải kho to cỡ nào. Quầy luôn chừa một khoảng trống để thay món, và khi khách gọi món vượt sức chứa của quầy, người phục vụ phải chạy ra kho: nhanh hay chậm tuỳ kho gần hay xa. Một xe hàng đổ cả kho lên quầy trong một đêm sẽ làm những món ít gọi bị đẩy xuống đất, và sáng sau những món đó phải lấy lại từng cái.
Bài tiếp theo
Bài này cho thấy dirty page nằm trong cache và phải được ghi xuống. Nhưng ghi xuống khi nào, và nếu mất điện giữa lúc đó thì sao? Lệnh ghi đã được xác nhận nằm ở đâu trong lúc page chưa được ghi?
Journal & Checkpoint trả lời những câu đó: write-ahead log, j: true, chu kỳ checkpoint, và cách khôi phục sau crash. Cách giữ nhiều phiên bản của cùng một document trong cache, và vì sao một snapshot chạy lâu làm cache căng thẳng, là chuyện của bài WiredTiger MVCC.
Tài liệu tham khảo
- WiredTiger Storage Engine (memory use, cache size mặc định, container, nén)
- Configuration File Options: storage.wiredTiger.engineConfig.cacheSizeGB / cacheSizePct
- serverStatus: wiredTiger.cache
- Server Parameters (transactionTooLargeForCacheThreshold, wiredTigerEngineRuntimeConfig)
- WiredTiger: Cache and eviction tuning
- WiredTiger: Eviction (architecture guide)
- WiredTiger: Cache (architecture guide)
- PostgreSQL: Resource Consumption (shared_buffers)