1
0
mirror of https://github.com/MariaDB/server.git synced 2025-09-16 16:42:28 +03:00
Commit Graph

67 Commits

Author SHA1 Message Date
Sergey Vojtovich
02eb8f7246 MDL_lock encapsulation: MDL_lock::get_key()
Avoid accessing MDL_lock::key from outside of MDL_lock class directly,
use MDL_lock::get_key() instead.

This is part of broader cleanup, which aims to make large part of
MDL_lock members private. It is needed to simplify further work on
MDEV-19749 - MDL scalability regression after backup locks.
2025-07-15 23:19:06 +04:00
Sergei Golubchik
82867e07e3 MDEV-35897 vector index search allocates too much memory for large ef_search
never estimate that a graph search will visit more nodes than there
are in the graph. In fact, let's reduce the graph size by 30%, it'll
increase the false positive rate of a bloom filter by 2% when
visiting the whole graph, it doesn't affect recall noticeably.

we need to read the shared graph size under a lock. let's store it
in the thread-local unused TABLE::used_stat_records member.
2025-04-25 16:00:37 +02:00
Sergei Golubchik
1691b0cfac cleanup: mhnsw, fix vector length when cosine
formulas use half the distance, so the correct normalized
length is 0.5.

This change doesn't affect search results.
2025-04-18 09:41:24 +02:00
Sergei Golubchik
7db60533c7 MDEV-36188 assert with vector index and very long PK
limit PK length if the table has a vector index
2025-04-18 09:41:23 +02:00
Manjul Mohan
6bb92f98ce MDEV-36184 - mhnsw: support powerpc64 SIMD instructions
This patch optimises the dot_product function by leveraging
vectorisation through SIMD intrinsics. This transformation enables
parallel execution of multiple operations, significantly improving the
performance of dot product computation on supported architectures.

The original dot_product function does undergo auto-vectorisation when
compiled with -O3. However, performance analysis has shown that the
newly optimised implementation performs better on Power10 and achieves
comparable performance on Power9 machines.

Benchmark tests were conducted on both Power9 and Power10 machines,
comparing the time taken by the original (auto-vectorized) code and the
new vectorised code. GCC 11.5.0 on RHEL 9.5 operating system with -O3
were used. The benchmarks were performed using a sample test code with
a vector size of 4096 and 10⁷ loop iterations. Here are the average
execution times (in seconds) over multiple runs:

Power9:
Before change: ~16.364 s
After change: ~16.180 s
Performance gain is modest but measurable.

Power10:
Before change: ~8.989 s
After change: ~6.446 s
Significant improvement, roughly 28–30% faster.

Signed-off-by: Manjul Mohan <manjul.mohan@ibm.com>
2025-04-14 18:01:16 +02:00
Sergei Golubchik
9ee09a33bb Merge branch '11.7' into 11.8 2025-02-11 20:29:43 +01:00
Sergei Golubchik
c453391187 MDEV-35834 Server crash in FVector::distance_to upon concurrent SELECT
a start node in the shared context must be atomically assigned
to a valid and fully initialized node otherwise a concurrent
thread might use it before it's safe
2025-02-06 21:47:01 +01:00
Sergei Golubchik
528249a20a cleanup: one Item_func_vec_distance class, not three
prepare for MDEV-35450 VEC_DISTANCE auto-detection
2025-01-21 12:18:56 +01:00
Sergey Vojtovich
11a6c1b30a MDEV-34699 - mhnsw: support aarch64 SIMD instructions
SIMD implementations of bloom filters and dot product calculation.

A microbenchmark shows 1.7x dot product performance improvement compared to
regular -O2/-O3 builds and 2.4x compared to builds with auto-vectorization
disabled.

Performance improvement (microbenchmark) for bloom filters is less exciting,
within 10-30% ballpark depending on compiler options and load.

Misc implementation notes:
CalcHash: no _mm256_shuffle_epi8(), use explicit XOR/shift.
CalcHash: no 64bit multiplication, do scalar multiplication.
ConstructMask/Query: no _mm256_i64gather_epi64, access array elements explicitly.
Query: no _mm256_movemask_epi8, accumulate bits manually.

Closes #3671
2025-01-17 22:56:51 +01:00
Sergei Golubchik
8ef37ade17 MDEV-35745 Assertion failure, ASAN errors, crash in mhnsw_read_first/mhnsw_read_next
If the table has many deleted nodes, they can overflow
`candidates` even when `best` isn't full yet
2025-01-13 19:57:12 +01:00
Marko Mäkelä
33907f9ec6 Merge 11.4 into 11.7 2024-12-02 17:51:17 +02:00
Sergei Golubchik
74743b0d88 fix test failures on x86, gcc -O1
x86 builds don't use SIMD, fast math and inlining causes
distances to be quite unstable and

