PS-11203 Vector Index Syntax - #5987
Conversation
c055ddc to
bac48eb
Compare
bac48eb to
6f38434
Compare
a985587 to
96481cb
Compare
f0e62ee to
3c65f43
Compare
3c65f43 to
67722d7
Compare
67722d7 to
09426a6
Compare
Every line written to GCS_DEBUG_TRACE now carries an ISO 8601 UTC timestamp with microsecond precision at the front of the line: [YYYY-MM-DDTHH:MM:SS.uuuuuuZ] [MYSQL_GCS_DEBUG] [GCS] <message>
Every line written to GCS_DEBUG_TRACE now carries an ISO 8601 UTC timestamp with microsecond precision at the front of the line: [YYYY-MM-DDTHH:MM:SS.uuuuuuZ] [MYSQL_GCS_DEBUG] [GCS] <message>
09426a6 to
b22f12a
Compare
| eng "Vector index can only be created in tables with a BIGINT UNSIGNED primary key." | ||
|
|
||
| ER_ONLY_SINGLE_VECTOR_INDEX_ALLOWED | ||
| eng "A table can have at most one vector index." |
There was a problem hiding this comment.
why is this the case? I've seen examples where having two indexed vector columns make a lot of sense. Is this only for the MVP?
There was a problem hiding this comment.
Was not documented properly, but now is:
| inline constexpr const decltype(handlerton::flags) | ||
| HTON_SECONDARY_SUPPORTS_TEMPORARY_TABLE(1 << 25); | ||
|
|
||
|
|
There was a problem hiding this comment.
please revert these changes to the end of the file as they are not related to this commit
There was a problem hiding this comment.
Good catch. These were actually part of the underlying PR, so I'll update that one, too.
| ); | ||
| SHOW CREATE TABLE t1; | ||
| SHOW INDEXES FROM t1; | ||
|
|
There was a problem hiding this comment.
I think we don't have any test cases where you try creating a VECTOR index on a table without BIGINT UNSIGNED, also one where you create the table with such primary key but then you alter the table by droping it or perhaps change data type(though I think the second part of this idea should not be possible)
There was a problem hiding this comment.
The latter I have: https://github.com/percona/percona-server/pull/5987/changes#diff-5daf5ca2072c4bcd935fb51611868b7a7a550bd8b541804f20556541446216e0R18-R19
Added the rest.
b22f12a to
fa59cb0
Compare
| } | ||
| hnsw_param.M = std::atoi(p.value.str); | ||
| } else if (my_strcasecmp(system_charset_info, p.key.str, "metric") == 0) { | ||
| if (my_strcasecmp(system_charset_info, p.value.str, "euclidean") == 0) { |
There was a problem hiding this comment.
how will this tie into the distance functions? does it need to support all the metrics?
There was a problem hiding this comment.
I also wonder what visibility do we have from the optimizer if an index is defined on one metric and we query it using distance function using another metric. Shouldn't this be more "static" since there are only a few available options?
There was a problem hiding this comment.
Indeed. I changed the code to make it more obvious how to add new metrics. If you have a list of metrics already, feel free to drop them!
Not sure what you mean by static? You mean coded into the Bison parser?
| } | ||
| ; | ||
|
|
||
| index_construction_parameter: |
There was a problem hiding this comment.
I wonder if it's a good idea to expect IDENT_QUOTED instead of IDENT. As far as I know the former accepts non-ASCII bytes/multi-byte. This should be a simple config and I don't expect anything "exotic" here, it's easier to start with something restricted, and relax requirements later
There was a problem hiding this comment.
There's some ugly lexer hack around IDENT, I never managed to get it to work
mysql> CREATE TABLE t1 ( id BIGINT UNSIGNED PRIMARY KEY, v1 VECTOR( 1234 ), VECTOR KEY( v1 ) TYPE hnsw WITH ( m=a ) );
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'm=a ) )' at line 1
The rest of the string is eaten up by the IDENT above
There was a problem hiding this comment.
Changing to ident for now. That seems to be what people do
| assert((key_info->flags & flags_before_switch) == flags_before_switch); | ||
| if (key->generated) key_info->flags |= HA_GENERATED_KEY; | ||
|
|
||
| // Serialize vector index construction params (WITH clause). |
There was a problem hiding this comment.
CREATE TABLE t2 ( a INT, KEY k ( a ) TYPE hnsw WITH ( M = 6 ) ); should not be accepted
There was a problem hiding this comment.
Isn't now 😄
mysql> CREATE TABLE t2 ( a INT, KEY k ( a ) TYPE hnsw WITH ( M = 6 ) );
ERROR 7034 (HY000): A VECTOR index may only contain a vector type column.
Implement SQL DISTANCE(vector, vector, metric) and the VECTOR_DISTANCE() synonym for vector similarity queries. Supported metrics: EUCLIDEAN (L2), EUCLIDEAN_SQUARED, MANHATTAN (L1), COSINE, and DOT. Core library (vector-common/vector_distance.*): - Runtime SIMD dispatch across Scalar, SSE4.2/NEON, AVX2, AVX-512F, and SVE2 tiers; per-kernel target attributes, no global -march=native - Dim-aware wide/narrow dispatch (dims >= 16 use widest tier; smaller vectors use 128-bit tier to avoid AVX setup overhead) - Unaligned load intrinsics throughout: VECTOR data may be misaligned; on modern CPUs unaligned and aligned loads have identical throughput when data is aligned; aligned loads would fault on misaligned inputs without a performance benefit. Loads that span a 64-byte cache-line boundary may still be slower (two line fetches); that depends on runtime address, not on loadu vs load, and is not avoided by switching to aligned intrinsics - Float32 SIMD accumulation with double-precision horizontal sum and scalar tail: preserves correctness for large dimensions and extreme values (e.g. 2e38 Euclidean distance) without sacrificing SIMD width
https://perconadev.atlassian.net/browse/PS-10102 When component_keyring_kmip is loaded but keyring initialization fails (e.g. KMIP server not running or misconfigured), g_keyring_operations remains a null unique_ptr. Any subsequent call to a keyring service method (generate, store, remove, read, encrypt, decrypt, iterator) dereferences this null pointer, triggering an assertion in unique_ptr::operator*() and aborting the server. Add a null guard at the start of every service method that dereferences g_keyring_operations, returning true (error) immediately when the keyring has not been successfully initialized.
https://perconadev.atlassian.net/browse/PS-10102 Various fixes of component_keyring_kmip MTR tests to run successfully on PyKMIP and CosmianKMS servers. Some tests are excluded from parallel run. The CosmianKMS KMIP server now is main for testing, but PyKMIP server is still supported
Add shutdown protection on the level into the shared auth/plugin lifecycle so all authentication plugins are covered. - Add a global auth-plugin shutdown barrier API (`sql/auth/auth_plugin_shutdown.h` + implementation in `sql/auth/sql_authentication.cc`). - Guard auth plugin callback entry points with RAII operation tracking. - Start barrier shutdown/drain from `plugin_shutdown()` before plugin deinitialization (`sql/sql_plugin.cc`). - Remove plugin-local quiesce state/wait logic from `sql/auth/sha2_password.cc` now that teardown protection is centralized. - Bound auth-plugin shutdown barrier wait with a 2s timeout (`AUTH_PLUGIN_SHUTDOWN_TIMEOUT_MS`) to avoid indefinite shutdown hangs. - Keep normal shutdown flow progressing even when auth operations fail to drain in time. Regression coverage: - Add `percona.bug_ps11143_threadpool_auth_shutdown` to reproduce the threadpool auth-vs-shutdown race and verify clean shutdown. - Include expected result file and test cleanup to preserve MTR state. Verified: - percona.bug_ps11143_threadpool_auth_shutdown - percona.signal_handling_threadpool - percona.threadpool_stats - percona.kill_idle_trx_threadpool - percona.threadpool_debug
https://perconadev.atlassian.net/browse/PS-10102 When component_keyring_kmip is loaded but keyring initialization fails (e.g. KMIP server not running or misconfigured), g_keyring_operations remains a null unique_ptr. Any subsequent call to a keyring service method (generate, store, remove, read, encrypt, decrypt, iterator) dereferences this null pointer, triggering an assertion in unique_ptr::operator*() and aborting the server. Add a null guard at the start of every service method that dereferences g_keyring_operations, returning true (error) immediately when the keyring has not been successfully initialized.
https://perconadev.atlassian.net/browse/PS-10102 Various fixes of component_keyring_kmip MTR tests to run successfully on PyKMIP and CosmianKMS servers. Some tests are excluded from parallel run. The CosmianKMS KMIP server now is main for testing, but PyKMIP server is still supported
Add shutdown protection on the level into the shared auth/plugin lifecycle so all authentication plugins are covered. - Add a global auth-plugin shutdown barrier API (`sql/auth/auth_plugin_shutdown.h` + implementation in `sql/auth/sql_authentication.cc`). - Guard auth plugin callback entry points with RAII operation tracking. - Start barrier shutdown/drain from `plugin_shutdown()` before plugin deinitialization (`sql/sql_plugin.cc`). - Remove plugin-local quiesce state/wait logic from `sql/auth/sha2_password.cc` now that teardown protection is centralized. - Bound auth-plugin shutdown barrier wait with a 2s timeout (`AUTH_PLUGIN_SHUTDOWN_TIMEOUT_MS`) to avoid indefinite shutdown hangs. - Keep normal shutdown flow progressing even when auth operations fail to drain in time. Regression coverage: - Add `percona.bug_ps11143_threadpool_auth_shutdown` to reproduce the threadpool auth-vs-shutdown race and verify clean shutdown. - Include expected result file and test cleanup to preserve MTR state. Verified: - percona.bug_ps11143_threadpool_auth_shutdown - percona.signal_handling_threadpool - percona.threadpool_stats - percona.kill_idle_trx_threadpool - percona.threadpool_debug
- Job-level repository guard on both jobs so forks with Actions enabled stop inheriting the hourly cron, which fails without the canonical repo's secrets and OIDC trust and emails the fork owner every hour.
- Job-level repository guard on both jobs so forks with Actions enabled stop inheriting the hourly cron, which fails without the canonical repo's secrets and OIDC trust and emails the fork owner every hour.
…e postinst inconsistency)
…rphan-sweep runs on forks (percona#6087))
c044c64 to
475d45c
Compare
…rcona/8.4'@(b6d91cf PS-11078 [8.4]: Skip orphan-sweep runs on forks (percona#6087)))
…port from 8.4 Remove native password handling code that references PLUGIN_MYSQL_NATIVE_PASSWORD and PLUGIN_SHA256_PASSWORD constants which are not available in 10.x+. This code block was ported from 8.4 branch during cherrypick but the referenced constants and native password plugin were removed in 10.x architecture. The code was handling legacy 5.6 database layout upgrades, which is no longer needed or supported in 10.x+. Fixes compilation errors: - 'is_old_db_layout' was not declared in this scope - 'PLUGIN_MYSQL_NATIVE_PASSWORD' was not declared in this scope; did you mean 'PLUGIN_SHA256_PASSWORD'? The cherrypick properly removed the parameter from function signatures in 10.x, but this code block that used it was incorrectly left behind. Please squash it with the prev. PS-11143 commit
…t always restarted the LRU scan https://perconadev.atlassian.net/browse/PS-11446 LRUItr::start() is meant to leave the scan hand pointer (m_hp) in place while it is still within the "old" (cold) sublist of the LRU list, and only rewind it to the tail of the LRU list once the pointer has advanced past the old/young boundary into the young (hot) sublist. The boundary check was inverted: `m_hp->old` is true while the pointer is still in the old region, so the scan was rewound on every call instead of only at the old -> young transition. This defeated the intended optimization by repeatedly restarting the scan from the tail rather than letting it continue. Fix the condition to `!m_hp->old`, so the pointer is only reset once it has crossed into the young sublist.
…o ON Post-push fix for PS-10595. innodb_buffer_pool_populate defaulted to OFF, deferring buffer pool page faults to first access at runtime. Testing showed this default causes a measurable performance regression as pages get faulted in on demand during normal operation instead of being pre-populated at startup. Flip the default to ON so pre-population happens automatically on startup, avoiding the regression without requiring users to set the variable explicitly. https://perconadev.atlassian.net/browse/PS-10595
…r_read() https://perconadev.atlassian.net/browse/PS-11444 On IO-bound workloads every physical read serialized on the pool-wide LRU list mutex, because buf_page_init_for_read() held it across the page-hash latch acquisition, the descriptor initialization and the 16 KB frame memset. This narrows that scope and adjusts the surrounding paths accordingly. 1. Narrow the LRU list mutex in buf_page_init_for_read() to cover only the LRU list insert. The page-hash insert, the read io-fix, the frame X-lock and the memset now run under the page-hash cell X-latch alone. Page-hash membership is from now on serialized by the hash cell latch, not by the LRU list mutex. 2. This exposes a short window in which a page is reachable through the page hash but not yet linked into the LRU list. It is made safe by the read io-fix, which is published before the page becomes hash-reachable and cleared only after the LRU-add: threads that find the page through the hash back off on the io-fix, readers wait on the frame X-lock, and eviction cannot see it. In particular buf_page_make_young_if_needed() skips such a page, so promotion never manipulates a not-yet-linked node. 3. Introduce a latching rule: no thread may wait for a block frame rw-lock while holding a buffer pool LRU list mutex. The paths that latch a frame under the LRU list mutex use the rw_lock_*_nowait() variants; the rule is enforced in debug builds by rw_lock_assert_wait_allowed(). 4. Keep the page-hash cell X-latch across the delete + re-insert of the keep-zip path of buf_LRU_free_page() (new keep_hash_lock parameter), so a page id is never observably absent from the page hash while it is still logically in the buffer pool. Without this a concurrent read could insert a duplicate descriptor for the same page id. This also closes the PS-9837 window at the root. 5. Drop the LRU list mutex from buf_pool_watch_set(). It was taken only to avoid the keep-zip gap of point 4; with that gap now closed by the continuous hash cell latch, the hash cell latches purge already holds are sufficient, and purge no longer contends on the LRU list mutex. 6. Add debug assertions guarding the new invariants and a stress test (innodb_zip.lru_mutex_narrow_stress_debug) that widens the windows via debug sync points and drives the attacking compressed-table workload.
https://perconadev.atlassian.net/browse/PS-11445 Minimal restore of the pre-removal per-buffer-pool LRU manager thread (reverted 97d7eba, PS-9071: Merge MySQL 8.3.0 - remove multithreaded asynchronous LRU flusher), completed and reconciled with the current 8.4 codebase: - Removes the recv_writer thread and its mutex/PFS key/latch level. It was reintroduced by 97d7eba as part of adopting upstream's recovery-time LRU flushing model; with the dedicated per-instance LRU manager threads restored, recv_writer's role is redundant and its removal is required for the restore to be self-consistent (recv_sys_t's writer_mutex/flush_type fields don't survive the revert, since later 8.4 development touched this area too, so leaving recv_writer's now-dangling references in place would not compile). - Reuses buf_flush_page_cleaner_disabled_debug for the new thread instead of adding a new sysvar, keeping the patch smaller (the flush/evict/scan stats rework is a separate commit). - Completes the thread lifecycle: joins the LRU manager threads on page-cleaner shutdown (missing after a plain revert) and fixes the m_lru_managers array to be freed with the allocator that matches how it is allocated. - Skips the dblwr::force_flush() call in buf_flush_end() when a batch flushed no pages (e.g. an LRU batch that only evicted clean pages), since nothing was written to the doublewrite buffer for it to flush and there is no reason to take the dblwr instance mutex. - Drops dead per-slot LRU accounting from the page cleaner (superseded by the restored thread) and an unrelated dead-code leftover in Flush_observer (inc_estimate/m_estimate/m_lsn). - Updates MTR results/tests across affected suites for the restored thread's effect on thread lists and shutdown/flush ordering.
https://perconadev.atlassian.net/browse/PS-11445 The restored implementation pauses the LRU manager thread around buf_pool_invalidate_instance() purely by resetting the run_lru event. That's racy: resetting run_lru only makes the *next* os_event_wait() park, so a manager thread that had already returned from the wait (or was in its pre-batch sleep) when invalidation began could still start an LRU batch concurrently with the teardown. Close that window with a flushing_allowed flag: - buf_pool_invalidate_instance() clears it on teardown, - buf_flush_start() checks it when starting a new flush. Updates to the flag are protected by the flush_state_mutex. When starting a new flush, the flag is checked and the flush is started only if allowed and atomically with the check. Therefore after setting the flag to false, the only possible flushes are those that started before the flag was set, so it is then enough to wait for any pending flushes.
https://perconadev.atlassian.net/browse/PS-11445 1. Introduce a knob that allows to enable LRU manager threads. 2. Aggregate multi-instance flush/evict/scan stats for LRU flushing for both LRU threads and page cleaners. With a per-buffer-pool-instance LRU manager thread, several instances can call into the LRU batch path concurrently. We want to avoid the need to synchronize on stats update between them. Therefore each of them returns the statistics and the aggregation happens in a single thread - the buf_flush_page_coordinator thread.
https://perconadev.atlassian.net/browse/PS-11445 The per-pool sleep-time adaptation only considered pages the LRU manager thread had flushed as "made progress" (lru_n_flushed). A batch that only evicted clean pages - which refills the free list exactly as a flush does - was treated the same as a batch that made no progress at all, causing the manager to back off its sleep time even while it was successfully keeping the free list topped up. Renames the counter to lru_n_processed and feeds it n_flushed + n_evicted, so eviction-only batches correctly count as progress for the free-list-fullness heuristic in buf_lru_manager_adapt_sleep_time(). The flush-specific counters (srv_stats.buf_pool_flushed, lru_manager_stat) are unaffected and continue to count flushes only.
https://perconadev.atlassian.net/browse/PS-11447 Previously, whenever an LRU batch flush was already in progress on a buffer pool instance and the doublewrite buffer was enabled, a user thread looking for a free block always waited for that in-progress LRU flush to finish instead of ever attempting its own single-page flush, or even a scan for pages that can be freed immediately. This was decreasing TPS for low concurrency when user thread had to wait for the whole batch as opposed to quickly solving the issue itself. This waiting mechanism is now enabled if and only if lru threads are enabled (innodb_lru_threads = ON), so disabled by default. Also, when this mechanism is enabled, a thread will wait only when there is already an in-flight single-page flush, i.e. the concurrent single-page flush count is capped at 1 per buffer pool instance. Also, before awaiting the LRU batch, user thread is scanning LRU list, searching for a page that could be freed immediately (without flushing). Additional, "not await twice" fix ensures a given buf_LRU_get_free_block() call waits for an in-progress flush at most once, so it does not keep waiting indefinitely on repeated loop iterations (to avoid starvation). New buffer_LRU_% monitor counters (disabled by default): - buffer_LRU_single_page_flush_count - buffer_LRU_flush_await_count
Cut Azure Pipelines usage on pull requests, and make the full compiler matrix
something CI can ask for explicitly.
- Drop the CI trigger (`trigger: none`); pushes no longer queue builds. The
nightly 1:00 AM UTC schedule for 8.4 and PR validation are unaffected.
- Skip PR validation while a pull request is a draft (`pr: drafts: false`);
builds start when it is marked ready for review.
- Replace the `clang-22 Debug INVERTED` matrix leg with `gcc-16 Debug
INVERTED`, dropping the now-inert `UBUNTU_CODE_NAME` (it only feeds the
apt.llvm.org repository line, which is guarded on clang).
- Build only clang-22 RelWithDebInfo, clang-22 Debug, gcc-16 RelWithDebInfo,
gcc-16 Debug and gcc-16 Debug INVERTED by default. The other 31 legs keep a
per-leg compile-time guard, now on a queue-time parameter:
${{ if or(parameters.fullCI, eq(variables['Build.Reason'], 'Schedule')) }}
This replaces the "fullci" branch-name escape hatch, which could not work. A
guard is evaluated during template expansion, and the branch is not knowable
then. Measured on a pull request from branch PS-11473-8.4-fullci, the
compile-time values are:
Build.Reason 'PullRequest'
Build.SourceBranch 'refs/pull/6116/merge'
Build.SourceBranchName 'merge'
System.PullRequest.SourceBranch '' (runtime: the branch name)
So Build.SourceBranchName is always "merge" on a pull request and the old
expression never fired there; it only ever worked for non-PR runs, which its
Build.Reason term already covered.
A parameter is resolved when the run is created and therefore is visible to
${{ }}. It makes the pipeline callable from GitHub Actions in either mode:
POST https://dev.azure.com/it0639/percona-server/_apis/pipelines/<id>/runs?api-version=7.1
{
"resources": { "repositories": { "self": { "refName": "refs/pull/<n>/merge" } } },
"templateParameters": { "fullCI": true }
}
Queued without templateParameters it builds the five default configs; with
fullCI true, all 36. The guard tests the parameter rather than "not a pull
request" because an API-created run reports Build.Reason=Manual, which would
otherwise make every queued run build everything. Schedule is kept so the
nightly still covers all 36; a manual queue from the Azure UI now gets five
unless the checkbox is ticked.
Guards stay per-leg rather than grouped under a single ${{ if }}: that is the
shape this pipeline used before and is known to expand.
Verified by expanding both branches of the guard: the default run yields the
five configs and a fullCI run yields the same 36 legs as before, with the
job's steps, variables, pool and timeout unchanged.
…arset error
== Root Cause ==
mysql_reconnect() shallow-copies the caller's MYSQL options struct into
a stack-allocated tmp_mysql before calling mysql_real_connect():
tmp_mysql.options = mysql->options;
If mysql->options.extension was NULL at that point (e.g. a connection
created without any extended options), the shallow copy left
tmp_mysql.options.extension = NULL as well.
mysql_real_connect() calls ENSURE_EXTENSIONS_PRESENT(&mysql->options)
near its start (client.cc:6607). When extension is NULL the macro
allocates a new st_mysql_options_extention (232 bytes via calloc /
my_raw_malloc) and stores it in tmp_mysql.options.extension.
If mysql_set_character_set() subsequently fails (lines 7595-7605), the
error path executes:
memset(&tmp_mysql.options, 0, sizeof(tmp_mysql.options));
mysql_close(&tmp_mysql);
The memset zeroes tmp_mysql.options.extension to NULL *without freeing
it*. mysql_close() then skips the extension cleanup because the pointer
is already zero. The 232-byte allocation is permanently lost, along
with the connect_attributes map (My_hash) that may have been allocated
inside it (an additional 744 bytes across 7 blocks in the observed case).
The same code path is reached during the inner mysql_reconnect() call
that occurs when mysql_close(&tmp_mysql) sends COM_QUIT and
cli_advanced_command() triggers a second reconnect attempt because the
socket is already in an error state (client.cc:1434).
== Valgrind Report (Build 816, PXC 9.7.1-1 RelWithDebInfo Ubuntu Noble) ==
976 (232 direct, 744 indirect) bytes in 1 blocks are definitely lost
at calloc (vgpreload_memcheck)
by my_raw_malloc (my_malloc.cc:321)
by my_internal_malloc (my_malloc.cc:371)
by mysql_real_connect (client.cc:6607) <- ENSURE_EXTENSIONS_PRESENT
by mysql_reconnect (client.cc:7583)
by cli_advanced_command (client.cc:1434)
by mysql_close (client.cc:8006) <- COM_QUIT path
by mysql_reconnect (client.cc:7601) <- charset-error cleanup
by connect_to_master_via_namespace (rpl_replica.cc:8792)
by try_to_reconnect (rpl_replica.cc:5578)
by handle_slave_io (rpl_replica.cc:5828)
PID 116239. 232 bytes directly lost + 744 bytes indirectly lost
(7 connect-attribute strings inside the extension).
== Fix ==
Call ENSURE_EXTENSIONS_PRESENT(&mysql->options) before the shallow copy
so that mysql->options.extension is guaranteed to be non-NULL:
ENSURE_EXTENSIONS_PRESENT(&mysql->options); /* <-- new */
tmp_mysql.options = mysql->options;
After this change:
• tmp_mysql.options.extension always aliases mysql->options.extension
(same pointer, not a new allocation).
• ENSURE_EXTENSIONS_PRESENT inside mysql_real_connect is a no-op.
• The memset in the charset-error path still zeroes the pointer in
tmp_mysql.options, but the actual extension struct remains alive
and reachable through mysql->options.extension — nothing is leaked.
• mysql_close_free_options() called during normal mysql_close(mysql)
will free the extension via the mysql pointer, as before.
ENSURE_EXTENSIONS_PRESENT is already defined and used throughout
client.cc; no new headers are required.
== Failing Test (Build 816) ==
rpl.rpl_tlsv13 (PID 116239)
== Developer Notes ==
* The leak only triggers when mysql->options.extension is NULL at the
time of the reconnect. Connections that have had any extended option
set (SSL, compression, connect-attrs, etc.) already have a non-NULL
extension and are unaffected.
* The inner reconnect via cli_advanced_command (client.cc:1434) bypasses
the mysql->reconnect guard that mysql_close sets to false (client.cc:
8004) because line 1434 calls mysql_reconnect() unconditionally on any
net_write_command failure that is not ER_NET_PACKET_TOO_LARGE.
* The memset-before-mysql_close pattern at lines 7589 and 7600 is
intentional (it prevents mysql_close_free_options from double-freeing
strings that were shallow-copied from the original mysql handle).
The fix does not change that pattern; it only ensures there is nothing
new to leak.
… path
== Root Cause ==
In Gcs_xcom_control::try_send_add_node_request_to_seeds(), a connection
established by connect_to_peer() was only closed inside the
if (!finalized && connected)
block. If m_view_control->is_finalized() became true between the
connect_to_peer() return and the condition check (a TOCTOU race during
retry_do_join), the block was skipped entirely.
free_connection() only calls free() on the connection_descriptor struct
itself (node_connection.h:90) — it does NOT call SSL_free() or close the
socket. The SSL object allocated by SSL_new() in
timed_connect_ssl_msec() (xcom_network_provider_ssl_native_lib.cc:694),
plus all memory allocated internally by SSL_connect() (session state,
cipher context, etc.), was permanently lost.
== Valgrind Report (Build 816, PXC 9.7.1-1 RelWithDebInfo Ubuntu Noble) ==
96,664 (7,640 direct, 89,024 indirect) bytes in 1 blocks are
definitely lost
at malloc (vgpreload_memcheck)
by CRYPTO_zalloc
by SSL_new (libssl.so.3)
by timed_connect_ssl_msec (xcom_network_provider_ssl_native_lib.cc:694)
by Xcom_network_provider::open_connection (xcom_network_provider.cc:332)
by Network_provider_manager::open_xcom_connection (network_provider_manager.cc:241)
by Gcs_xcom_control::connect_to_peer (gcs_xcom_control_interface.cc:623)
by Gcs_xcom_control::try_send_add_node_request_to_seeds(gcs_xcom_control_interface.cc:563)
by Gcs_xcom_control::send_add_node_request (gcs_xcom_control_interface.cc:543)
by Gcs_xcom_control::retry_do_join (gcs_xcom_control_interface.cc:477)
by Gcs_xcom_control::do_join (gcs_xcom_control_interface.cc:291)
Observed in 9 of 11 affected mysqld PIDs (PIDs 16098, 164923, 61642 …).
Each leak is 7,640 bytes direct + ~89–98 KB indirect per occurrence.
== Fix ==
Add an else-if branch that calls xcom_client_close_connection(con)
whenever connected == true but finalized == true. This ensures the
SSL object and socket are always released exactly once:
• !finalized && connected → existing path: add_node + close
• finalized && connected → new path: close only
• !connected → close already done inside connect_to_peer
(disable_nagle failure path) or ssl_fd
is null (connect failure); nothing to do
== Failing Tests (Build 816) ==
group_replication.gr_ssl_options
group_replication.gr_ssl_tls13_runtime_valid_configuration
group_replication.gr_recovery_tlsv13_*
group_replication.gr_rejoin_bootstrap
group_replication.gr_rejoin_no_bootstrap
group_replication.gr_clone_integration_*
group_replication.gr_acf_receiver_*
group_replication.gr_flush_logs
group_replication.gr_primary_mode_group_operations_22_1
group_replication.gr_reset_slave_channel
== Developer Notes ==
* free_connection() (node_connection.h:88) is intentionally a bare
free() — it does not own the SSL or socket lifetime. Only
xcom_client_close_connection() / close_xcom_connection() drives the
provider's close_connection() which calls ssl_free_con() -> SSL_free().
* The race is narrow but reproducible under Valgrind (slow process) or
high-load retry scenarios where is_finalized() transitions while the
SSL handshake is in progress.
* No behaviour change for the normal path (not finalized): the existing
xcom_client_close_connection() call inside the if-block is untouched.
Vector indexes have the type (algorithm) SE_SPECIFIC, and we add a column option in the data dictionary saying `vector_index=1;` which gets picked up by dedicated code in the data dictionary and the handler part of InnoDB. In the SQL layer, the vector index is very much a thing; there is an `HA_KEY_ALG_VECTOR`, an `HA_VECTOR` and a `KEYTYPE_VECTOR`. Extra SQL is added to display the type of a vector index as VECTOR rather than SE_SPECIFIC.
Adding the syntax `TYPE <ident> WITH ( <ident> = <ident>...) ` to index creation syntax. E.g.: `CREATE INDEX <name> ( <table> ) TYPE hnsw WITH ( M = 6 )` The syntax in the `WITH` list, christened Index Construction Parameters in this commit, must be verified by the storage engine. There were no hooks for this in InnoDB so one has been added in check_engine(). We have to do it fairly early so that we can prevent table creation in the DD in case of errors. Hence, the index type and parameter list are validated inside the storage engine using a new interface validate_engine_attributes(). The index construction parameters are serialized as a string of key-value pairs inside the index's `option` field. The serialization and de-serialization happen entirely inside the Data Dictionary. To do: We will probably still need a hook to handle the case of an already-existing table with invalid attributes; there are no hooks for this.
475d45c to
17bbde8
Compare
Stacked on #6000