Packfiles - Người hùng thầm lặng của Git
Git
55
hardcore
17
White

Cẩm Huỳnh viết ngày 06/10/2017

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 :see_no_evil:, 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@tacgia.com> 1506278351 +0200
committer Cẩm Huỳnh <email@tacgia.com> 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 :point_up:, 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


Bài viết được đăng lại từ blog của mình

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Cẩm Huỳnh

42 bài viết.
346 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
40 9
(Ảnh) Vì sao lại là Bật Đèn? Ai từng đọc qua Tắt Đèn hẳn đã biết tác phẩm được kết thúc bằng tình huống: Buông tay, chị vội choàng dậy, mở cửa...
Cẩm Huỳnh viết hơn 1 năm trước
40 9
White
38 6
Làm thế nào để chỉ với một đoạn text vài trăm ký tự, bạn có thể làm ngốn vài gigabyte bộ nhớ và từ chối dịch vụ của một hệ thống dùng XML? _____ ...
Cẩm Huỳnh viết 6 tháng trước
38 6
White
35 25
Vừa rồi mình vừa tiết kiệm được $5 mỗi tháng sau khi migrate cái (Link) từ Digital Ocean sang Heroku Free Dyno. (Ảnh) Kết quả thật mĩ mãn vì hầu ...
Cẩm Huỳnh viết 1 năm trước
35 25
Bài viết liên quan
White
9 0
Làm việc với git submodule (Ảnh) Đôi lúc ta cần phải sử dụng các repo khác như là một module của dự án hiện tại, nhưng ta lại muốn quản lý nó riê...
Tân Nguyễn viết 1 tháng trước
9 0
White
50 8
Tôi xin tổng hợp các cách dùng git stash tôi hay sử dụng Lưu lại thay đổi Git stash được sử dụng khi muốn lưu lại các thay đổi chưa commit, thườ...
BB viết hơn 3 năm trước
50 8
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
42 bài viết.
346 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!