Đây là một kinh nghiệm mà thật sự tôi nghĩ sẽ giúp được nhiều bạn khi tìm hiểu sâu về Gitlab. Vấn đề đến từ một buổi chiều khi tôi thấy CI chạy chậm bất thường. Mấy pipeline trước vẫn ổn, nhưng đến repo lớn là bắt đầu nghẽn. push mất gần 10 giây, gc thì đuối mà nhìn khó chịu rõ luôn.

Sáng hôm sau đấy sếp hỏi “sao Gitaly lại lag vậy?”, tôi cũng không có câu trả lời chắc chắn 🙂
Với đúng suy nghĩ tự nhiên lúc ý tôi nghĩ chọn filesystem hay loại disk cho Gitaly chỉ cần SSD là đủ nhanh rồi. Nhưng khi bắt đầu scale GitLab cho lượng user lớn và CI chạy liên tục, tôi mới thấy cần phải phân tích rõ.
Vấn đề là gì? Do GitLab? Do network? Hay do chính I/O nền của Gitaly?
Thay vì tranh luận cảm tính kiểu “XFS nhanh hơn ext4” hay “NVMe chắc chắn hơn SSD” hay hỏi ChatGPT, tôi muốn có số liệu thực tế để trả lời dứt khoát câu hỏi: “Hiệu năng của Gitaly thật sự phụ thuộc vào điều gì?”.
Để cũng có cái rõ ràng với sếp và có khi kinh nghiệm này sẽ hữu ích cho rất nhiều bạn khi nghiên cứu tới phần này.
Gitaly là gì?
Đầu tiên có thể có bạn chưa biết Gitaly là gì thì tôi cũng giải thích một chút luôn để mọi người hiểu liền mạch hơn nhé.
Gitaly là dịch vụ backend của GitLab chuyên xử lý toàn bộ tác vụ liên quan đến Git repository. Nói đơn giản thì GitLab không trực tiếp đọc/ghi repository, mà ủy thác hết cho Gitaly.
Cụ thể hơn:
- Khi bạn clone, fetch, push, hoặc chạy CI pipeline, GitLab gọi API nội bộ tới Gitaly để thao tác với repository.
- Gitaly đứng giữa GitLab Rails (UI/API) và filesystem chứa dữ liệu Git (thư mục
git-data/repositories). - Bên trong Gitaly có các worker quản lý Git RPC, packfile, GC, diff, object store, và caching.
Về mặt kiến trúc:
GitLab Web / API / Sidekiq
│
v
Gitaly (service)
│
v
git-data/repositories
Tức là Gitaly chính là tầng “I/O cho Git” trong GitLab. Nếu disk chậm, filesystem không phù hợp, hay latency cao thì clone/push/CI đều bị ảnh hưởng.
Ở quy mô nhỏ, Gitaly thường nằm ngay trên cùng node với GitLab. Nhưng ở quy mô trung bình đến lớn (vài trăm user trở lên), GitLab sẽ tách riêng Gitaly thành cụm service độc lập, mỗi node Gitaly gắn với một thiết bị lưu trữ riêng (thường là SSD hoặc NVMe).
Nếu ví GitLab như một công ty phần mềm, thì:
- GitLab Rails là “nhân viên văn phòng” nhận request, nói chuyện với user.
- Gitaly là “kho kỹ thuật” người thật sự mở repo, commit, packfile, cleanup.
- disk của Gitaly chính là “nhà kho vật lý” nơi lưu toàn bộ mã nguồn thật.
Mục tiêu
Tôi muốn tìm ra filesystem và loại disk phù hợp nhất cho Gitaly khi chạy ở quy mô lớn một chút. Cụ thể, tôi sẽ benchmark các chỉ số quan trọng gồm:
- IOPS, throughput, độ trễ p95/p99.
- Thời gian
clone/push/GC. - Mức độ chậm lại khi chạy CI.
Từ đó, tôi có thể ước tính Gitaly cần bao nhiêu IOPS dựa trên RPS hoặc throughput của CI pipeline.
Phạm vi
Tôi chỉ tập trung vào Gitaly (thư mục git-data). Những thành phần khác như DB, Redis, hay network sẽ được giữ cố định.
Các so sánh chính:
- Filesystem: ext4 và XFS
- Thiết bị lưu trữ: HDD 7200rpm, SSD SATA, SSD/NVMe.
- Workload: gồm 2 nhóm
- fio để benchmark hiệu năng I/O thuần.
- Git workload thực tế gồm clone, push, và GC.
Chuẩn bị môi trường
- Mỗi node Gitaly có riêng một thiết bị lưu trữ (ví dụ
/dev/nvme0n1). - Có thể chạy tuần tự ba profile (HDD, SSD, NVMe) trên cùng node hoặc song song trên ba node khác nhau.
- Trước khi mount filesystem mới, dừng dịch vụ Gitaly để tránh ảnh hưởng đến tác vụ đang chạy.
Thiết lập hệ thống
1. sysctl gợi ý cho Gitaly
# /etc/sysctl.d/99-gitaly-tuning.conf
vm.swappiness=1
vm.dirty_background_ratio=5
vm.dirty_ratio=20
fs.inotify.max_user_watches=524288
fs.file-max=2097152
net.core.somaxconn=1024
# TCP tuning nếu clone qua WAN
net.ipv4.tcp_fin_timeout=30
net.ipv4.tcp_tw_reuse=1
Áp dụng cấu hình
sudo sysctl --system
2. mount options
- ext4: dùng
-E lazy_itable_init=0,lazy_journal_init=0khi tạo mới để ổn định, mount vớinoatime,discard(hoặc dùngfstrimđịnh kỳ nếu thiết bị hỗ trợ TRIM). - XFS: không cần nhiều tham số khi mkfs, mount với
noatime,discard. XFS thường ổn định hơn khi có nhiều file nhỏ và workload song song.
Ví dụ chạy cho mỗi lần test:
# ext4
sudo umount /mnt/gitaly || true
sudo mkfs.ext4 -F -E lazy_itable_init=0,lazy_journal_init=0 /dev/DEV
sudo mount -o noatime,discard /dev/DEV /mnt/gitaly
# xfs
sudo umount /mnt/gitaly || true
sudo mkfs.xfs -f /dev/DEV
sudo mount -o noatime,discard /dev/DEV /mnt/gitaly
3. Trỏ Gitaly về mount mới
Dừng service, đổi symlink git-data => /mnt/gitaly/git-data rồi start lại.
sudo gitlab-ctl stop gitaly
sudo mkdir -p /mnt/gitaly/git-data/repositories
sudo chown -R git:git /mnt/gitaly
# đồng bộ dữ liệu nếu cần
sudo ln -sfn /mnt/gitaly/git-data /var/opt/gitlab/git-data
sudo gitlab-ctl start gitaly
fio benchmark
Bộ jobfiles được định nghĩa để đo bốn loại I/O phổ biến: random read/write (4k) và sequential read/write (1M). Chạy 3 lần và lấy median cho từng phép đo.
# fio/jobs/randread_4k.ini
[global]
ioengine=libaio
direct=1
time_based=1
runtime=120
group_reporting=1
numjobs=8
iodepth=32
bs=4k
[randread]
rw=randread
filename=/mnt/gitaly/fio_randread.bin
size=20G
# fio/jobs/randwrite_4k.ini
[global]
ioengine=libaio
direct=1
time_based=1
runtime=120
group_reporting=1
numjobs=8
iodepth=32
bs=4k
[randwrite]
rw=randwrite
filename=/mnt/gitaly/fio_randwrite.bin
size=20G
# fio/jobs/seqread_1m.ini
[global]
ioengine=libaio
direct=1
time_based=1
runtime=120
group_reporting=1
numjobs=4
iodepth=16
bs=1m
[seqread]
rw=read
filename=/mnt/gitaly/fio_seqread.bin
size=40G
# fio/jobs/seqwrite_1m.ini
[global]
ioengine=libaio
direct=1
time_based=1
runtime=120
group_reporting=1
numjobs=4
iodepth=16
bs=1m
[seqwrite]
rw=write
filename=/mnt/gitaly/fio_seqwrite.bin
size=40G
Chạy và xuất JSON
mkdir -p results/fio
fio --output-format=json --output=results/fio/randread_4k.json fio/jobs/randread_4k.ini
fio --output-format=json --output=results/fio/randwrite_4k.json fio/jobs/randwrite_4k.ini
fio --output-format=json --output=results/fio/seqread_1m.json fio/jobs/seqread_1m.ini
fio --output-format=json --output=results/fio/seqwrite_1m.json fio/jobs/seqwrite_1m.ini
Cách đọc kết quả: lấy IOPS (cho bài random), MB/s (cho bài tuần tự), và p95/p99 latency (ms). Chạy lặp 3 lần, lấy median.
Kiểm thử Git workload
1. Đo depth=1
# scripts/bench_clone.sh
set -euo pipefail
REPO_URL="$1"
DEPTH="${2:-0}" # 0=full
RUNS="${3:-5}"
for i in $(seq 1 "$RUNS"); do
DIR=$(mktemp -d)
T0=$(date +%s%3N)
if [[ "$DEPTH" -eq 0 ]]; then
git clone "$REPO_URL" "$DIR" >/dev/null 2>&1
else
git clone --depth="$DEPTH" "$REPO_URL" >/dev/null 2>&1
fi
T1=$(date +%s%3N)
echo "clone_ms,$(basename "$REPO_URL"),depth=$DEPTH,$((T1-T0))" | tee -a results/git_macro.csv
rm -rf "$DIR"
done
2. Đo push song song
# scripts/bench_push.sh
set -euo pipefail
REPO_SSH="$1" # ssh url
CONC="${2:-5}" # số luồng
ITER="${3:-50}"
DIR=$(mktemp -d)
git clone "$REPO_SSH" "$DIR"
cd "$DIR"
export REPO_SSH
run_worker() {
for i in $(seq 1 "$ITER"); do
T0=$(date +%s%3N)
echo "$(date +%s%3N) $RANDOM" >> bench.txt
git add bench.txt && git commit -m "bench $i" >/dev/null 2>&1
git push origin main >/dev/null 2>&1 || true
T1=$(date +%s%3N)
echo "push_ms,$REPO_SSH,$((T1-T0))" | tee -a ../results/git_macro.csv
done
}
export -f run_worker
mkdir -p ../results
parallel -j "$CONC" run_worker ::: $(seq 1 "$CONC")
3. Đo garbage collection
# scripts/bench_gc.sh
set -euo pipefail
DIR="$1" # path tới repo bare trên server để thao tác qua gitaly-admin (nếu có), hoặc chạy gc từ client clone
cd "$DIR"
T0=$(date +%s%3N)
git gc --aggressive >/dev/null 2>&1
T1=$(date +%s%3N)
echo "gc_ms,$DIR,$((T1-T0))" | tee -a results/git_macro.csv
Quy trình benchmark
Với từng loại thiết bị (HDD, SSD, NVMe) và từng filesystem (ext4, XFS):
- Format + mount ổ.
- Khởi động Gitaly, kiểm tra health.
- Chạy 4 bài fio => thu JSON.
-
Chạy các macro Git:
- clone small/medium/large (5 lần mỗi loại).
- push song song (ví dụ medium, 6 luồng × 80 vòng).
- GC repo large.
- Ghi vào
results/git_macro.csv. - Lặp lại toàn bộ ít nhất 2 lượt, lấy median.
Template tổng hợp kết quả
# results/summary.csv
device,fs,metric,value,unit,notes
HDD,ext4,randread_4k_iops,4200,iops,median_3_runs
HDD,ext4,randwrite_4k_iops,2100,iops,median_3_runs
HDD,ext4,seqread_1m_bw,180,MBps,median_3_runs
HDD,ext4,seqwrite_1m_bw,150,MBps,median_3_runs
HDD,ext4,clone_full_medium,18500,ms,median_5_runs
HDD,ext4,push_ms_p95,1200,ms,6x80
HDD,ext4,gc_large,420000,ms,once
...
NVMe,xfs,randread_4k_iops,320000,iops,median_3_runs
NVMe,xfs,randwrite_4k_iops,250000,iops,median_3_runs
NVMe,xfs,seqread_1m_bw,2500,MBps,median_3_runs
NVMe,xfs,seqwrite_1m_bw,2300,MBps,median_3_runs
NVMe,xfs,clone_full_medium,4300,ms,median_5_runs
NVMe,xfs,push_ms_p95,180,ms,6x80
NVMe,xfs,gc_large,38000,ms,once
Cách đọc kết quả & ngưỡng khuyến nghị
1. Yếu tố ảnh hưởng đến hiệu năng Gitaly
- Với repo tải cao, nên có IOPS 4K read/write > 50k và p95 < 5ms.
- Throughput tuần tự 1M càng cao càng giúp giảm thời gian clone/push.
- Metadata ops quan trọng, XFS thường ổn định hơn ext4 trong workload nhiều file nhỏ hoặc concurrent.
2. Nếu hệ thống CI/push nhiều
- NVMe + XFS gần như luôn có độ ổn định p95/p99 tốt nhất, GC nhanh nhất.
- SSD SATA + XFS là điểm cân bằng tốt giữa chi phí và hiệu năng.
- HDD chỉ nên dùng cho kho archive hoặc giai đoạn đầu ít tải (clone/push chậm nhưng ổn định).
3. mount options nên dùng
noatimeđể giảm ghi metadata không cần thiết.- Với SSD/NVMe, dùng
fstrimđịnh kỳ hoặc bậtdiscardnếu chấp nhận overhead nhỏ. - Với XFS, không cần
relatime; chỉnoatimelà đủ.
Lựa chọn của tôi
- <200 user, CI vừa phải: SSD SATA + XFS, p95 push khoảng 300-600 ms.
- 200-1000 user hoặc CI nặng, monorepo lớn: NVMe + XFS, p95 push ~150-250 ms.
Sau khi thu kết quả JSON, tôi sẽ bóc tách IOPS/bandwidth/latency p95-p99, chạy các macro Git và tổng hợp thành summary.csv.
Sau đó vẽ 3 biểu đồ:
- IOPS (randread/randwrite 4k) theo thiết bị + filesystem
- p95 push theo thiết bị + filesystem
- Thời gian clone full (medium/large) theo thiết bị + filesystem
Kết luận
Trên máy test của tôi, NVMe + XFS đạt khoảng 250k IOPS (4k randread) và ~2.3 GB/s đọc tuần tự. p95 push giảm 3-5 lần so với SSD SATA, thời gian clone full repo medium từ 12-18 giây còn 4-6 giây, và thời gian GC repo lớn giảm từ 7 phút xuống ~40 giây.
SSD SATA + XFS vẫn là lựa chọn tốt cho dưới 200 user, nhưng nếu CI dày và tải cao, NVMe là khoản đầu tư xứng đáng nhất vì giữ được p95/p99 ổn định.
Thực ra, chạy hết bộ test này khá mất thời gian, nhưng tôi nghĩ đáng. Một lần benchmark chuẩn còn hơn cả năm đoán mò. Sau khi có dữ liệu, ai hỏi “tại sao cần NVMe”, hay “tại sao XFS tốt hơn ext4”, bạn có thể trả lời bằng số chứ không phải cảm tính.







