Git development
 help / color / mirror / Atom feed
From: Johannes Schindelin <Johannes.Schindelin@gmx.de>
To: Patrick Steinhardt <ps@pks.im>
Cc: git@vger.kernel.org,
	Wolfgang Kritzinger <wkritzinger@atlassian.com>,
	 Justin Tobler <jltobler@gmail.com>
Subject: Re: [PATCH v2 1/6] odb/source-files: fix performance when reading objects with alternates
Date: Wed, 14 Oct 2026 15:35:56 +0200 (CEST)	[thread overview]
Message-ID: <332d341e-0a1b-790f-4b0f-55a7ebb80f3f@gmx.de> (raw)
In-Reply-To: <20261014-pks-odb-files-alternates-cleanups-v2-1-fed9b2caa3be@pks.im>

[-- Attachment #1: Type: text/plain, Size: 8247 bytes --]

Hi Patrick,

On Wed, 14 Oct 2026, Patrick Steinhardt wrote:

> With the move towards pluggable object databases we reorganized how we
> access alternates. Before the introduction of pluggability, we were
> reading objects by first scanning through packfiles from all alternates
> before scanning through all loose object directories. With the change
> though, we are now iterating through the sources one by one, checking
> each of them for whether they contain the object either as a packed or
> loose object.
> 
> Checking whether an object exists in packfiles is comparatively cheap
> because the list of packs is cached, and the pack indices are likely to
> be in the page cache. In contrast to that, checking whether an object
> exists in loose format always incurs one stat(3p) syscall. So scanning
> for objects one alternate at a time led to a performance regression in
> in the case where most of the objects are contained in a packfile of an
> alternate, as reported in [1].
> 
> We could fix that issue by introducing another flag that tells the
> backends to only search the optimized storage backend. But that is
> adding more complexity to the object database layers that is ultimately
> only really required because of the way the "files" backend stores its
> data. So for an alternate backend, such new infrastructure would not
> really be a good fit.
> 
> But since then we have moved handling of alternates into the "files"
> backend itself. One of the consequences of this is that this backend is
> now having full control over all of its sub-sources, and that also makes
> it way easier for us to optimize for this access pattern again.
> 
> Do so by first trying to read objects from packfiles located in any of
> its object directories before trying to read loose objects.
> 
> The following benchmark demonstrates that this significantly improves
> performance in some cases. The benchmark uses the Git repository itself
> and sets up its object directory as an alternate, while the primary
> object directory is simply an empty object directory. It then reads all
> commits enumerated via `git rev-list HEAD` with git-cat-file(1):
> 
>   Benchmark 1: git cat-file --batch-check --unordered (rev = HEAD~)
>     Time (mean ± σ):     179.4 ms ±   2.6 ms    [User: 114.6 ms, System: 61.4 ms]
>     Range (min … max):   177.1 ms … 187.7 ms    16 runs
> 
>   Benchmark 2: git cat-file --batch-check --unordered (rev = HEAD)
>     Time (mean ± σ):      95.0 ms ±   1.5 ms    [User: 79.8 ms, System: 15.0 ms]
>     Range (min … max):    93.0 ms …  97.9 ms    30 runs
> 
>   Summary
>     git cat-file --batch-check --unordered (rev = HEAD) ran
>       1.89 ± 0.04 times faster than git cat-file --batch-check --unordered (rev = HEAD~)
> 
> [1]: <CAFXJcxvpKHoVDwE5mBOd=w-A5vPdUmehqr8SHLUD7qv1qB00rA@mail.gmail.com>
> 
> Reported-by: Wolfgang Kritzinger <wkritzinger@atlassian.com>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>

Thank you so much for this! This fixes the bug we had to patch
independently in dc305fc33833 (odb: scan all sources’ packfiles before
loose objects), via https://github.com/microsoft/git/pull/975, first
contained in Microsoft Git v2.55.0.vfs.0.7. Would you kindly include (my
colleague) Tyrie Vella's regression test in the next iteration of this
patch series?

-- snip --
From: Tyrie Vella <tyrielv@gmail.com>
Date: Wed, 14 Oct 2026 15:01:45 +0200
Subject: [PATCH] t5615: verify packed alternates precede local loose objects

This regression tests protects the packed-first lookup order required for
decent performance in setups where the majority of Git objects lives in a
separate alternate's packfiles (such as is the case with typical Scalar or
VFS for Git setups).

