Quần Cam Blog

Packfile - Người hùng thầm lặng của Git

#git #hardcore

Bài viết nằm trong nhóm nghiên cứu #hardcore của Ruby Vietnam.


Trước khi tìm hiểu về Packfile ta hãy xem qua cấu trúc dữ liệu của Git.

Cấu trúc dữ liệu của Git

Như ta đã biết 🤔, Git là một key-value store, với các object có key SHA-1.

Objects

Git có 3 loại objects chính là blob, tree và commit, mỗi loại object có một công dụng khác nhau.

Ta hãy lướt qua ví dụ dưới đây để tìm hiểu xem các objects này làm gì.

Giả sử ta có một Git project có cấu trúc thư mục như sau:

tree .

.
├── a.txt
└── lib
    └── b.txt

Git lưu trữ toàn bộ dữ liệu trong thư mục .git, các Git objects sẽ được lưu tại .git/objects/ dưới dạng binary.

tree .git/objects/

.git/objects
├── 25
│   └── f6d58e2f8207f01e50baf137c1d0a48f78875c
├── b0
│   └── b8a639f4b1f311771acd73fce1a9fec5cd9b47
├── b4
│   └── 9df1d24f68bfe416a3ce02309ff7782d3622a7
├── ba
│   └── 38b7dbf40c2fd2c6ed61dcf2ef61672632d42d
├── c5
│   └── 0ded571243897825481cc7301c3639773ac58f
├── cd
│   └── b89d86c483141673811399b7cc20419283623d
├── f7
│   └── d34ac248f8144e473958a3f4d7aea6c7274c92
├── info
└── pack

Project này đã có một commit là 25f6d58e2f8207f01e50baf137c1d0a48f78875c, và như đã nói ở trên, commit trong Git là một object. Để xem nội dụng của một object ta có thể dùng lệnh git cat-file -p [SHA-1].

git cat-file -p 25f6d58e2f8207f01e50baf137c1d0a48f78875c

tree ba38b7dbf40c2fd2c6ed61dcf2ef61672632d42d
parent cdb89d86c483141673811399b7cc20419283623d
author Cẩm Huỳnh <[email protected]> 1506278351 +0200
committer Cẩm Huỳnh <[email protected]> 1506278351 +0200

add b

Nội dung của object cho thấy commit trỏ đến một tree object có SHA-1 là ba38b7dbf40c2fd2c6ed61dcf2ef61672632d42d, có parent commit (commit trước đó) là cdb89d86c483141673811399b7cc20419283623d, thông tin tác giả và người commit, và cuối cùng là message của commit.

Ta sẽ tiếp tục xem nội dung của tree object.

git cat-file -p ba38b7dbf40c2fd2c6ed61dcf2ef61672632d42d

100644 blob b0b8a639f4b1f311771acd73fce1a9fec5cd9b47    a.txt
040000 tree f7d34ac248f8144e473958a3f4d7aea6c7274c92    lib

Ta thấy tree object này chứa một file a.txt là một blob (file) và lib là một tree (thư mục). Tiếp tục truy vào xem nội dung của blob trên ta thấy object này chứa toàn bộ nội dung của file a.txt.

git cat-file -p b0b8a639f4b1f311771acd73fce1a9fec5cd9b47

Hello world from a

Dựng lại commit object của chúng ta dưới dạng cây, nó sẽ có hình thù như sau.

packfile-objects-tree

Với ví dụ trên, ta rút ra công dụng của 3 loại objects lần lượt là:

  1. blob: lưu trữ nội dung của file.

  2. tree: lưu trữ cây thư mục.

  3. commit: lưu trữ các thông tin commit như tác giả, ngày giờ commit, và tree mà nó trỏ tới.

Git version project của bạn như thế nào.

Với cấu trúc này Git có thể lưu toàn bộ version trong project của bạn một cách dễ dàng.

Giả sử ta sửa nội dung file a.txt thành Hello from the other side, khi tạo commit các thao tác của Git lần lượt là:

  1. Tạo ra một blob object có nội dụng là Hello from the other side.

  2. Tạo ra một tree object chứa blob object được tạo ra cho a.txt và giữ nguyên thông tin của tree object cho lib (vì nó không đổi).

  3. Tạo ra một commit object trỏ đến tree được tạo ra.

