diff mbox series

[v3,13/20] maintenance: auto-size incremental-repack batch

Message ID ef2a2319565939bfbdbd2e898a980c61ecc39c6a.1596147867.git.gitgitgadget@gmail.com (mailing list archive)
State New, archived
Headers show
Series Maintenance builtin, allowing 'gc --auto' customization | expand

Commit Message

Linus Arver via GitGitGadget July 30, 2020, 10:24 p.m. UTC
From: Derrick Stolee <dstolee@microsoft.com>

When repacking during the 'incremental-repack' task, we use the
--batch-size option in 'git multi-pack-index repack'. The initial setting
used --batch-size=0 to repack everything into a single pack-file. This is
not sustainable for a large repository. The amount of work required is
also likely to use too many system resources for a background job.

Update the 'incremental-repack' task by dynamically computing a
--batch-size option based on the current pack-file structure.

The dynamic default size is computed with this idea in mind for a client
repository that was cloned from a very large remote: there is likely one
"big" pack-file that was created at clone time. Thus, do not try
repacking it as it is likely packed efficiently by the server.

Instead, we select the second-largest pack-file, and create a batch size
that is one larger than that pack-file. If there are three or more
pack-files, then this guarantees that at least two will be combined into
a new pack-file.

Of course, this means that the second-largest pack-file size is likely
to grow over time and may eventually surpass the initially-cloned
pack-file. Recall that the pack-file batch is selected in a greedy
manner: the packs are considered from oldest to newest and are selected
if they have size smaller than the batch size until the total selected
size is larger than the batch size. Thus, that oldest "clone" pack will
be first to repack after the new data creates a pack larger than that.

We also want to place some limits on how large these pack-files become,
in order to bound the amount of time spent repacking. A maximum
batch-size of two gigabytes means that large repositories will never be
packed into a single pack-file using this job, but also that repack is
rather expensive. This is a trade-off that is valuable to have if the
maintenance is being run automatically or in the background. Users who
truly want to optimize for space and performance (and are willing to pay
the upfront cost of a full repack) can use the 'gc' task to do so.

Reported-by: Son Luong Ngoc <sluongng@gmail.com>
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
---
 builtin/gc.c           | 43 +++++++++++++++++++++++++++++++++++++++++-
 t/t7900-maintenance.sh |  5 +++--
 2 files changed, 45 insertions(+), 3 deletions(-)

Comments

Chris Torek July 30, 2020, 11:36 p.m. UTC | #1
On Thu, Jul 30, 2020 at 3:26 PM Derrick Stolee via GitGitGadget
<gitgitgadget@gmail.com> wrote:
> diff --git a/builtin/gc.c b/builtin/gc.c
> index 99ab1f5e9d..d94eb3e6ad 100644
> --- a/builtin/gc.c
> +++ b/builtin/gc.c
> @@ -988,6 +988,46 @@ static int multi_pack_index_expire(void)
>         return 0;
>  }
>
> +#define TWO_GIGABYTES (0x7FFF)

You meant (0x7FFFFFFF) here, right?

Chris
Derrick Stolee Aug. 3, 2020, 5:43 p.m. UTC | #2
On 7/30/2020 7:36 PM, Chris Torek wrote:
> On Thu, Jul 30, 2020 at 3:26 PM Derrick Stolee via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
>> diff --git a/builtin/gc.c b/builtin/gc.c
>> index 99ab1f5e9d..d94eb3e6ad 100644
>> --- a/builtin/gc.c
>> +++ b/builtin/gc.c
>> @@ -988,6 +988,46 @@ static int multi_pack_index_expire(void)
>>         return 0;
>>  }
>>
>> +#define TWO_GIGABYTES (0x7FFF)
> 
> You meant (0x7FFFFFFF) here, right?

Ugh. Of course. Thanks for being diligent here. I hadn't pulled
this branch into microsoft/git for testing with Scalar yet, where
I would have noticed this.

I'm tempted to create an EXPENSIVE test that actually checks this
condition. Let's see what I can do.

Thanks,
-Stolee
diff mbox series

Patch

diff --git a/builtin/gc.c b/builtin/gc.c
index 99ab1f5e9d..d94eb3e6ad 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -988,6 +988,46 @@  static int multi_pack_index_expire(void)
 	return 0;
 }
 
+#define TWO_GIGABYTES (0x7FFF)
+
+static off_t get_auto_pack_size(void)
+{
+	/*
+	 * The "auto" value is special: we optimize for
+	 * one large pack-file (i.e. from a clone) and
+	 * expect the rest to be small and they can be
+	 * repacked quickly.
+	 *
+	 * The strategy we select here is to select a
+	 * size that is one more than the second largest
+	 * pack-file. This ensures that we will repack
+	 * at least two packs if there are three or more
+	 * packs.
+	 */
+	off_t max_size = 0;
+	off_t second_largest_size = 0;
+	off_t result_size;
+	struct packed_git *p;
+	struct repository *r = the_repository;
+
+	reprepare_packed_git(r);
+	for (p = get_all_packs(r); p; p = p->next) {
+		if (p->pack_size > max_size) {
+			second_largest_size = max_size;
+			max_size = p->pack_size;
+		} else if (p->pack_size > second_largest_size)
+			second_largest_size = p->pack_size;
+	}
+
+	result_size = second_largest_size + 1;
+
+	/* But limit ourselves to a batch size of 2g */
+	if (result_size > TWO_GIGABYTES)
+		result_size = TWO_GIGABYTES;
+
+	return result_size;
+}
+
 static int multi_pack_index_repack(void)
 {
 	struct child_process child = CHILD_PROCESS_INIT;
@@ -998,7 +1038,8 @@  static int multi_pack_index_repack(void)
 	if (opts.quiet)
 		strvec_push(&child.args, "--no-progress");
 
-	strvec_push(&child.args, "--batch-size=0");
+	strvec_pushf(&child.args, "--batch-size=%"PRIuMAX,
+				  (uintmax_t)get_auto_pack_size());
 
 	close_object_store(the_repository->objects);
 
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 0cc59adb21..4e9c1dfa0f 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -138,10 +138,11 @@  test_expect_success 'incremental-repack task' '
 	test_line_count = 4 packs-between &&
 
 	# the job deletes the two old packs, and does not write
-	# a new one because only one pack remains.
+	# a new one because the batch size is not high enough to
+	# pack the largest pack-file.
 	git maintenance run --task=incremental-repack &&
 	ls .git/objects/pack/*.pack >packs-after &&
-	test_line_count = 1 packs-after
+	test_line_count = 2 packs-after
 '
 
 test_done