The test was originally added to Microsoft Git via
https://github.com/microsoft/git/pull/975

Assisted-by: GPT-6.1 Sol
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5615-alternate-env.sh | 43 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/t/t5615-alternate-env.sh b/t/t5615-alternate-env.sh
index 9d6aa2187f2..c982e8e95d2 100755
--- a/t/t5615-alternate-env.sh
+++ b/t/t5615-alternate-env.sh
@@ -88,4 +88,47 @@ test_expect_success !MINGW 'broken quoting falls back to interpreting raw' '
 	EOF
 '
 
+test_expect_success 'packs across sources are checked before loose objects' '
+	# Regression test for a performance issue in which an object that
+	# resides in an alternate as a packed object caused a spurious loose
+	# object lookup (a filesystem stat) on the main object store before the
+	# alternate packfile was consulted. Reading such an object must resolve
+	# to the alternate packfile, never to a loose copy in the main store.
+	#
+	# Build an alternate whose object "B" is stored as a delta in a
+	# packfile. git deltifies successive versions of a tracked file, so the
+	# older, shorter blob "B" becomes a delta against the newer, longer
+	# blob "O". A loose object has no delta base, so %(deltabase) tells us
+	# which store answered the read: the alternate pack (O) or a loose copy
+	# (the zero oid).
+	git init alt-src &&
+	test_seq 1 200 >alt-src/file &&
+	git -C alt-src add file &&
+	git -C alt-src commit -q -m base &&
+	B=$(git -C alt-src rev-parse HEAD:file) &&
+	git -C alt-src cat-file blob "$B" >b-content &&
+	test_seq 1 210 >alt-src/file &&
+	git -C alt-src add file &&
+	git -C alt-src commit -q -m more &&
+	O=$(git -C alt-src rev-parse HEAD:file) &&
+	git -C alt-src repack -adf --window=10 --depth=50 &&
+
+	# Precondition: in the alternate, B is a delta based on O.
+	echo "$B" >in &&
+	echo "$O" >expect &&
+	git -C alt-src cat-file --batch-check="%(deltabase)" <in >actual &&
+	test_cmp expect actual &&
+
+	# Main repo: write B as a loose object, before any alternate is active.
+	git init main &&
+	git -C main hash-object -w --stdin <b-content >/dev/null &&
+	test -e "main/.git/objects/$(test_oid_to_path "$B")" &&
+
+	# With the alternate active, B must resolve to the alternate packfile
+	# (deltabase O), not to the main store loose copy (deltabase zero oid).
+	GIT_ALTERNATE_OBJECT_DIRECTORIES="$PWD/alt-src/.git/objects" \
+		git -C main cat-file --batch-check="%(deltabase)" <in >actual &&
+	test_cmp expect actual
+'
+
 test_done
-- snap --

I ran the test on Linux with both SHA-1 and SHA-256. In both formats, the
unfixed base passes the nine existing tests; With vanilla v2.56.0 the
added case fails at its final lookup-order assertion: `%(deltabase)`
returns the zero OID despite the packed-delta precondition passing. Patch
1 (and of course the full patch series) pass the tests.

Ciao,
Johannes

