WiredTiger MVCC (P4/4): So với PostgreSQL và lỗi thường gặp

5 phút đọcSeries: MongoDB: từ gốc đến internals

Ở phần trước: một snapshot giữ mở trong lúc 300.000 lệnh $inc vẫn đọc tổng n là 0, còn đọc mới nhất là 300.000. Version cũ không được dọn khi snapshot còn mở, và file history store không co lại ngay.

  • Cần đọc trước: Ba lab về snapshot bị giữ
  • Dẫn tới: Concurrency: Locks, Latches & Tickets. Với bài này, WiredTiger MVCC khép lại tại đây.

So với PostgreSQL

Cùng một bài toán: nhiều transaction cùng đọc và ghi một dòng/document. [tài liệu] PostgreSQL cũng dùng MVCC: mỗi câu lệnh thấy một snapshot, và đọc không chặn ghi, ghi không chặn đọc. Khác biệt nằm ở chỗ cất version cũ:

PostgreSQLMongoDB (WiredTiger)
Version cũ nằm ở đâungay trong heap (file của bảng): UPDATE ghi một dòng mới, dòng cũ vẫn ở đó cho tới khi bị xoáversion mới nhất trong file của bảng; version cũ trong update chain (cache) rồi sang một file riêng, history store
Ai biết dòng nào thấy đượcmỗi dòng mang xmin và xmax (transaction id tạo ra và làm nó hết hiệu lực) trong headermỗi update mang transaction id và timestamp; snapshot gồm danh sách transaction đang chạy cộng read timestamp
Ai xoá version cũVACUUM (autovacuum chạy nền); không có nó dòng chết tích tụ thành bloatreconciliation và checkpoint, khi oldest timestamp tiến lên; không có tiến trình riêng tên "vacuum"
Một transaction chạy lâu làm gìVACUUM không xoá được dòng chết còn có thể bị nhìn thấy; khi cứu sự cố transaction ID wraparound, manual khuyên kết thúc transaction mở lâupinned timestamp bị giữ, history store giữ nhiều version hơn; bị chặn bởi transactionLifetimeLimitSeconds

[tài liệu] Manual PostgreSQL: UPDATE hoặc DELETE không xoá ngay version cũ của dòng, vì MVCC cần nó; VACUUM thông thường xoá dòng chết và đánh dấu chỗ trống để dùng lại nhưng không trả không gian cho hệ điều hành (trừ các page cuối bảng trống hẳn); VACUUM FULL viết lại cả bảng. [quan sát] History store của WiredTiger trong lab cũng dùng lại chỗ trống chứ không co ngay (30,44 MB trống, file 32,89 MB). Nghe giống nhau nhưng không tương đương: ở PostgreSQL, dòng chết nằm trong chính bảng (làm bảng phình) và VACUUM chạy riêng; ở WiredTiger version cũ nằm ở file riêng. Bài này không đo PostgreSQL, nên không kết luận bên nào bloat nhiều hơn hay nhanh hơn.

Những lỗi thường gặp

  • "MVCC nghĩa là không có lock và không có xung đột." Đọc không chặn ghi, nhưng hai lệnh ghi vào cùng một document vẫn xung đột, kể cả khi sửa hai field khác nhau (WriteConflict ở trường hợp 1 của lab ở phần trước (Update chain và history store)). Lock và ticket nằm ở bài Concurrency.
  • Quên commit hoặc abort transaction. Transaction còn mở giữ pinned timestamp ở lại phía sau: stable − pinned ở Lab 1 của phần trước (Ba lab về snapshot bị giữ) là 480 giây chỉ vì một transaction không ai đóng. Tìm chúng bằng $currentOp với idleSessions: true.
  • Tin history store là miễn phí. 300.000 update trên 100 document sinh 31 đến 33 MB history store khi giữ snapshot, 27 MB khi không giữ gì nhưng để cửa sổ mặc định, và 2,1 GB ở lab 200 KB: thêm lượng ghi cho reconciliation và thêm đĩa.
  • Nhầm oplog với nơi lưu các version cũ. Oplog để secondary phát lại, nằm trong collection có giới hạn kích thước, và không cho bạn đọc document ở thời điểm cũ. Version cũ sống trong update chain và history store, không có API công khai.
  • Cho rằng giữ snapshot lâu luôn làm app chậm. Ở quy mô lab, độ trễ và cache không khác giữa hold và control. Cái giá đo được là lượng ghi vào history store và dung lượng đĩa; đọc chậm thêm chỉ thấy khi 100.000 update dồn lên một hot document (Lab 2). Cache thật sự chật là chuyện của bài Cache, Eviction & Working Set.
  • Mong file WiredTigerHS.wt nhỏ lại ngay khi snapshot đóng. Phần trống dùng lại được sau vài phút, file co lại muộn hơn nhiều: đọc cả file bytes available for reuse.

Cột mốc: Bạn đã có thể so cách MVCC của WiredTiger và PostgreSQL cất version cũ, nói vì sao transaction quên commit giữ timestamp lại, và tìm chúng bằng $currentOp. Bài này khép lại tại đây.

Hỏi & đáp