Packfile

Cấu trúc dữ liệu trên tuy giải quyết vấn đề lưu trữ của Git, nhưng đời luôn cho ta những vấn đề khác.

Giả sử file a.txt hiện đang có 200,000 dòng, giờ ta cần thêm 1 dòng có nội dung Hello, can you hear me? vào cuối file.

Theo như cách làm đã được mô tả ở trên, Git sẽ tạo ra một blob object mới gồm 200,001 dòng, và tạo tree và commit như thông thường. Thành thử ra chỉ với việc thêm 1 dòng, Git đã phải lưu trữ thêm 200,001 dòng dữ liệu. Như vậy thì không được hay lắm cho bộ nhớ.

Và packfile được sinh ra để giải cứu nhân loại.

Packfile là gì?

Packfile sẽ được sinh ra mỗi khi bạn git push lên remote hoặc gom ve chai với git gc trên local machine.

Công dụng của Packfile:

  • Giải quyết vấn đề nêu ra ở trên 👆, tối ưu hoá lưu trữ.
  • Giúp ta đồng bộ hoá dữ liệu với server.

Tối ưu hoá lưu trữ

Như đã nói Packfile được sinh ra khi chạy git gc. Vậy ta hãy thử chạy nó cho project.

git gc
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (10/10), done.
Total 10 (delta 0), reused 0 (delta 0)

tree .git/objects/
.git/objects
├── info
│   └── packs
└── pack
    ├── pack-362927142ab7ba8157829e7f044768c0f9beb047.idx
    └── pack-362927142ab7ba8157829e7f044768c0f9beb047.pack

Ồ, sau khi git gc thì Bố ơi, objects đi đâu thế?

Thật ra thì objects không biến đi đâu cả mà đã được gom vào packfile trong .git/objects/packs, bao gồm file .idx.pack.

Ta hãy xem packfile đó chứa thông tin gì?

git verify-pack -v .git/objects/pack/pack-362927142ab7ba8157829e7f044768c0f9beb047.idx

      662f8069da4f0b2c275658cc8e0f106fb1fe4a53 commit 233 164 12
      eeea7aa059a347b0a65ae80921361b25712f8d75 commit 189 138 176
[S]   a43ed97bc49bbb5d0dea412cfba84b1bb2df8021 blob   405 53 314
      b49df1d24f68bfe416a3ce02309ff7782d3622a7 blob   19 29 367
      035821c97042669f86ec82335e997b2f31c008ea tree   63 74 396
      f7d34ac248f8144e473958a3f4d7aea6c7274c92 tree   33 44 470
      6be958cca4e99fd7656d09d9e7e0f259069ba3d2 tree   63 73 514
[T]   d160120d63700bf12e03a2d7222e9d442a83d7ff blob   7 18 587 1 a43ed97bc49bbb5d0dea412cfba84b1bb2df8021
      non delta: 7 objects
      chain length = 1: 1 object
      .git/objects/pack/pack-3f60df1e6f910c5ecd9e0693df0bdffb758a3e62.pack: ok

Hãy xem 2 blob được đánh dấu [T][S], đây là version trước và sau khi thêm vào dòng mới của file a.txt.

Thông tin cột thứ 3 cho biết rằng [S] có kích thước file là 405 bytes và [T] là 7 bytes, đồng thời cột cuối của [T] trỏ đến SHA-1 của [S]. Có nghĩa blob object [T] chỉ lưu delta/diff (sự thay đổi), còn [S] sẽ lưu toàn bộ nội dung file.

Vì sao Git lưu delta ở blob [T] chứ không phải [S]? Lý do là để tối ưu hoá tốc độ truy cập, vì thường version sau cùng sẽ được truy cập nhiều nhất.

Tham khảo

Các thông tin trong bài viết này chủ yếu được tham khảo ở Git book V2 - chương Git Internals. Chương này cung cấp các kiến thức cơ bản trước khi bạn vào đọc source code của Git.

Mình cũng có vẽ một mind map mà các bạn có thể sử dụng khi đọc sách.

Git internal mind map