Tăng tốc Git trên Repo khủng với Git Commit-Graph: Log, Rebase và Check nhánh mượt mà

Git tutorial - IT technology blog
Git tutorial - IT technology blog

1. Cực hình mang tên repo trăm nghìn commit

Khoảng nửa năm trước, team mình quyết định gộp gần 30 microservices về một monorepo. Mọi chuyện khởi đầu khá êm ả. Nhưng chỉ sau 8 tháng với 45 dev commit liên tục, repo nhanh chóng cán mốc 140.000 commit cùng hơn 300 branch hoạt động song song.

Lúc này, những thao tác cơ bản bắt đầu biến thành cơn ác mộng:

  • Gõ git log --graph --oneline -n 20 mất trắng 5 giây quạt máy rú rít chỉ để vẽ vài dòng nhánh.
  • Chạy git branch --contains <commit_id> kiểm tra code đã lên staging chưa? Đợi 12 đến 15 giây là chuyện thường tình.
  • Ức chế nhất là rebase. Git phải dò từng mối quan hệ tổ tiên (ancestry) giữa các nhánh sâu hoắm. Không ít lần cả máy dev lẫn runner CI nghẽn cứng vì CPU chạm trần 100%.

Cảm giác chờ đợi một công cụ CLI phản hồi lâu hơn cả mở trình duyệt thực sự khiến nhịp làm việc bị đứt gãy liên tục.

2. Vì sao Git đuối sức khi đồ thị phình to?

Git tổ chức dữ liệu theo mô hình đồ thị có hướng không chu trình (DAG). Mỗi commit trỏ ngược về một hoặc nhiều commit cha.

Với dự án nhỏ, mô hình này chạy cực nhanh. Nhưng khi repo chạm ngưỡng hàng trăm nghìn nút, hai điểm nghẽn nghiêm trọng sẽ xuất hiện:

  • Đọc đĩa dồn dập (Disk I/O): Khi chạy git log hay git merge-base, Git phải bóc tách từng commit object từ packfile trên ổ cứng. Đáng nói là Git chỉ cần lấy đúng hai mẩu dữ liệu: hash commit cha và timestamp. Việc giải nén hàng vạn object chỉ để lấy thông tin này gây lãng phí tài nguyên khủng khiếp.
  • Duyệt đồ thị đắt đỏ: Muốn biết commit A có thuộc nhánh commit B hay không, Git phải đi lùi từ B về quá khứ qua hàng nghìn tầng. Thiếu chỉ mục dựng sẵn, các thuật toán BFS/DFS buộc phải duyệt mù toàn bộ cây commit, ngốn sạch I/O lẫn chu kỳ CPU.

3. Những giải pháp cứu cháy và điểm giới hạn

Trước khi tìm ra chìa khóa thực sự, team mình từng thử qua vài cách quen thuộc nhưng đều vướng điểm nghẽn riêng:

Cách 1: Chạy dọn rác git gc

git gc --prune=now --aggressive

Lệnh này nén các loose objects thành packfile gọn gàng hơn. Dung lượng thư mục .git có giảm, nhưng Git vẫn phải mổ từng header commit để duyệt cây phả hệ. Tốc độ các lệnh duyệt sâu gần như không cải thiện.

Cách 2: Dùng Shallow clone

git clone --depth 50 https://github.com/org/repo.git

Giải pháp này ổn cho CI runner vì chỉ kéo về 50 commit gần nhất. Tuy nhiên trên máy dev, shallow clone tạo ra hàng tá phiền toái: không git blame được code cũ, không rebase xa được và rất dễ ném lỗi lạ lúc push.

Cách 3: Tách nhỏ repo

Đập monorepo ra lại thành repo nhỏ nghe thì bài bản. Dẫu vậy, chi phí tái cấu trúc kéo dài nhiều tháng, kèm theo rủi ro xáo trộn toàn bộ pipeline CI/CD lẫn quy trình phối hợp giữa các team.

4. Đòn bẩy thực sự: Kích hoạt Git Commit-Graph

Từ bản 2.18 (và đặc biệt hoàn thiện ở 2.24+), Git giới thiệu Commit-Graph. Đây là một file nhị phân duy nhất đặt tại .git/objects/info/commit-graph.

File này lưu trước toàn bộ xương sống đồ thị: danh sách hash, quan hệ cha-con, ngày tạo và chỉ số thế hệ (generation number). Nhờ đó, Git không cần giải nén từng object nữa. Thay vào đó, nó tra thẳng vào bảng mục lục nhị phân gọn nhẹ.

Bước 1: Bật cờ cấu hình

Đầu tiên, kích hoạt tính năng đọc và ghi commit-graph trên máy:

git config --global core.commitGraph true
git config --global gc.writeCommitGraph true

Bước 2: Dựng file commit-graph

Mở terminal tại thư mục repo và chạy lệnh:

git commit-graph write --reachable --changed-paths

Cờ --reachable gom toàn bộ commit mà các nhánh và tag đang trỏ tới. Đặc biệt, tham số --changed-paths (hỗ trợ từ Git 2.28) tích hợp thêm Bloom filter cho danh sách file thay đổi. Nhờ vậy, lệnh lọc lịch sử file như git log -- path/to/file.go chạy nhanh hơn gấp 4 đến 8 lần.

Bước 3: Đo kiểm khác biệt

Hãy thử đo lại lệnh kiểm tra nhánh từng khiến bạn bực bội:

time git branch --contains HEAD~5000

Trên repo thực tế 140.000 commit của team mình, thời gian thực thi giảm ngoạn mục từ 14.2 giây xuống vỏn vẹn 0.16 giây. Cây git log --graph bung ra tức thì, không còn hiện tượng khựng giật.

Bước 4: Tự động cập nhật với git maintenance

Gõ lệnh cập nhật thủ công mỗi ngày rõ ràng không khả thi. Từ Git 2.30, bạn có thể bàn giao hoàn toàn việc này cho tiến trình ngầm:

# Đưa repo vào danh sách bảo trì định kỳ
git maintenance register

# Kích hoạt background service
git maintenance start

Git sẽ tự cài cronjob (hoặc systemd timer trên Linux, Task Scheduler trên Windows). Cứ mỗi giờ, nó tự tạo thêm các lát cắt commit-graph phân tầng (split commit-graph). Thao tác này diễn ra êm ru dưới nền, giúp repo luôn duy trì tốc độ tối đa mà bạn chẳng cần bận tâm nữa.

Share: