23/10/2026 · VietTechies · Biên tập

Tháng 4 năm 2005, Matt Mackall công bố Mercurial phiên bản 0.1, một nguyên mẫu hệ thống quản lý phiên bản viết bằng chưa đến 600 dòng Python. Công cụ này đã tự quản lý mã nguồn của mình; Mackall muốn Mercurial lưu và truy xuất hàng nghìn phiên bản tệp mà không bị chậm vì truy cập đĩa, và thiết kế lưu trữ của ông làm được điều đó bằng cách kết hợp các delta gọn nhẹ với những bản chụp đầy đủ lưu xen kẽ.
Trong thông báo gửi danh sách thư của cộng đồng phát triển nhân Linux, Mackall lưu ý rằng định dạng kho mã vẫn đang thay đổi và Mercurial chưa xử lý được thao tác đổi tên tệp.
Các hệ thống quản lý phiên bản đời đầu như Source Code Control System (SCCS, hệ thống quản lý mã nguồn) và Revision Control System (RCS, hệ thống quản lý các bản sửa đổi) đã đặt nền móng cho cách lưu phần khác biệt thay vì toàn bộ bản sao. Lưu nguyên một bản sao của tệp mã nguồn lớn sau mỗi commit sẽ nhanh chóng làm đầy đĩa. Nhưng chỉ nén bằng cách lưu phần khác biệt, gọi là delta, cũng phải trả giá đắt. Với một chuỗi delta tổ chức theo cách đơn giản, muốn dựng lại phiên bản thứ 1.000, hệ thống phải đọc phiên bản gốc làm cơ sở rồi lần lượt áp dụng 999 bản vá. Lịch sử càng dài, việc truy xuất tệp cũ càng tốn công di chuyển đầu đọc đĩa và xử lý.
Revlog: Tách chỉ mục khỏi dữ liệu
Để giải quyết nút thắt đó, Mackall thiết kế revlog, một cấu trúc lưu trữ thống nhất mà Mercurial dùng cho lịch sử tệp, bản kê thư mục và siêu dữ liệu của commit. Revlog tách siêu dữ liệu khỏi nội dung tệp bằng cách duy trì hai tệp trên đĩa: tệp chỉ mục có đuôi .i và tệp dữ liệu có đuôi .d. Với tệp nhỏ có lịch sử ngắn, Mercurial gộp cả hai vào một tệp chỉ mục để tránh những thao tác đĩa không cần thiết.
Chỉ mục gồm các bản ghi có độ dài cố định 64 byte, chứa vị trí dữ liệu trong tệp, độ dài sau nén, kích thước trước nén, con trỏ tới phiên bản cơ sở, mã định danh phiên bản cha và giá trị băm mật mã. Vì mọi bản ghi trong chỉ mục đều chiếm đúng số byte như nhau, muốn xác định vị trí của một phiên bản bất kỳ, hệ thống chỉ cần nhân kích thước bản ghi với số thứ tự phiên bản. Cấu trúc cố định này cho phép truy cập siêu dữ liệu phiên bản trong thời gian hằng số, tức là thời gian không tăng theo số phiên bản. Nhờ đó, Mercurial tìm được vị trí của một phiên bản tệp trên đĩa mà không phải quét qua hàng megabyte mã nguồn.
Khi nào nên dùng thêm dung lượng đĩa?
Quyết định kỹ thuật then chốt trong revlog là khi nào lưu delta, khi nào lưu bản sao đầy đủ. Thay vì lưu bản chụp theo khoảng cách tùy ý, chẳng hạn cứ mỗi một trăm commit, Mercurial đo tổng số byte của các delta đã lưu kể từ bản chụp cơ sở gần nhất. Khi tổng dung lượng delta tích lũy vượt một ngưỡng so với độ dài chưa nén của phiên bản đang xét, Mercurial ngừng ghi thêm delta và ghi một bản chụp đầy đủ mới của tệp, có nén.
Quy tắc ngưỡng này giới hạn lượng dữ liệu delta tích lũy theo kích thước phiên bản. Muốn dựng lại một phiên bản, hệ thống cần bản chụp cơ sở cùng các delta từ bản chụp đó đến phiên bản cần dựng; ngưỡng tính theo byte không đặt ra số lượng delta tối đa cố định. Ngoài ra, Mercurial còn kiểm tra xem nén có thực sự giảm dung lượng lưu trữ hay không. Nếu tệp vốn đã được nén, chẳng hạn ảnh hoặc tệp nén lưu trữ, nén lại có thể khiến dữ liệu lớn hơn. Khi nén không tiết kiệm dung lượng, Mercurial lưu bản chụp hoặc delta ở dạng không nén.
Thứ tự ghi và khả năng đọc đồng thời không cần khóa
Mercurial xây dựng toàn bộ mô hình kho mã trên cấu trúc revlog nền tảng này. Các phiên bản của từng tệp được lưu trong nhật ký riêng, gọi là filelog. Ở tầng trên, các bản kê thư mục theo dõi những phiên bản tệp thuộc về mỗi commit. Tầng cao nhất là changelog, nhật ký ghi lại thông điệp commit, dấu thời gian và con trỏ tới phiên bản bản kê tương ứng. Dữ liệu luôn được ghi từ dưới lên: Mercurial ghi thêm phiên bản mới vào filelog trước, tiếp đến ghi bản kê, rồi cuối cùng mới cập nhật changelog.
Vì mỗi tầng trên đều trỏ xuống dữ liệu đã ghi xong, mọi tiến trình đọc changelog đều được bảo đảm rằng dữ liệu mà changelog tham chiếu tới là hợp lệ và đầy đủ. Tiến trình đọc không bao giờ thấy commit mới ghi được một phần, và không cần giữ khóa đọc kho mã, ngay cả khi một giao dịch commit đang ghi dữ liệu mới. Một khóa tiến trình đơn giản ngăn các tiến trình ghi đồng thời, giúp thao tác đọc diễn ra nhanh và không bị chặn.
Cái giá của lịch sử tổ chức theo từng tệp
Tối ưu việc lưu phiên bản theo từng tệp kéo theo một đánh đổi về kiến trúc. Trong bài phân tích kiến trúc năm 2011, Dirkjan Ochtman cho rằng thao tác đổi tên kém hiệu quả là do các phiên bản được lưu riêng cho từng tệp. Dù filelog vốn đã dùng các phần siêu dữ liệu tùy chọn ở đầu bản ghi để ghi nhận thao tác sao chép và đổi tên, ông vẫn dự đoán rằng muốn cải thiện hiệu quả đổi tên, hệ thống sẽ phải vượt qua ranh giới giữa các tầng kiến trúc.
Xây dựng một hệ thống quản lý phiên bản đồng nghĩa với việc chọn tài nguyên nào để đánh đổi. Nhờ dành thêm dung lượng để thỉnh thoảng lưu bản chụp, Mercurial bảo đảm khối lượng xử lý cần để dựng lại phiên bản tệp cũ được giới hạn theo kích thước dữ liệu, thay vì tăng theo độ dài lịch sử commit.
Bài liên quan
Đọc bằng ngôn ngữ khác: English
Nguồn
- Danh sách thư nhân Linux: Mercurial v0.1 – a minimal scalable distributed SCM (Matt Mackall, tháng 4 năm 2005)
- The Architecture of Open Source Applications, Volume 1: Mercurial (Dirkjan Ochtman, 2011)
- Mercurial: The Definitive Guide: Behind the scenes (Bryan O’Sullivan)
- Mercurial: The Definitive Guide: How did we get here? (Bryan O’Sullivan)
- Danh sách thư nhân Linux: Thảo luận đo hiệu năng Mercurial 0.4b so với git patchbomb (Matt Mackall, tháng 4 năm 2005)
- Ảnh đại diện: Mk2010; Wikimedia Commons; CC BY 4.0 (đã chỉnh sửa từ ảnh gốc)
