Skip to content

Commit 6c7f19b

Browse files
committed
pack-aggregate: introduce a builtin for cheap object/pack aggregation
On busy servers it is possible for the object directory to accumulate many loose objects and many small push packs while a long-running maintenance pass is still working. Until that pass finally finishes, unrelated git operations end up scanning more files in `objects/` and `objects/pack/` than is healthy, which shows up as visible latency on otherwise quick commands. Introduce a new plumbing builtin, `git pack-aggregate`, intended to be spawned by `git repack` and run in a loop alongside the normal repacking process. Each cycle does two things in order: 1. Bundle local loose objects (minus any in `--exclude-loose-file`) into a new pack with `pack-objects --window=0 --mark-bad-deltas --delta-base-offset --no-write-bitmap-index`, and unlink the loose copies on success. 2. Aggregate small local packs (minus any in `--exclude-pack-file`, minus packs in the multi-pack-index, minus packs carrying `.keep`, `.promisor`, `.mtimes`, or `.bitmap` sidecars) into a single new pack the same way, then unlink the source packs. Both steps copy the existing on-disk representation of each object through unchanged: no delta search, no bitmap or commit-graph update, no object recompression. The output pack lands with a `.baddeltas` marker so a subsequent thorough repack knows it should reconsider intra-pack deltas in that pack. The roll-up pack produced in step 1 is naturally a candidate for step 2 of the same cycle and is normally folded in. `git pack-aggregate` takes no locks of its own, much like `git repack`. Callers that need serialization (typically `git gc`, or a server-side maintenance driver) must arrange it themselves. The integration with `git repack` (gated on a new `repack.aggregate` config and `--aggregate` CLI flag) is left for a follow-on patch. Assisted-by: Claude Opus 4.7 Signed-off-by: Elijah Newren <newren@gmail.com>
1 parent 3a8428b commit 6c7f19b

8 files changed

