From: "Kristofer Karlsson via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Kristofer Karlsson <krka@spotify.com>
Subject: [PATCH v3 0/2] reftable: fix quadratic behavior when re-creating deleted refs
Date: Fri, 10 Jul 2026 10:36:05 +0000 [thread overview]
Message-ID: <pull.2166.v3.git.1783679767.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2166.v2.git.1783598912.gitgitgadget@gmail.com>
This series fixes quadratic behavior in the reftable backend when many
tombstones are present. Any operation that seeks into a range containing
tombstones is affected, including ref lookups and D/F conflict checks.
The root cause is the merged iterator's suppress_deletions flag, which
silently consumes tombstone records in a tight internal loop. This prevents
higher-level code from checking iteration bounds until after all tombstones
have been scanned, making both refs_verify_refnames_available() and
reftable_backend_read_ref() O(n) per call in the presence of tombstones.
The fix makes suppress_deletions configurable via reftable_stack_options
(defaulting to off) and handles deletion records at each call site in the
reftable backend, where prefix and refname bounds are available. This lets
existing bounds checks terminate iteration early when encountering
tombstones past the relevant bound.
Downstream users of the reftable library (e.g. libgit2) can enable
suppress_deletions through the stack options to retain the previous
behavior.
The first patch adds a perf test (p1401) exercising two tombstone scenarios
with 8000 refs. The second patch is the optimization. Both p1401 tests go
from ~13s to ~0.2s with the fix.
Note that auto-compaction typically merges tombstones before they accumulate
to this degree, so the quadratic behavior may not show up in every workflow.
But the fix ensures correct time complexity regardless of compaction state,
and the change is fairly contained.
Changes since v2:
* Add suppress_deletions to reftable_stack_options so downstream callers
can control it at stack creation time (suggested by Patrick)
Changes since v1:
* Keep suppress_deletions in the reftable library for downstream users;
only stop setting it in stack.c
* Broaden scope description to cover all readers, not just ref creation
* Use separate repositories in perf test to avoid cross-scenario state
* Drop correctness test (implicitly covered by t1400)
Previous discussion:
https://lore.kernel.org/git/20260701080014.GA3748390@coredump.intra.peff.net/
Kristofer Karlsson (2):
t/perf: add perf test for ref tombstone scenarios
reftable: fix quadratic behavior in the presence of tombstones
refs/reftable-backend.c | 54 ++++++++++++++++++++++------
reftable/reftable-stack.h | 2 ++
reftable/stack.c | 2 +-
t/perf/p1401-ref-store-tombstones.sh | 46 ++++++++++++++++++++++++
4 files changed, 92 insertions(+), 12 deletions(-)
create mode 100755 t/perf/p1401-ref-store-tombstones.sh
base-commit: f85a7e662054a7b0d9070e432508831afa214b47
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2166%2Fspkrka%2Freftable-tombstone-perf-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2166/spkrka/reftable-tombstone-perf-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/2166
Range-diff vs v2:
1: 889d0d38bc = 1: 889d0d38bc t/perf: add perf test for ref tombstone scenarios
2: c13f15ddc2 ! 2: 4fdcec8440 reftable: fix quadratic behavior in the presence of tombstones
@@ Commit message
found", because the merged iterator skips the matching tombstone
and searches for the next live record.
- Fix this by no longer setting suppress_deletions on the stack's
- merged table and instead handling deletion records at each call site
- in the reftable backend, where prefix and refname bounds are
- available. Tombstones are now returned to callers, which skip them
- after their existing bounds checks. This allows iteration to
- terminate as soon as a tombstone past the relevant bound is
- encountered.
+ Fix this by making suppress_deletions configurable via
+ reftable_stack_options instead of unconditionally enabling it. Git
+ no longer sets the flag, so tombstones are now returned to callers in
+ the reftable backend, which skip them after their existing bounds
+ checks. This allows iteration to terminate as soon as a tombstone
+ past the relevant bound is encountered.
- The suppress_deletions flag and its logic in the merged iterator are
- retained for downstream users of the reftable library (e.g. libgit2).
+ Downstream users of the reftable library (e.g. libgit2) can still
+ enable suppress_deletions through the stack options to retain the
+ previous behavior.
This also requires adding deletion checks to the log iteration paths,
since suppress_deletions applied to both ref and log iterators.
- Both tests in p1401 go from ~14s to ~0.2s with this change.
+ Both tests in p1401 go from ~13s to ~0.2s with this change.
Reported-by: Jeff King <peff@peff.net>
Signed-off-by: Kristofer Karlsson <krka@spotify.com>
@@ refs/reftable-backend.c: static int reftable_be_fsck(struct ref_store *ref_store
case REFTABLE_REF_VAL2: {
struct object_id oid;
+ ## reftable/reftable-stack.h ##
+@@ reftable/reftable-stack.h: struct reftable_stack_options {
+ */
+ void (*on_reload)(void *payload);
+ void *on_reload_payload;
++
++ int suppress_deletions;
+ };
+
+ /* open a new reftable stack. The tables along with the table list will be
+
## reftable/stack.c ##
@@ reftable/stack.c: static int reftable_stack_reload_once(struct reftable_stack *st,
/* Update the stack to point to the new tables. */
if (st->merged)
reftable_merged_table_free(st->merged);
- new_merged->suppress_deletions = 1;
++ new_merged->suppress_deletions = st->opts.suppress_deletions;
st->merged = new_merged;
if (st->tables)
--
gitgitgadget
next prev parent reply other threads:[~2026-07-10 10:36 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 13:35 [PATCH 0/2] reftable: fix quadratic behavior when re-creating deleted refs Kristofer Karlsson via GitGitGadget
2026-07-06 13:35 ` [PATCH 1/2] t: add tests for ref tombstone scenarios Kristofer Karlsson via GitGitGadget
2026-07-07 15:24 ` Patrick Steinhardt
2026-07-07 16:12 ` Kristofer Karlsson
2026-07-08 5:59 ` Patrick Steinhardt
2026-07-08 7:28 ` Kristofer Karlsson
2026-07-06 13:35 ` [PATCH 2/2] reftable: fix quadratic behavior when re-creating deleted refs Kristofer Karlsson via GitGitGadget
2026-07-07 15:24 ` Patrick Steinhardt
2026-07-07 16:04 ` Kristofer Karlsson
2026-07-08 11:15 ` [PATCH 0/2] " brian m. carlson
2026-07-09 12:08 ` [PATCH v2 " Kristofer Karlsson via GitGitGadget
2026-07-09 12:08 ` [PATCH v2 1/2] t/perf: add perf test for ref tombstone scenarios Kristofer Karlsson via GitGitGadget
2026-07-09 12:08 ` [PATCH v2 2/2] reftable: fix quadratic behavior in the presence of tombstones Kristofer Karlsson via GitGitGadget
2026-07-09 13:53 ` Patrick Steinhardt
2026-07-09 14:48 ` Kristofer Karlsson
2026-07-09 14:54 ` Patrick Steinhardt
2026-07-10 10:36 ` Kristofer Karlsson via GitGitGadget [this message]
2026-07-10 10:36 ` [PATCH v3 1/2] t/perf: add perf test for ref tombstone scenarios Kristofer Karlsson via GitGitGadget
2026-07-10 10:36 ` [PATCH v3 2/2] reftable: fix quadratic behavior in the presence of tombstones Kristofer Karlsson via GitGitGadget
2026-07-10 14:32 ` Patrick Steinhardt
2026-07-10 15:03 ` Kristofer Karlsson
2026-07-13 5:14 ` 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=pull.2166.v3.git.1783679767.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=krka@spotify.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.