1) comparison with the threshold no longer works, the distance calculated
   twice between the same two vectors comes out differently

2) a bunch of identical vectors get the non-zero distance between
   them and HNSW cross-links them with no outbound links (if there're
   more than 2M identical vectors). Let's strengthen the select_neighbors
   heuristic to skip neighbors that are too close to each other

MDEV-35418 suggests a better solution for this.
2024-11-14 11:32:08 +01:00
Sergei Golubchik
ebbbe9d960 MDEV-35319 ER_LOCK_DEADLOCK not detected upon DML on table with vector key, server crashes
cannot ignore the error in MHNSW_Share::acquire() - it could be a deadlock
signal, after which no further operations are allowed
2024-11-05 14:00:52 -08:00
Sergei Golubchik
e5a5d2b78d MDEV-35214 Server crashes in FVectorNode::gref_len with insufficient mhnsw_max_cache_size
now with streaming (MDEV-35032) we cannot longer free MHNSW_Trx
at the end of the search. Cannot even free it at the end of the
mhnsw_insert, because there can be a search running (INSERT ... SELECT).

Let's do reference counting, even though it's a thread-local object.
2024-11-05 14:00:52 -08:00
Sergei Golubchik
b09c8b03d7 MDEV-35244 Vector-related system variables could use better names
considering that users don't interact with MariaDB vector search directly,
but primarily use AI frameworks, we should use names familiar
to vector store connector writers and for AI framework users.
That is industry standard M and ef.

mhnsw_cache_size -> mhnsw_max_cache_size
mhnsw_distance_function -> mhnsw_default_distance
mhnsw_max_edges_per_node -> mhnsw_default_m
mhnsw_min_limit -> mhnsw_ef_search

inside CREATE TABLE:
max_edges_per_node -> m
distance_function -> distance
2024-11-05 14:00:52 -08:00
Sergei Golubchik
0b9bc6c3cd MDEV-35246 Vector search skips a row in the table
stronger condition in select_neighbors() to reject exact matches too
2024-11-05 14:00:52 -08:00
Sergei Golubchik
7d081c1b83 MDEV-35223 REPAIR does not fix MyISAM table with vector key after crash recovery
resort to alter for repair too
2024-11-05 14:00:52 -08:00
Sergei Golubchik
926b339b93 MDEV-35194 non-BNL join fails on assertion
with streaming implemened mhnsw no longer needs to know
the LIMIT in advance. let's just cap it to avoid allocating
too much memory for the one step result set
2024-11-05 14:00:52 -08:00
Sergei Golubchik
597e34d000 MDEV-35213 Server crash or assertion failure upon query with high value of mhnsw_min_limit
mhnsw_min_limit must not be larger than candidates queue size
2024-11-05 14:00:52 -08:00
Sergei Golubchik
78119d1ae5 MDEV-33410 VECTOR data type 2024-11-05 14:00:51 -08:00
Sergei Golubchik
eb4ab2ce8f MDEV-35061 XA PREPARE "not supported by the engine" from storage engine mhnsw, memory leak
disallow explicit XA PREPARE over mhnsw indexes
2024-11-05 14:00:51 -08:00
Sergei Golubchik
09cd817f5d MDEV-35060 Assertion failure upon DML on table with vector under lock 2024-11-05 14:00:51 -08:00
Sergei Golubchik
09889d417b MDEV-35055 ASAN errors in TABLE_SHARE::lock_share upon committing transaction after FLUSH on table with vector key
MHNSW_Trx cannot store a pointer to the TABLE_SHARE for the duration
of a transaction, because the share can be evicted from the cache any
time.

Use a pointer to the MDL_ticket instead, it is stored in the THD
and has a duration of MDL_TRANSACTION, so won't go away.

When we need a TABLE_SHARE - on commit - get a new one from tdc.
Normally, it'll be already in the cache, so it'd be fast.
We don't optimize for the edge case when TABLE_SHARE was evicted.
2024-11-05 14:00:51 -08:00
Sergei Golubchik
9f80e3fbb7 MDEV-35032 streaming mode for mhnsw search
support SQL semantics for SELECT ... WHERE ... ORDER BY ... LIMIT

* switch from returning k nearest neighbors to returning
  as many as needed, in k-neighbor chunks, with increasing distance
* make search_layer() skips nodes that are closer than a threshold
* read_next keeps a search context - list of k found nodes,
  threshold, ctx, etc.
* when the list of found nodes is exhausted, it repeats the search
  starting from last found nodes and a threshold
* search context kepts ctx->refcount incremented, so ctx won't go away
* but commit_lock is unlocked between calls, so InnoDB can modify the table
* use ctx version to detect that, switch to MHNSW_Trx when it happens

bugfix:
* use the correct lock in ha_external_lock() for the graph table
* InnoDB didn't reset locks on ha_external_lock(F_UNLCK) and previous
  LOCK_X leaked into the next statement
2024-11-05 14:00:51 -08:00
Sergei Golubchik
b4811a9b63 cleanup: simplify search_layer() usage, remove std::swap() 2024-11-05 14:00:51 -08:00
Sergei Golubchik
7b00e2351b rename MHNSW_Context->MHNSW_Share 2024-11-05 14:00:51 -08:00
Sergei Golubchik
a6499049af MDEV-35033 LeakSanitizer errors in my_malloc / safe_mutex_lazy_init_deadlock_detection / MHNSW_Context::alloc_node and alike
ctx wasn't released on errors
2024-11-05 14:00:51 -08:00
Sergei Golubchik
3c6e836110 generous_furthest optimization
make generosity depend on

1. M. Keep small M's fast, increase generosity for larger M's to get
   better recall.

2. distance. Keep generosity small when vectors are far from the
   target, increase generosity when the search gets closer. This
   allows to examine more relevant vectors but doesn't waste time
   examining irrelevant vectors. Particularly important with cosine
   metric when the distance is bounded
2024-11-05 14:00:50 -08:00
Sergei Golubchik
97b2392ede cleanup: TABLE_SHARE::lock_share() helper
also: renames, s/const/constexpr/ for consistency
2024-11-05 14:00:50 -08:00
Sergey Vojtovich
a90fa3f397 ALTER TABLE fixes for high-level indexes (i)
Fixes for ALTER TABLE ... ADD/DROP COLUMN, ALGORITHM=COPY.

Let quick_rm_table() remove high-level indexes along with original table.

Avoid locking uninitialized LOCK_share for INTERNAL_TMP_TABLEs.

Don't enable bulk insert when altering a table containing vector index.
InnoDB can't handle situation when bulk insert is enabled for one table
but disabled for another. We can't do bulk insert on vector index as it
does table updates currently.
2024-11-05 14:00:50 -08:00
Sergei Golubchik
e826875fe5 AVX-512 support 2024-11-05 14:00:50 -08:00
Sergei Golubchik
2ad9df8c9b VEC_Distance_Cosine() 2024-11-05 14:00:50 -08:00
Sergei Golubchik
2e1fcc6a80 rename VEC_Distance to VEC_Distance_Euclidean
and create a parent Item_func_vec_distance_common class
2024-11-05 14:00:50 -08:00
Sergei Golubchik
0da820cb12 mhnsw: use plugin index options and transaction_participant API 2024-11-05 14:00:50 -08:00
Sergei Golubchik
aed5928207 cleanup: extract transaction-related part of handlerton
into a separate transaction_participant structure

handlerton inherits it, so handlerton itself doesn't change.
but entities that only need to participate in a transaction,
like binlog or online alter log, use a transaction_participant
and no longer need to pretend to be a full-blown but invisible
storage engine which doesn't support create table.
2024-11-05 14:00:50 -08:00
Sergei Golubchik
ebcbed6d74 post-fixes for TRUNCATE
* fix the truncate-by-handler variant, used by InnoDB
* test that insert works after truncate, meaning graph table was emptied
* test that the vector index size is zero after truncate in MyISAM
2024-11-05 14:00:49 -08:00
Sergei Golubchik
26e599cd32 mhnsw: make the search less greedy
introduced a generosity factor that makes the search less greedy.
it dramatically improves the recall by making the search a bit slower
(for the same recall one can use half the M and smaller ef).

had to add Queue::safe_push() method that removes one of the
furthest elements (not necessarily the furthest) in the queue
to keep it from overflowing.
2024-11-05 14:00:49 -08:00
Sergei Golubchik
885eb19823 cleanup search_layer()
to return only as many elements as needed, the caller no longer needs to
overallocate result arrays for throwaway nodes
2024-11-05 14:00:49 -08:00
Sergei Golubchik
fa2078ddff mhnsw: store coordinates in 16 bits, not 32
use int16_t instead of floats, they're faster and smaller.
but perform intermediate SIMD calculations with floats to avoid overflows.
recall drop with such scheme is below 0.002, often none.

