Skip to content

Commit 0408b18

Browse files
committed
rn-132: add pack-refs article
1 parent 6a1f178 commit 0408b18

1 file changed

Lines changed: 77 additions & 2 deletions

File tree

rev_news/drafts/edition-132.md

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,84 @@ This edition covers what happened during the months of January and February 2026
2525
### Reviews
2626
-->
2727

28-
<!---
28+
2929
### Support
30-
-->
30+
31+
* [Slow git pack-refs --all](https://www.google.com/search?q=https://lore.kernel.org/git/CH3PR12MB9026B5872FD42F031970074BC2B3A%40CH3PR12MB9026.namprd12.prod.outlook.com)
32+
33+
Martin Fick started the discussion by reporting a significant
34+
performance issue where `git pack-refs --all` was taking over five
35+
minutes to complete on a large repository (~3M refs) hosted on an
36+
NFS filesystem. This delay was particularly problematic for Gerrit
37+
servers because Git holds the `packed-refs.lock` for nearly the
38+
entire duration, blocking other reference updates. Martin noted that
39+
JGit was able to perform the same operation in under 20 seconds on
40+
the same repository, suggesting the bottleneck was specific to the
41+
Git implementation.
42+
43+
The `packed-refs` file is used by Git to store a large number of
44+
references in a single sorted file to avoid the overhead of many
45+
small "loose" reference files. However, updating this file requires
46+
rewriting it entirely, and Git typically verifies that objects exist
47+
and "peels" tags (finding the underlying object a tag points to)
48+
during this process.
49+
50+
brian m. carlson replied to Martin, suggesting that the slowdown
51+
might be occurring in `should_pack_ref()` because Git needs to
52+
verify that the object at the end of a reference actually
53+
exists. Brian also pointed out that NFS was likely a major factor,
54+
as the network latency involved in opening many pack files and
55+
checking loose objects can be substantial. He suggested setting
56+
`receive.unpackLimit` to 1 to reduce the number of loose objects
57+
created in the first place.
58+
59+
Peff (alias Jeff King) explained that the `packed-refs` file stores
60+
"tag-peeling" information, which requires Git to open each object
61+
for newly written refs via `peel_object()` to read its header and
62+
determine its type. Peff noted that this logic resides in
63+
`write_with_updates()` within `packed-backend.c`.
64+
65+
Martin conducted further testing using `strace` and `drop_caches` to
66+
eliminate filesystem caching interference. He discovered that while
67+
the actual `write()` calls were fast, there were long gaps—up to
68+
four minutes in total—where the program was not making any system
69+
calls. Martin hypothesized that this "hidden" time was spent by the
70+
kernel handling page faults for `mmap()`ed memory over NFS.
71+
72+
Patrick Steinhardt concurred that NFS was frequently a source of
73+
such performance issues, mentioning that GitLab had eventually
74+
sunsetted the use of NFS for this reason. Patrick suggested using
75+
`perf(1)` to generate a flame graph to see exactly where the CPU was
76+
spending time.
77+
78+
Martin provided a summary of a flame graph, which showed about
79+
one-third of the time spent in `_memcmp_sse4_1` and another third in
80+
`unpack_object_header_buffer()`, both accompanied by high page fault
81+
rates. He also noticed significant time spent in a function he
82+
identified as `packed_refs_store_create()`.
83+
84+
Peff corrected the function name to `packed_ref_store_create()` and
85+
noted that Git might be performing an extra linear pass through the
86+
`packed-refs` file if it lacks certain header tags. He discovered
87+
that JGit-generated files were missing the `sorted` and
88+
`fully-peeled` traits in the header. Without the `sorted` tag, Git
89+
reads the entire file linearly to verify its order before it can
90+
perform binary searches. Peff suggested that JGit should be updated
91+
to write these markers.
92+
93+
In a final breakthrough, Martin tested adding these tags
94+
manually. He found that while the `sorted` tag did not provide a
95+
major boost, adding the `fully-peeled` tag was the "trigger" that
96+
dropped the execution time from over five minutes to under four
97+
seconds. The absence of the `fully-peeled` tag was forcing Git to
98+
re-peel every reference by looking up the objects in the pack files
99+
over the slow NFS connection.
100+
101+
The discussion concluded with the identification of a specific
102+
interoperability issue between JGit and Git. By identifying that the
103+
missing `fully-peeled` header was causing redundant, expensive I/O
104+
operations, Martin was able to plan a fix for JGit that would
105+
resolve the performance bottleneck on his production servers.
31106

32107
<!---
33108
## Developer Spotlight:

0 commit comments

Comments
 (0)