Skip to content

fix: route insert-runner thread safety through a client capability - #849

Merged
jamesgao-jpg merged 5 commits into
zilliztech:mainfrom
serhiizghama:fix/rate-runner-thread-safe-capability
Aug 27, 2026
Merged

fix: route insert-runner thread safety through a client capability#849
jamesgao-jpg merged 5 commits into
zilliztech:mainfrom
serhiizghama:fix/rate-runner-thread-safe-capability

Conversation

@serhiizghama

Copy link
Copy Markdown
Contributor

Fixes #810. The fixed-rate insert runner decided per-thread client handling by checking db.name against a hardcoded list (PgVector, Doris, SeekDB, VolcMySQL), so clients that declare thread_safe = False but aren't on that list — OceanBase, VectorChord, Adbpg, LanceDB — fell through to the default path and shared one non-thread-safe connection across insert workers. OceanBase was never in the list at all.

I moved the decision onto the existing thread_safe capability. The runner now branches on db.thread_safe and asks the client for a per-thread copy via a new VectorDB.copy_for_thread(). The default deep-copies the instance, which already works for the psycopg-family clients and for LanceDB (it has a custom __deepcopy__); the mysql.connector-backed clients override it to shallow-copy and drop their open socket so init() reconnects inside the worker. Behavior for the databases that were already special-cased is unchanged — the capability just also covers the ones the name list missed, and adding a new non-thread-safe client no longer means remembering to edit the runner.

Added a unit test that drives send_insert_task with a fake client and checks that thread-safe clients insert through the shared object while non-thread-safe ones go through a copy plus init().

@sre-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: serhiizghama
To complete the pull request process, please assign xuanyang-cn after the PR has been reviewed.
You can assign the PR to them by writing /assign @xuanyang-cn in a comment when ready.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@jamesgao-jpg

Copy link
Copy Markdown
Collaborator

/assign @jamesgao-jpg

deep-copied (e.g. an open DB-API socket) override this to shallow-copy and
drop their connection handles so init() re-establishes them per thread.
"""
return deepcopy(self)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Avoid deep-copying live Psycopg handles

Adbpg and VectorChord both declare thread_safe=False but do not override copy_for_thread. run_with_rate keeps the parent init context open while these workers run, so the default deepcopy(self) receives a live psycopg.Connection. I reproduced this with Psycopg 3.3.4: deepcopy(connection) raises TypeError: no default reduce due to non-trivial cinit. As a result, the first fixed-rate insert task for either backend fails before the copy reaches init().

Please give both clients a shallow-copy override that clears conn and cursor before worker init reopens them; PgVector uses the same handle pattern and is worth covering too. The fake regression client only contains copyable lists, so it does not exercise this boundary.

Comment thread tests/test_rate_runner_thread_safety.py Outdated


def _runner(db: VectorDB):
return RatedMultiThreadingInsertRunner(rate=10, db=db, dataset_iter=None)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Construct this runner with a valid current batch size

Current main defaults the insert batch size to 100 and rejects rate % batch_size != 0. This helper passes rate=10 without batch_size, so in the current merge result every helper-using test raises ValueError before reaching send_insert_task.

Please use a valid rate and batch-size pair here. The green PR workflow does not catch this because make unittest currently runs only tests/test_dataset.py::TestDataSet::test_download_small, not this new test file.

Replace the db.name branching in RatedMultiThreadingInsertRunner with a
VectorDB.copy_for_thread() hook driven by the existing thread_safe flag, so
non-thread-safe clients get a per-thread copy without the runner hardcoding
which databases those are.
The mysql.connector-backed clients (SeekDB, VolcMySQL, OceanBase) can't be
deep-copied while holding an open socket, and Doris needs its client/table
cleared; each now overrides copy_for_thread accordingly. OceanBase was
previously missing from the runner's name list entirely, so its fixed-rate
insert workers shared one connection.
PgVector, Adbpg and VectorChord all keep a live psycopg connection open
on the parent while run_with_rate spins up insert workers. The default
copy_for_thread() deep-copies the client, and psycopg connections have
no __reduce__ for their non-trivial __cinit__ state, so the copy raises
before the worker ever calls init(). Shallow-copy and drop conn/cursor
instead, matching the pattern already used by Doris/OceanBase/SeekDB.
…idation

Upstream's insert-batch-size work (zilliztech#813) landed after this branch and now
rejects rate % batch_size != 0. The helper's implicit rate=10 with the
new default batch_size=100 tripped that check before send_insert_task
ever ran.
@serhiizghama
serhiizghama force-pushed the fix/rate-runner-thread-safe-capability branch from 29b454c to 8544074 Compare August 27, 2026 05:10
@serhiizghama

Copy link
Copy Markdown
Contributor Author

Both fixed. Gave PgVector, Adbpg and VectorChord their own shallow copy_for_thread (same pattern as Doris/OceanBase/SeekDB) so init() reconnects instead of deepcopy choking on the live psycopg handle. Also rebased on main to pick up the insert-batch-size work and fixed the test helper's rate/batch_size pair — you were right that it was raising before reaching send_insert_task.

@jamesgao-jpg

Copy link
Copy Markdown
Collaborator

/lgtm

@jamesgao-jpg
jamesgao-jpg merged commit 666aa40 into zilliztech:main Aug 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fixed-rate insert runner can share non-thread-safe DB clients

3 participants