int8_t would've been better but the precision loss is too big
and recall degrades too much.
2024-11-05 14:00:49 -08:00
Sergei Golubchik
f44989ff0f UPDATE/DELETE post-fixes 2024-11-05 14:00:49 -08:00
Hugo Wen
0e2b9e7621 MDEV-33408 Initial support for vector DELETE and UPDATE
When the source row is deleted, mark the corresponding node in HNSW
index by setting `tref` to null. An index is added for the `tref` in
secondary table for faster searching of the to-be-marked nodes.

The nodes marked as deleted will still be used for search, but will not
be included in the final query results.

As skipping deleted nodes and not adding deleted nodes for new-inserted
nodes' neighbor list could impact the performance, we now only skip
these nodes in search results.

- for some reason the bitmap is not set for hlindex during the delete so
  I had to temporarily comment out one line

All new code of the whole pull request, including one or several files
that are either new files or modified ones, are contributed under the
BSD-new license. I am contributing on behalf of my employer Amazon Web
Services, Inc.
2024-11-05 14:00:49 -08:00
Sergei Golubchik
173b017c06 non-SIMD fallback 2024-11-05 14:00:49 -08:00
Sergei Golubchik
049d839350 mhnsw: inter-statement shared cache
* preserve the graph in memory between statements
* keep it in a TABLE_SHARE, available for concurrent searches
* nodes are generally read-only, walking the graph doesn't change them
* distance to target is cached, calculated only once
* SIMD-optimized bloom filter detects visited nodes
* nodes are stored in an array, not List, to better utilize bloom filter
* auto-adjusting heuristic to estimate the number of visited nodes
  (to configure the bloom filter)
* many threads can concurrently walk the graph. MEM_ROOT and Hash_set
  are protected with a mutex, but walking doesn't need them
* up to 8 threads can concurrently load nodes into the cache,
  nodes are partitioned into 8 mutexes (8 is chosen arbitrarily, might
  need tuning)
* concurrent editing is not supported though
* this is fine for MyISAM, TL_WRITE protects the TABLE_SHARE and the
  graph (note that TL_WRITE_CONCURRENT_INSERT is not allowed, because an
  INSERT into the main table means multiple UPDATEs in the graph)
* InnoDB uses secondary transaction-level caches linked in a list in
  in thd->ha_data via a fake handlerton
* on rollback the secondary cache is discarded, on commit nodes
  from the secondary cache are invalidated in the shared cache
  while it is exclusively locked
* on savepoint rollback both caches are flushed. this can be improved
  in the future with a row visibility callback
* graph size is controlled by @@mhnsw_cache_size, the cache is flushed
  when it reaches the threshold
2024-11-05 14:00:49 -08:00
Sergei Golubchik
8eb39be512 mhnsw: change storage format
instead of one row per node per layer, have one row per node.
store all neighbors for all layers in that row, and the vector itself too

it completely avoids searches in the graph table and
will allow to implement deletions in the future
2024-11-05 14:00:49 -08:00
Sergei Golubchik
ca21d49042 mhnsw: return an error if lazy neighbor read failed 2024-11-05 14:00:49 -08:00
Sergei Golubchik
4657b63aa1 mhnsw: SIMD for euclidean distance 2024-11-05 14:00:49 -08:00
Sergei Golubchik
5c2b7c6e7f mhnsw: configurable parameters
1. introduce alpha. the value of 1.1 is optimal, so hard-code it.

2. hard-code ef_construction=10, best by test

3. rename hnsw_max_connection_per_layer to mhnsw_max_edges_per_node
   (max_connection is rather ambiguous in MariaDB) and add a help text

4. rename hnsw_ef_search to mhnsw_min_limit and add a help text
2024-11-05 14:00:49 -08:00
Sergei Golubchik
25b4000290 InnoDB support for hlindexes and mhnsw
* mhnsw:
  * use primary key, innodb loves and (and the index cannot have dupes anyway)
    * MyISAM is ok with that, performance-wise
  * must be ha_rnd_init(0) because we aren't going to scan
    * MyISAM resets the position on ha_rnd_init(0) so query it before
    * oh, and use the correct handler, just in case
  * HA_ERR_RECORD_IS_THE_SAME is no error
* innodb:
  * return ref_length on create
  * don't assume table->pos_in_table_list is set
  * ok, assume away, but only for system versioned tables
* set alter_info on create (InnoDB needs to check for FKs)
* pair external_lock/external_unlock correctly
2024-11-05 14:00:49 -08:00
Sergei Golubchik
2efd9b17ba mhnsw: cache start node too, don't push too much in pg_discard 2024-11-05 14:00:49 -08:00
Sergei Golubchik
613542dceb mhnsw: build indexes with the columns of exactly right size 2024-11-05 14:00:49 -08:00