Cùng bài toán nhiều transaction đọc và ghi một dòng/document, khác biệt chính giữa PostgreSQL và WiredTiger mà bài nêu là gì?

  1. PostgreSQL không có MVCC nên đọc phải chờ ghi xong, còn WiredTiger thì không

    PostgreSQL cũng dùng MVCC: mỗi câu lệnh thấy một snapshot, đọc không chặn ghi và ghi không chặn đọc. Khác biệt nằm ở chỗ cất version cũ. Xem mục "So với PostgreSQL".

  2. Cả hai đều chuyển version cũ sang một file riêng, chỉ khác tên gọi của tiến trình dọn dẹp chúng

    Ở PostgreSQL dòng chết nằm trong chính bảng (làm bảng phình) và VACUUM chạy riêng; ở WiredTiger version cũ nằm ở update chain rồi history store. Nghe giống nhau nhưng không tương đương. Xem mục "So với PostgreSQL".

  3. WiredTiger có tiến trình riêng tên vacuum chạy nền, còn PostgreSQL dọn khi checkpoint

    Ngược lại: PostgreSQL có VACUUM (autovacuum chạy nền), còn WiredTiger không có tiến trình riêng tên "vacuum"; version cũ bị xoá lúc reconciliation và checkpoint khi oldest timestamp tiến lên. Xem bảng ở mục "So với PostgreSQL".

  4. PostgreSQL để dòng cũ trong heap và VACUUM dọn; WiredTiger dùng update chain rồi history store

    Đúng. Đó là khác biệt về chỗ cất version cũ: UPDATE ở PostgreSQL ghi một dòng mới và dòng cũ ở lại trong bảng cho tới khi bị dọn, nên không có VACUUM thì dòng chết tích tụ thành bloat. Bài này không đo PostgreSQL nên không kết luận bên nào bloat nhiều hơn. Xem mục "So với PostgreSQL".

Ứng dụng quên commit hoặc abort một transaction snapshot, và stable − pinned ở lab giữ snapshot của phần trước là 480 giây chỉ vì thế. Cách tìm thủ phạm, và vì sao nó gây hậu quả đó?

  1. Đọc oplog, vì các version cũ mà transaction giữ lại được lưu ở đó

    Nhầm oplog với nơi lưu các version cũ. Oplog để secondary phát lại và có giới hạn kích thước; version cũ sống trong update chain và history store. Xem mục "Những lỗi thường gặp".

  2. Không cần tìm: transaction đã idle thì không còn giữ pinned timestamp, chỉ transaction đang chạy lệnh mới giữ

    Transaction còn mở, kể cả đang idle, vẫn giữ pinned timestamp ở lại phía sau; chính vì không ai đóng mà stable − pinned thành 480 giây. Xem mục "Những lỗi thường gặp".

  3. Dùng $currentOp với idleSessions: true: transaction còn mở giữ pinned timestamp ở read timestamp của nó

    Đúng. Pinned timestamp không tiến lên được qua read timestamp của transaction còn mở, nên history store phải giữ thêm version. $currentOp với idleSessions: true cho thấy transaction đó và read timestamp của nó. Xem mục "Những lỗi thường gặp".

  4. Chờ WiredTigerHS.wt tự nhỏ lại, vì file đó co ngay khi transaction bị bỏ quên

    Không có gì tự giải quyết: transaction còn mở thì pinned timestamp vẫn bị giữ, và dù sau khi đóng file cũng chỉ co lại muộn hơn nhiều (phần trống dùng lại được sau vài phút). Xem mục "Những lỗi thường gặp".

Nếu bỏ hết thuật ngữ: một thư viện luôn in ấn bản mới thay vì tẩy chữ trên bản cũ, và đóng dấu thời điểm in lên từng bản. Ai bước vào nhận bản mới nhất lúc đó và đọc bản đó đến hết, dù thư viện in thêm bao nhiêu. Chồng bản gần nhất để trên bàn, bản cũ hơn xuống tầng hầm. Thư viện chỉ vứt một ấn bản khi chắc chắn không ai còn cầm bản đó hoặc bản cũ hơn. Có một người ngồi lì cả tuần với ấn bản cũ thì cả chồng bản từ lúc đó phải ở lại, và nếu thư viện còn quy ước giữ mọi bản của năm phút gần nhất thì chồng đó đã dày sẵn kể cả khi chẳng ai ngồi lì. Hai người cùng sửa một cuốn thì người sửa sau bị từ chối và phải làm lại, dù hai người sửa hai trang khác nhau.

Bài tiếp theo

Bài này nói ai thấy gì và ai xung đột với ai ở tầng version. Nó chưa nói những gì chặn nhau thật sự: hai thread cùng chạm một page, hai lệnh cùng xin một lock, nhiều lệnh xếp hàng chờ được chạy.

Concurrency: Locks, Latches & Tickets trả lời: lock của MongoDB ở các tầng, latch của WiredTiger bên trong, ticket là gì và vì sao nó giới hạn số thao tác chạy đồng thời, và phân biệt rõ logical isolation, physical locking và concurrency control.

Tài liệu tham khảo

Chủ đề

Bạn thấy bài này thế nào?