git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH] commit: detect commits that exist in commit-graph but not in the ODB

From
Patrick Steinhardt <ps@pks.im>
Date
Oct 17, 2023, 06:37 UTC
Message-ID
<ZS4rmtBTYnp2RMiY@tanuki>
In-Reply-To
<xmqq1qdy1iyr.fsf@gitster.g>
On Fri, Oct 13, 2023 at 11:21:48AM -0700, Junio C Hamano wrote:
Show 32 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > @@ -572,8 +572,13 @@ int repo_parse_commit_internal(struct repository *r,
> >  		return -1;
> >  	if (item->object.parsed)
> >  		return 0;
> > -	if (use_commit_graph && parse_commit_in_graph(r, item))
> > +	if (use_commit_graph && parse_commit_in_graph(r, item)) {
> > +		if (!has_object(r, &item->object.oid, 0))
> > +			return quiet_on_missing ? -1 :
> > +				error(_("commit %s exists in commit-graph but not in the object database"),
> > +				      oid_to_hex(&item->object.oid));
> >  		return 0;
> > +	}
> 
> Ever since this codepath was introduced by 177722b3 (commit:
> integrate commit graph with commit parsing, 2018-04-10), we blindly
> trusted what commit-graph file says.  This change is a strict
> improvement in the correctness department, but there are two things
> that are a bit worrying.
> 
> One.  The additional check should certainly be cheaper than a full
> reading and parsing of an object, either from a loose object file or
> from a pack entry.  It may not hurt performance too much, but it
> still would give us more confidence if we know by how much we are
> pessimising good cases where the commit-graph does match reality.
> Our stance on these secondary files that store precomputed values
> for optimization purposes is in general to use them blindly unless
> in exceptional cases where the operation values the correctness even
> when the validity of these secondary files is dubious (e.g., "fsck"),
> and doing this extra check regardless of the caller at this low level
> of the callchain is a bit worrying.

Fair point indeed. The following is a worst-case scenario benchmark of of the change where we do a full topological walk of all reachable commits in the graph, executed in linux.git. We parse commit parents via `repo_parse_commit_gently()`, so the new code path now basically has to check for object existence of every reachable commit:

Benchmark 1: git -c core.commitGraph=true rev-list --topo-order --all (git = master)
  Time (mean ± σ):      2.913 s ±  0.018 s    [User: 2.363 s, System: 0.548 s]
  Range (min … max):    2.894 s …  2.950 s    10 runs
Benchmark 2: git -c core.commitGraph=true rev-list --topo-order --all (git = pks-commit-graph-inconsistency)
  Time (mean ± σ):      3.834 s ±  0.052 s    [User: 3.276 s, System: 0.556 s]
  Range (min … max):    3.780 s …  3.961 s    10 runs
Benchmark 3: git -c core.commitGraph=false rev-list --topo-order --all (git = master)
  Time (mean ± σ):     13.841 s ±  0.084 s    [User: 13.152 s, System: 0.687 s]
  Range (min … max):   13.714 s … 13.995 s    10 runs
Benchmark 4: git -c core.commitGraph=false rev-list --topo-order --all (git = pks-commit-graph-inconsistency)
  Time (mean ± σ):     13.762 s ±  0.116 s    [User: 13.094 s, System: 0.667 s]
  Range (min … max):   13.645 s … 14.038 s    10 runs
Summary
  git -c core.commitGraph=true rev-list --topo-order --all (git = master) ran
    1.32 ± 0.02 times faster than git -c core.commitGraph=true rev-list --topo-order --all (git = pks-commit-graph-inconsistency)
    4.72 ± 0.05 times faster than git -c core.commitGraph=false rev-list --topo-order --all (git = pks-commit-graph-inconsistency)
    4.75 ± 0.04 times faster than git -c core.commitGraph=false rev-list --topo-order --all (git = master)

The added check does lead to a performance regression indeed, which is not all that unexpected. That being said, the commit-graph still results in a significant speedup compared to the case where we don't have it.

Show 7 quoted lines
> Another is that by the time parse_commit_in_graph() returns true and
> we realize that the answer we got is bogus by asking has_object(),
> item->object.parsed has already been toggled on, so the caller now
> has a commit object that claimed it was already parsed and does not
> match reality.  Hopefully the caller takes an early exit upon seeing
> a failure from parse_commit_gently() and the .parsed bit does not
> matter, but maybe I am missing a case where it does.  I dunno.

We could also call `unparse_commit()` when we notice the stale commit graph item. This would be in the same spirit as the rest of this patch as it would lead to an overall safer end state.

In any case I'll wait for additional input before sending a v2, most importantly to see whether we think that consistency trumps performance in this case. Personally I'm still of the mind that it should, which also comes from the fact that we were fighting with stale commit graphs several times in production data.

Patrick
Show 36 quoted lines
> Other than that, sounds very sensible and the code change is clean.
> 
> Will queue.  Thanks.
> 
> > diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
> > index ba65f17dd9..25f8e9e2d3 100755
> > --- a/t/t5318-commit-graph.sh
> > +++ b/t/t5318-commit-graph.sh
> > @@ -821,4 +821,27 @@ test_expect_success 'overflow during generation version upgrade' '
> >  	)
> >  '
> >  
> > +test_expect_success 'commit exists in commit-graph but not in object database' '
> > +	test_when_finished "rm -rf repo" &&
> > +	git init repo &&
> > +	(
> > +		cd repo &&
> > +
> > +		test_commit A &&
> > +		test_commit B &&
> > +		test_commit C &&
> > +		git commit-graph write --reachable &&
> > +
> > +		# Corrupt the repository by deleting the intermittent commit
> > +		# object. Commands should notice that this object is absent and
> > +		# thus that the repository is corrupt even if the commit graph
> > +		# exists.
> > +		oid=$(git rev-parse B) &&
> > +		rm .git/objects/"$(test_oid_to_path "$oid")" &&
> > +
> > +		test_must_fail git rev-parse HEAD~2 2>error &&
> > +		grep "error: commit $oid exists in commit-graph but not in the object database" error
> > +	)
> > +'
> > +
> >  test_done
Previous: Junio C HamanoNext: Junio C Hamano
Message 18 of 61 in “rev-list: add support for commits in `--missing`”
  1. 0/3 rev-list: add support for commits in `--missing`Karthik Nayak, Oct 9, 2023
  2. 1/3 revision: rename bit to `do_not_die_on_missing_objects`Karthik Nayak, Oct 9, 2023
  3. 3/3 rev-list: add commit object support in `--missing` optionKarthik Nayak, Oct 9, 2023
  4. 2/3 rev-list: move `show_commit()` to the bottomKarthik Nayak, Oct 9, 2023
  5. Junio C HamanoOct 9, 2023
  6. Patrick SteinhardtOct 10, 2023
  7. Junio C HamanoOct 10, 2023
  8. Karthik NayakOct 11, 2023
  9. Junio C HamanoOct 11, 2023
  10. Karthik NayakOct 12, 2023
  11. Patrick SteinhardtOct 12, 2023
  12. Karthik NayakOct 12, 2023
  13. Junio C HamanoOct 12, 2023
  14. Patrick SteinhardtOct 13, 2023
  15. Patrick SteinhardtOct 13, 2023
  16. commit: detect commits that exist in commit-graph but not in the ODBPatrick Steinhardt, Oct 13, 2023
  17. Junio C HamanoOct 13, 2023
  18. Patrick SteinhardtOct 17, 2023
  19. Junio C HamanoOct 17, 2023
  20. Patrick SteinhardtOct 19, 2023
  21. Patrick SteinhardtOct 19, 2023
  22. Junio C HamanoOct 19, 2023
  23. Jeff KingOct 20, 2023
  24. Junio C HamanoOct 20, 2023
  25. Patrick SteinhardtOct 23, 2023
  26. Junio C HamanoOct 13, 2023
  27. Junio C HamanoOct 12, 2023
  28. 0/3 rev-list: add support for commits in `--missing`Karthik Nayak, Oct 16, 2023
  29. 1/3 revision: rename bit to `do_not_die_on_missing_objects`Karthik Nayak, Oct 16, 2023
  30. 2/3 rev-list: move `show_commit()` to the bottomKarthik Nayak, Oct 16, 2023
  31. 3/3 rev-list: add commit object support in `--missing` optionKarthik Nayak, Oct 16, 2023
  32. Junio C HamanoOct 16, 2023
  33. Karthik NayakOct 16, 2023
  34. Junio C HamanoOct 16, 2023
  35. 0/3 rev-list: add support for commits in `--missing`Karthik Nayak, Oct 19, 2023
  36. 1/3 revision: rename bit to `do_not_die_on_missing_objects`Karthik Nayak, Oct 19, 2023
  37. 2/3 rev-list: move `show_commit()` to the bottomKarthik Nayak, Oct 19, 2023
  38. 3/3 rev-list: add commit object support in `--missing` optionKarthik Nayak, Oct 19, 2023
  39. Junio C HamanoOct 19, 2023
  40. Junio C HamanoOct 19, 2023
  41. Karthik NayakOct 20, 2023
  42. Karthik NayakOct 20, 2023
  43. Junio C HamanoOct 20, 2023
  44. Junio C HamanoOct 20, 2023
  45. Karthik NayakOct 24, 2023
  46. 0/3 rev-list: add support for commits in `--missing`Karthik Nayak, Oct 24, 2023
  47. 1/3 revision: rename bit to `do_not_die_on_missing_objects`Karthik Nayak, Oct 24, 2023
  48. 2/3 rev-list: move `show_commit()` to the bottomKarthik Nayak, Oct 24, 2023
  49. 3/3 rev-list: add commit object support in `--missing` optionKarthik Nayak, Oct 24, 2023
  50. Junio C HamanoOct 24, 2023
  51. Junio C HamanoOct 25, 2023
  52. Karthik NayakOct 25, 2023
  53. Patrick SteinhardtOct 25, 2023
  54. Junio C HamanoOct 26, 2023
  55. 0/3 rev-list: add support for commits in `--missing`Karthik Nayak, Oct 26, 2023
  56. 1/3 revision: rename bit to `do_not_die_on_missing_objects`Karthik Nayak, Oct 26, 2023
  57. 2/3 rev-list: move `show_commit()` to the bottomKarthik Nayak, Oct 26, 2023
  58. 3/3 rev-list: add commit object support in `--missing` optionKarthik Nayak, Oct 26, 2023
  59. Patrick SteinhardtOct 27, 2023
  60. Karthik NayakOct 27, 2023
  61. 3/3 rev-list: add commit object support in `--missing` optionKarthik Nayak, Oct 27, 2023

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.