Skip to content

Release 2.7.0 - #19

Merged
CoderDayton merged 6 commits into
mainfrom
release/2.7.0
Jul 29, 2026
Merged

Release 2.7.0#19
CoderDayton merged 6 commits into
mainfrom
release/2.7.0

Conversation

@CoderDayton

@CoderDayton CoderDayton commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Release 2.7.0. Four themes, each with tests; full changelog in CHANGELOG.md.

The async API now mirrors the sync one

The async surface had drifted: eight collection methods and four database
methods had no async counterpart, and the sub-namespaces were flattened into
names that no longer matched (collection.ttl.sweep() became sweep_ttl()).

Async sub-namespaces are now generic proxies over the sync ones, so a method
added to a sync namespace is reachable from async immediately, with no wrapper
to write.

await collection.edges.upsert(src, dst, kind="cites", weight=0.9)
await collection.ttl.sweep()

Added collection.edges/.events/.ttl/.pending/.maintenance/.counters on
AsyncVectorCollection, async with collection.tx(),
AsyncVectorDB.transaction(fn), save, as_langchain, as_llama_index,
add_texts_streaming, and a real async-generator events.subscribe.
A parity test fails if a public sync method gains no async counterpart.

Fixed: cancelling an async with transaction wedged the database — teardown
ran through an await, and suspending while a GeneratorExit is in flight
left the savepoint open and the DB lock held for the life of the process.

One connection per thread

A single shared sqlite3.Connection served every collection and thread.
SQLite provides no isolation between operations on one connection, so a read
on one thread could observe another thread's uncommitted rows.

File-backed databases now open one connection per thread. In-memory databases
keep a single shared connection: pooling one requires a shared-cache URI, and
shared cache takes table-level write locks that busy_timeout does not wait
out.

The database-wide lock stands down when connections are pooled — a read on
another thread returned in 1 ms against a transaction held open for 400 ms,
and still saw committed state. Transactions now begin BEGIN IMMEDIATE, and a
rolled-back transaction ends its enclosing transaction as well as its
savepoint.

Crash and concurrency fixes

  • Deleting from a memory-mapped index segfaulted the process. add()
    reloaded a view=True index as writable; remove() did not, and usearch
    crashes rather than raising on a read-only mapping.
  • Index reads could dereference a closed index — six methods checked
    _index is None then re-read the attribute; they now snapshot it once.
  • A locked write inside a transaction is no longer retried, which had
    re-executed statements that already applied and duplicated auto-id rows.
  • close() and delete_collection() stop TTL sweepers, which previously
    outlived both and logged a failure every interval.
  • usearch>=2.24.0 — earlier versions can underflow Index::size() under
    concurrent add/remove (Bug: Integer underflow in Index::size() during concurrent add/remove. unum-cloud/USearch#697).

Transactions cover the vector index

tx() and db.transaction() guarded only the catalog: a SAVEPOINT rolled the
rows back while the vectors stayed, leaving the index keyed to rows that no
longer existed. add_texts, delete_by_ids, pending.flush() and
ttl.sweep() now buffer their HNSW mutations and apply them just before the
outermost savepoint releases.

Breaking: add_texts(ids=…) refuses an id that already exists instead of
upserting silently. Pass on_conflict="replace" for the old behaviour. The
check runs before anything is written. The LangChain adapter keeps upserting
so its own contract holds.

Added collection.reserve_ids(n) and AsyncVectorCollection.atomic(fn).

Changed: a search inside a transaction does not see that transaction's own
vector writes — they land at commit. Catalog reads are unaffected.

Verification

ruff-format, ruff, mypy (99 files), bandit, pytest — 872 passed,
3 skipped.

@CoderDayton CoderDayton self-assigned this Jul 29, 2026
@CoderDayton
CoderDayton merged commit a7a16d1 into main Jul 29, 2026
9 checks passed
@CoderDayton
CoderDayton deleted the release/2.7.0 branch July 29, 2026 14:52
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.

1 participant