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

[PATCH v6 03/16] commit-graph: ensure Bloom filters are read with consistent settings

From
Taylor Blau <me@ttaylorr.com>
Date
Jan 31, 2024, 22:52 UTC
Message-ID
<a77ab941bc8867a2a40e02a0f9be4194681aa6e1.1706741516.git.me@ttaylorr.com>
In-Reply-To
<cover.1706741516.git.me@ttaylorr.com>

The changed-path Bloom filter mechanism is parameterized by a couple of variables, notably the number of bits per hash (typically "m" in Bloom filter literature) and the number of hashes themselves (typically "k").

It is critically important that filters are read with the Bloom filter settings that they were written with. Failing to do so would mean that each query is liable to compute different fingerprints, meaning that the filter itself could return a false negative. This goes against a basic assumption of using Bloom filters (that they may return false positives, but never false negatives) and can lead to incorrect results.

We have some existing logic to carry forward existing Bloom filter settings from one layer to the next. In `write_commit_graph()`, we have something like:

    if (!(flags & COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS)) {
        struct commit_graph *g = ctx->r->objects->commit_graph;
        /* We have changed-paths already. Keep them in the next graph */
        if (g && g->chunk_bloom_data) {
            ctx->changed_paths = 1;
            ctx->bloom_settings = g->bloom_filter_settings;
        }
    }
, which drags forward Bloom filter settings across adjacent layers.

This doesn't quite address all cases, however, since it is possible for intermediate layers to contain no Bloom filters at all. For example, suppose we have two layers in a commit-graph chain, say, {G1, G2}. If G1 contains Bloom filters, but G2 doesn't, a new G3 (whose base graph is G2) may be written with arbitrary Bloom filter settings, because we only check the immediately adjacent layer's settings for compatibility.

This behavior has existed since the introduction of changed-path Bloom filters. But in practice, this is not such a big deal, since the only way up until this point to modify the Bloom filter settings at write time is with the undocumented environment variables:

  - GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY
  - GIT_TEST_BLOOM_SETTINGS_NUM_HASHES
  - GIT_TEST_BLOOM_SETTINGS_MAX_CHANGED_PATHS

(it is still possible to tweak MAX_CHANGED_PATHS between layers, but this does not affect reads, so is allowed to differ across multiple graph layers).

But in future commits, we will introduce another parameter to change the hash algorithm used to compute Bloom fingerprints itself. This will be exposed via a configuration setting, making this foot-gun easier to use.

To prevent this potential issue, validate that all layers of a split commit-graph have compatible settings with the newest layer which contains Bloom filters.

Reported-by: SZEDER Gábor <szeder.dev@gmail.com>
Original-test-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Taylor Blau <me@ttaylorr.com>
---
 commit-graph.c       | 25 +++++++++++++++++
 t/t4216-log-bloom.sh | 65 +++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 89 insertions(+), 1 deletion(-)
diff --git a/commit-graph.c b/commit-graph.c
index 45417d7412..216cf03da1 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -543,6 +543,30 @@ static int validate_mixed_generation_chain(struct commit_graph *g)
 	return 0;
 }
 
+static void validate_mixed_bloom_settings(struct commit_graph *g)
+{
+	struct bloom_filter_settings *settings = NULL;
+	for (; g; g = g->base_graph) {
+		if (!g->bloom_filter_settings)
+			continue;
+		if (!settings) {
+			settings = g->bloom_filter_settings;
+			continue;
+		}
+
+		if (g->bloom_filter_settings->bits_per_entry != settings->bits_per_entry ||
+		    g->bloom_filter_settings->num_hashes != settings->num_hashes) {
+			g->chunk_bloom_indexes = NULL;
+			g->chunk_bloom_data = NULL;
+			FREE_AND_NULL(g->bloom_filter_settings);
+
+			warning(_("disabling Bloom filters for commit-graph "
+				  "layer '%s' due to incompatible settings"),
+				oid_to_hex(&g->oid));
+		}
+	}
+}
+
 static int add_graph_to_chain(struct commit_graph *g,
 			      struct commit_graph *chain,
 			      struct object_id *oids,
@@ -666,6 +690,7 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
 	}
 
 	validate_mixed_generation_chain(graph_chain);
+	validate_mixed_bloom_settings(graph_chain);
 
 	free(oids);
 	fclose(fp);
diff --git a/t/t4216-log-bloom.sh b/t/t4216-log-bloom.sh
index cc6ebc8140..64737b6ee9 100755
--- a/t/t4216-log-bloom.sh
+++ b/t/t4216-log-bloom.sh
@@ -421,8 +421,71 @@ test_expect_success 'Bloom generation backfills empty commits' '
 	)
 '
 
+graph=.git/objects/info/commit-graph
+graphdir=.git/objects/info/commit-graphs
+chain=$graphdir/commit-graph-chain
+
+test_expect_success 'setup for mixed Bloom setting tests' '
+	repo=mixed-bloom-settings &&
+
+	git init $repo &&
+	for i in one two three
+	do
+		test_commit -C $repo $i file || return 1
+	done
+'
+
+test_expect_success 'ensure Bloom filters with incompatible settings are ignored' '
+	# Compute Bloom filters with "unusual" settings.
+	git -C $repo rev-parse one >in &&
+	GIT_TEST_BLOOM_SETTINGS_NUM_HASHES=3 git -C $repo commit-graph write \
+		--stdin-commits --changed-paths --split <in &&
+	layer=$(head -n 1 $repo/$chain) &&
+
+	# A commit-graph layer without Bloom filters "hides" the layers
+	# below ...
+	git -C $repo rev-parse two >in &&
+	git -C $repo commit-graph write --stdin-commits --no-changed-paths \
+		--split=no-merge <in &&
+
+	# Another commit-graph layer that has Bloom filters, but with
+	# standard settings, and is thus incompatible with the base
+	# layer written above.
+	git -C $repo rev-parse HEAD >in &&
+	git -C $repo commit-graph write --stdin-commits --changed-paths \
+		--split=no-merge <in &&
+
+	test_line_count = 3 $repo/$chain &&
+
+	# Ensure that incompatible Bloom filters are ignored.
+	git -C $repo -c core.commitGraph=false log --oneline --no-decorate -- file \
+		>expect 2>err &&
+	git -C $repo log --oneline --no-decorate -- file >actual 2>err &&
+	test_cmp expect actual &&
+	grep "disabling Bloom filters for commit-graph layer .$layer." err
+'
+
+test_expect_success 'merge graph layers with incompatible Bloom settings' '
+	# Ensure that incompatible Bloom filters are ignored when
+	# merging existing layers.
+	git -C $repo commit-graph write --reachable --changed-paths 2>err &&
+	grep "disabling Bloom filters for commit-graph layer .$layer." err &&
+
+	test_path_is_file $repo/$graph &&
+	test_dir_is_empty $repo/$graphdir &&
+
+	git -C $repo -c core.commitGraph=false log --oneline --no-decorate -- \
+		file >expect &&
+	trace_out="$(pwd)/trace.perf" &&
+	GIT_TRACE2_PERF="$trace_out" \
+		git -C $repo log --oneline --no-decorate -- file >actual 2>err &&
+
+	test_cmp expect actual &&
+	grep "statistics:{\"filter_not_present\":0," trace.perf &&
+	test_must_be_empty err
+'
+
 corrupt_graph () {
-	graph=.git/objects/info/commit-graph &&
 	test_when_finished "rm -rf $graph" &&
 	git commit-graph write --reachable --changed-paths &&
 	corrupt_chunk_file $graph "$@"
-- 
2.43.0.509.g253f65a7fc
Previous: Taylor BlauNext: Taylor Blau
Message 4 of 36 in “bloom: changed-path Bloom filters v2 (& sundries)”
  1. 00/16 bloom: changed-path Bloom filters v2 (& sundries)Taylor Blau, Jan 31, 2024
  2. 01/16 t/t4216-log-bloom.sh: harden `test_bloom_filters_not_used()`Taylor Blau, Jan 31, 2024
  3. 02/16 revision.c: consult Bloom filters for root commitsTaylor Blau, Jan 31, 2024
  4. 03/16 commit-graph: ensure Bloom filters are read with consistent settingsTaylor Blau, Jan 31, 2024
  5. 04/16 gitformat-commit-graph: describe version 2 of BDATTaylor Blau, Jan 31, 2024
  6. 05/16 t/helper/test-read-graph.c: extract `dump_graph_info()`Taylor Blau, Jan 31, 2024
  7. 06/16 bloom.h: make `load_bloom_filter_from_graph()` publicTaylor Blau, Jan 31, 2024
  8. 07/16 t/helper/test-read-graph: implement `bloom-filters` modeTaylor Blau, Jan 31, 2024
  9. 08/16 t4216: test changed path filters with high bit pathsTaylor Blau, Jan 31, 2024
  10. 09/16 repo-settings: introduce commitgraph.changedPathsVersionTaylor Blau, Jan 31, 2024
  11. 10/16 bloom: annotate filters with hash versionTaylor Blau, Jan 31, 2024
  12. 11/16 bloom: prepare to discard incompatible Bloom filtersTaylor Blau, Jan 31, 2024
  13. 12/16 commit-graph: unconditionally load Bloom filtersTaylor Blau, Jan 31, 2024
  14. 13/16 commit-graph: new Bloom filter version that fixes murmur3Taylor Blau, Jan 31, 2024
  15. SZEDER GáborFeb 25, 2024
  16. Jiang XinFeb 28, 2024
  17. 14/16 object.h: fix mis-aligned flag bits tableTaylor Blau, Jan 31, 2024
  18. 15/16 commit-graph: reuse existing Bloom filters where possibleTaylor Blau, Jan 31, 2024
  19. 16/16 bloom: introduce `deinit_bloom_filters()`Taylor Blau, Jan 31, 2024
  20. 00/16 bloom: changed-path Bloom filters v2 (& sundries)Taylor Blau, Jun 25, 2024
  21. 01/16 t/t4216-log-bloom.sh: harden `test_bloom_filters_not_used()`Taylor Blau, Jun 25, 2024
  22. 02/16 revision.c: consult Bloom filters for root commitsTaylor Blau, Jun 25, 2024
  23. 03/16 commit-graph: ensure Bloom filters are read with consistent settingsTaylor Blau, Jun 25, 2024
  24. 04/16 gitformat-commit-graph: describe version 2 of BDATTaylor Blau, Jun 25, 2024
  25. 05/16 t/helper/test-read-graph.c: extract `dump_graph_info()`Taylor Blau, Jun 25, 2024
  26. 06/16 bloom.h: make `load_bloom_filter_from_graph()` publicTaylor Blau, Jun 25, 2024
  27. 07/16 t/helper/test-read-graph: implement `bloom-filters` modeTaylor Blau, Jun 25, 2024
  28. 08/16 t4216: test changed path filters with high bit pathsTaylor Blau, Jun 25, 2024
  29. 09/16 repo-settings: introduce commitgraph.changedPathsVersionTaylor Blau, Jun 25, 2024
  30. 10/16 bloom: annotate filters with hash versionTaylor Blau, Jun 25, 2024
  31. 11/16 bloom: prepare to discard incompatible Bloom filtersTaylor Blau, Jun 25, 2024
  32. 12/16 commit-graph: unconditionally load Bloom filtersTaylor Blau, Jun 25, 2024
  33. 13/16 commit-graph: new Bloom filter version that fixes murmur3Taylor Blau, Jun 25, 2024
  34. 14/16 object.h: fix mis-aligned flag bits tableTaylor Blau, Jun 25, 2024
  35. 15/16 commit-graph: reuse existing Bloom filters where possibleTaylor Blau, Jun 25, 2024
  36. 16/16 bloom: introduce `deinit_bloom_filters()`Taylor Blau, Jun 25, 2024

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.