Lines changed: 978 additions & 0 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
git-pack-aggregate(1)
2+
=====================
3+
4+
NAME
5+
----
6+
git-pack-aggregate - Periodically roll up loose objects and small packs
7+
into a single pack without doing any delta compression or search
8+
9+
SYNOPSIS
10+
--------
11+
[verse]
12+
'git pack-aggregate' (--once | --loop) [--interval=<seconds>]
13+
[--min-loose=<n>] [--min-packs=<n>]
14+
[--exclude-pack-file=<path>]
15+
[--exclude-loose-file=<path>]
16+
[--parent-pipe-fd=<n>]
17+
18+
DESCRIPTION
19+
-----------
20+
21+
`git pack-aggregate` watches `$GIT_DIR/objects` for loose objects and
22+
small packfiles that have accumulated since the last cycle, and
23+
periodically rolls them up into a single new pack. Each cycle has two
24+
steps:
25+
26+
1. Bundle local loose objects (minus any listed in
27+
`--exclude-loose-file`) into a new pack and unlink the loose copies.
28+
2. Aggregate small local packs (minus any listed in
29+
`--exclude-pack-file`, or those referenced by the multi-pack-index,
30+
or those carrying a `.keep`, `.promisor`, `.mtimes`, or `.bitmap`
31+
sidecar) into another new pack and unlink the source packs. The
32+
pack written in step 1 is naturally a candidate here and will
33+
normally be folded into the step-2 output.
34+
35+
No delta search is performed, no bitmaps or commit-graphs are updated,
36+
and no objects are recompressed; the existing on-disk representation
37+
of each object is copied through unchanged. Both new packs are
38+
written with `pack-objects --window=0 --mark-bad-deltas`, so each one
39+
ships with a `.baddeltas` sidecar (see linkgit:gitformat-pack[5]). A
40+
subsequent thorough repack (linkgit:git-repack[1]) honors that marker
41+
and reconsiders intra-pack deltas at that time.
42+
43+
The purpose of this command is to keep `objects/` from accumulating
44+
large numbers of loose objects or small packs while a long-running
45+
repack is in flight, so unrelated `git` operations are not slowed down
46+
by readdir cost in a sprawling object directory. Used together with
47+
`repack.aggregate` (see linkgit:git-repack[1]), `git repack` will spawn
48+
this command for the duration of its own work.
49+
50+
`git pack-aggregate` takes no locks of its own. It mirrors
51+
linkgit:git-repack[1] in this regard: callers that need serialization
52+
must arrange it themselves (for example by running under `git gc`).
53+
The exclusion files supplied via `--exclude-pack-file` and
54+
`--exclude-loose-file` are the only mechanism by which a concurrent
55+
`pack-objects` or `git repack` declares "do not touch these inputs".
56+
57+
OPTIONS
58+
-------
59+
60+
--once::
61+
Run a single cycle and exit. Exactly one of `--once` or
62+
`--loop` is required.
63+
64+
--loop::
65+
Run cycles forever, sleeping `--interval` seconds between
66+
cycles. Stops on `SIGTERM`, `SIGHUP`, or `SIGINT`.
67+
68+
--interval=<seconds>::
69+
Number of seconds to sleep between the end of one cycle and the
70+
start of the next. Defaults to 60. Only meaningful with
71+
`--loop`.
72+
73+
--min-loose=<n>::
74+
Skip step 1 if fewer than `<n>` loose objects (after applying
75+
`--exclude-loose-file`) are present. Defaults to 5.
76+
77+
--min-packs=<n>::
78+
Skip step 2 if fewer than `<n>` aggregatable packs (after
79+
applying all exclusions) are present. Defaults to 5.
80+
81+
--exclude-pack-file=<path>::
82+
Read a list of pack basenames (one per line) from `<path>` and
83+
never touch any of those packs. Lines may name a basename with
84+
or without a `.pack` or `.idx` suffix; the suffix is stripped.
85+
Blank lines and lines beginning with `#` are ignored. When
86+
`git pack-aggregate` is spawned by a long-running `git repack`,
87+
this file is populated by that `pack-objects` itself via
88+
`--emit-input-packs`, and may contain a conservative superset
89+
of the packs it will actually consume (such as in `--geometric`
90+
mode).
91+
92+
--exclude-loose-file=<path>::
93+
Read a list of loose object IDs (one per line) from `<path>`
94+
and never pack or unlink any of those loose objects. Blank
95+
lines and lines beginning with `#` are ignored. When `git
96+
pack-aggregate` is spawned by a long-running `git repack`, this
97+
file is populated by that `pack-objects` itself via
98+
`--emit-input-loose`.
99+
100+
--parent-pipe-fd=<n>::
101+
An inherited pipe file descriptor whose write end the parent
102+
process holds open. When the parent exits the pipe is closed
103+
and `git pack-aggregate` exits at the next cycle boundary,
104+
without waiting out the remainder of `--interval`. This is an
105+
internal plumbing option used by `git repack` to keep its
106+
companion aggregator from outliving it.
107+
108+
SEE ALSO
109+
--------
110+
linkgit:git-repack[1], linkgit:git-pack-objects[1],
111+
linkgit:gitformat-pack[5]
112+
113+
GIT
114+
---
115+
Part of the linkgit:git[1] suite

Documentation/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ manpages = {
9999
'git-name-rev.adoc' : 1,
100100
'git-notes.adoc' : 1,
101101
'git-p4.adoc' : 1,
102+
'git-pack-aggregate.adoc' : 1,
102103
'git-pack-objects.adoc' : 1,
103104
'git-pack-refs.adoc' : 1,
104105
'git-patch-id.adoc' : 1,

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,6 +1450,7 @@ BUILTIN_OBJS += builtin/multi-pack-index.o
14501450
BUILTIN_OBJS += builtin/mv.o
14511451
BUILTIN_OBJS += builtin/name-rev.o
14521452
BUILTIN_OBJS += builtin/notes.o
1453+
BUILTIN_OBJS += builtin/pack-aggregate.o
14531454
BUILTIN_OBJS += builtin/pack-objects.o
14541455
ifndef WITH_BREAKING_CHANGES
14551456
BUILTIN_OBJS += builtin/pack-redundant.o

builtin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ int cmd_multi_pack_index(int argc, const char **argv, const char *prefix, struct
224224
int cmd_mv(int argc, const char **argv, const char *prefix, struct repository *repo);
225225
int cmd_name_rev(int argc, const char **argv, const char *prefix, struct repository *repo);
226226
int cmd_notes(int argc, const char **argv, const char *prefix, struct repository *repo);
227+
int cmd_pack_aggregate(int argc, const char **argv, const char *prefix, struct repository *repo);
227228
int cmd_pack_objects(int argc, const char **argv, const char *prefix, struct repository *repo);
228229
int cmd_pack_redundant(int argc, const char **argv, const char *prefix, struct repository *repo);
229230
int cmd_patch_id(int argc, const char **argv, const char *prefix, struct repository *repo);

0 commit comments

Comments
 (0)