From 023633a2747baf47221b03381805def6e4c9bd1f Mon Sep 17 00:00:00 2001 From: Andrew Kane Date: Mon, 25 Mar 2024 19:02:37 -0700 Subject: [PATCH] Added test for build recall --- test/t/020_hnsw_bit_build_recall.pl | 135 ++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 test/t/020_hnsw_bit_build_recall.pl diff --git a/test/t/020_hnsw_bit_build_recall.pl b/test/t/020_hnsw_bit_build_recall.pl new file mode 100644 index 0000000..2be0b9d --- /dev/null +++ b/test/t/020_hnsw_bit_build_recall.pl @@ -0,0 +1,135 @@ +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More; + +my $node; +my @queries = (); +my @expected; +my $limit = 20; +my $dim = 52; +my $max = 2**$dim; + +sub test_recall +{ + my ($min, $operator) = @_; + my $correct = 0; + my $total = 0; + + my $explain = $node->safe_psql("postgres", qq( + SET enable_seqscan = off; + EXPLAIN ANALYZE SELECT i FROM tst ORDER BY v $operator $queries[0] LIMIT $limit; + )); + like($explain, qr/Index Scan/); + + for my $i (0 .. $#queries) + { + my $actual = $node->safe_psql("postgres", qq( + SET enable_seqscan = off; + SELECT i FROM tst ORDER BY v $operator $queries[$i] LIMIT $limit; + )); + my @actual_ids = split("\n", $actual); + + my @expected_ids = split("\n", $expected[$i]); + my %expected_set = map { $_ => 1 } @expected_ids; + + foreach (@actual_ids) + { + if (exists($expected_set{$_})) + { + $correct++; + } + } + + $total += $limit; + } + + cmp_ok($correct / $total, ">=", $min, $operator); +} + +# Initialize node +$node = get_new_node('node'); +$node->init; +$node->start; + +# Create table +$node->safe_psql("postgres", "CREATE EXTENSION vector;"); +$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v bit($dim));"); +$node->safe_psql("postgres", + "INSERT INTO tst SELECT i, (random() * $max)::bigint::bit($dim) FROM generate_series(1, 10000) i;" +); + +# Generate queries +for (1 .. 20) +{ + my $r = int(rand() * $max); + push(@queries, "${r}::bit($dim)"); +} + +# Check each index type +my @operators = ("<~>"); +my @opclasses = ("bit_hamming_ops"); + +for my $i (0 .. $#operators) +{ + my $operator = $operators[$i]; + my $opclass = $opclasses[$i]; + + # Get exact results + @expected = (); + foreach (@queries) + { + # Handle ties + my $res = $node->safe_psql("postgres", qq( + WITH top AS ( + SELECT v $operator $_ AS distance FROM tst ORDER BY v $operator $_ LIMIT $limit + ) + SELECT i FROM tst WHERE (v $operator $_) <= (SELECT MAX(distance) FROM top) + )); + push(@expected, $res); + } + + # Build index serially + $node->safe_psql("postgres", qq( + SET max_parallel_maintenance_workers = 0; + CREATE INDEX idx ON tst USING hnsw (v $opclass); + )); + + # Test approximate results + my $min = 0.94; + test_recall($min, $operator); + + $node->safe_psql("postgres", "DROP INDEX idx;"); + + # Build index in parallel in memory + my ($ret, $stdout, $stderr) = $node->psql("postgres", qq( + SET client_min_messages = DEBUG; + SET min_parallel_table_scan_size = 1; + CREATE INDEX idx ON tst USING hnsw (v $opclass); + )); + is($ret, 0, $stderr); + like($stderr, qr/using \d+ parallel workers/); + + # Test approximate results + test_recall($min, $operator); + + $node->safe_psql("postgres", "DROP INDEX idx;"); + + # Build index in parallel on disk + # Set parallel_workers on table to use workers with low maintenance_work_mem + ($ret, $stdout, $stderr) = $node->psql("postgres", qq( + ALTER TABLE tst SET (parallel_workers = 2); + SET client_min_messages = DEBUG; + SET maintenance_work_mem = '4MB'; + CREATE INDEX idx ON tst USING hnsw (v $opclass); + ALTER TABLE tst RESET (parallel_workers); + )); + is($ret, 0, $stderr); + like($stderr, qr/using \d+ parallel workers/); + like($stderr, qr/hnsw graph no longer fits into maintenance_work_mem/); + + $node->safe_psql("postgres", "DROP INDEX idx;"); +} + +done_testing();