> ---
>  odb/source-files.c | 5 +++--
>  1 file changed, 3 insertions(+), 2 deletions(-)
> 
> diff --git a/odb/source-files.c b/odb/source-files.c
> index 455f409db0..9268f5aa9a 100644
> --- a/odb/source-files.c
> +++ b/odb/source-files.c
> @@ -426,6 +426,7 @@ static enum odb_read_status odb_source_files_read_object_info(struct odb_source
>  {
>  	struct odb_source_files *files = odb_source_files_downcast(source);
>  	enum odb_read_status status = ODB_READ_NOT_FOUND;
> +	enum odb_read_status ret;
>  
>  	/*
>  	 * Reading an object may fail even though the object exists, for
> @@ -434,15 +435,15 @@ static enum odb_read_status odb_source_files_read_object_info(struct odb_source
>  	 * prefer the first such error in case multiple reads have failed.
>  	 */
>  	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
> -		enum odb_read_status ret;
> -
>  		ret = odb_source_read_object_info(&dir->packed->base, oid, oi, flags,
>  						  status == ODB_READ_NOT_FOUND ? errmsg : NULL);
>  		if (!ret)
>  			return 0;
>  		if (ret != ODB_READ_NOT_FOUND && status == ODB_READ_NOT_FOUND)
>  			status = ret;
> +	}
>  
> +	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
>  		ret = odb_source_read_object_info(&dir->loose->base, oid, oi, flags,
>  						  status == ODB_READ_NOT_FOUND ? errmsg : NULL);
>  		if (!ret)
> 
> -- 

  reply	other threads:[~2026-10-14 13:35 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-13  7:36 [PATCH 0/5] odb/files: cleanups and improvements for alternates Patrick Steinhardt
2026-10-13  7:36 ` [PATCH 1/5] odb/source-files: fix performance when reading objects with alternates Patrick Steinhardt
2026-10-13 20:46   ` Justin Tobler
2026-10-13  7:36 ` [PATCH 2/5] odb/source-files: fix performance when streaming " Patrick Steinhardt
2026-10-13 20:51   ` Justin Tobler
2026-10-13  7:36 ` [PATCH 3/5] odb/source-files: make the second read an implementation detail Patrick Steinhardt
2026-10-13 21:28   ` Justin Tobler
2026-10-14  6:55     ` Patrick Steinhardt
2026-10-14 15:37       ` Justin Tobler
2026-10-13  7:36 ` [PATCH 4/5] odb: remove read loop for found objects Patrick Steinhardt
2026-10-13 21:48   ` Justin Tobler
2026-10-14  6:56     ` Patrick Steinhardt
2026-10-14 15:38       ` Justin Tobler
2026-10-13  7:36 ` [PATCH 5/5] odb/source: drop `local` flag Patrick Steinhardt
2026-10-13 22:44   ` Justin Tobler
2026-10-14  6:56     ` Patrick Steinhardt
2026-10-14 15:43       ` Justin Tobler
2026-10-14  8:53 ` [PATCH v2 0/6] odb/files: cleanups and improvements for alternates Patrick Steinhardt
2026-10-14  8:53   ` [PATCH v2 1/6] odb/source-files: fix performance when reading objects with alternates Patrick Steinhardt
2026-10-14 13:35     ` Johannes Schindelin [this message]
2026-10-14 13:44       ` Patrick Steinhardt
2026-10-14  8:53   ` [PATCH v2 2/6] odb/source-files: fix performance when streaming " Patrick Steinhardt
2026-10-14  8:53   ` [PATCH v2 3/6] odb/source-files: make the second read an implementation detail Patrick Steinhardt
2026-10-14  8:53   ` [PATCH v2 4/6] odb: remove read loop for found objects Patrick Steinhardt
2026-10-14  8:53   ` [PATCH v2 5/6] odb: don't try re-fetching corrupt objects Patrick Steinhardt
2026-10-14 15:59     ` Justin Tobler
2026-10-14  8:53   ` [PATCH v2 6/6] odb/source: drop `local` flag Patrick Steinhardt

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=332d341e-0a1b-790f-4b0f-55a7ebb80f3f@gmx.de \
    --to=johannes.schindelin@gmx.de \
    --cc=git@vger.kernel.org \
    --cc=jltobler@gmail.com \
    --cc=ps@pks.im \
    --cc=wkritzinger@atlassian.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox