Upgrading#
zeusdb pins zeusdb-vector-database to one minor series at a time, so a breaking release of the vector database reaches you only when zeusdb moves its constraint. The constraint is zeusdb-vector-database>=0.8.0,<0.9.0, so installing or upgrading zeusdb brings in the 0.8.0 series. Read the section for the series you are coming from before upgrading.
Upgrading to 0.8.0#
The changes below are observable from an application written against zeusdb-vector-database 0.5.0, 0.6.0 or 0.7.0. Most saved indexes and most code keep working; the items that do not are listed first.
Breaking#
dimis required oncreate(). It defaulted to 1,536.create("hnsw")without it raisesTypeError. Pass the width your embedding model produces.A metadata filter decides which records are ranked, not which results survive. A filtered search returns the
top_knearest of the records the filter matches, where it used to return whatever survived the filter out of thetop_knearest overall. Code that raisedtop_kto compensate can stop, and a test that pinned a short page under a filter needs its expectation refreshed. See Metadata Filtering.add()raises on a malformed batch. Parallel arrays of different lengths raiseValueError, and a parallel array that is not alist, such as a tuple or an ndarray of ids, raisesTypeError. Both used to be absorbed silently. See Add Data.l1cannot be quantized.create(space="l1", quantization_config=...)raises, andload()refuses a saved directory pairing them, whatever release saved it. Usel2with the same configuration, orl1without quantization. See Metrics that cannot be quantized.A quantized
cosineindex ranks and scores by cosine distance to the reconstruction, where it ranked by the squared distance and reported that. Result order changes, in each measured case for the better, and a test that pinned scores or exact result sets from a quantized cosine index needs refreshing. A directory saved earlier opens and is scored by cosine distance from then on; loading it once withZEUSDB_LOAD_REBUILD_GRAPH=1set and saving it again wires its graph on the new ordering.A quantized
l2index reports the rooted distance on every path. The traversal used to return the squared sum. A threshold derived from the old traversal scores needs re-deriving on the rooted scale.dim,ef_construction,top_kandef_searchhave ceilings: 65,536, 4,096, 65,536 and 131,072. None had one. A directory saved with a value outside these bounds does not open under this release.save()andload()print nothing. Both wrote progress lines to stdout. A script that read stdout for them has nothing to read. The steps aredebuglog records instead.The saved graph is one file,
hnsw_index.zdbgraph, in place ofhnsw_index.hnsw.graphandhnsw_index.hnsw.data, since 0.7.0. A directory holding the old pair opens, rebuilds its graph once, and writes the single file on the nextsave(). Code that lists the directory or readsfiles_includedneeds the new name.One wheel per platform, built against the stable ABI, since 0.6.0. A pinned wheel filename carrying an interpreter tag such as
cp312-cp312no longer resolves, and PyPy and free-threaded CPython build from the source distribution.
Changed#
An unquantized index holds about half the memory it did, because every raw vector was held twice. At 50,000 records of dimension 1,536 the index commits 325 MiB where it committed 632.
graph_memory_mbandtotal_memory_mbboth read lower on an index that has not changed.quantized_with_rawnow holds more than an unquantized index at every record count, 1.08x at 50,000 records of dimension 1,536, which the earlier figures on this site had the other way round.A save is atomic and verified. A save writes a sibling directory and renames it into place, and
manifest.jsonrecords a length and a digest for every other artefact, whichload()checks before parsing them.load()also refuses a directory missing a file the manifest names. See Persistence.list()returns records in arrival order and pages withoffsetandafter, where it returned hash map iteration order that differed between processes.Every spelling of a log level is accepted by both layers.
warning,critical,warn,err,fatalanderrorall work, wherewarningandcriticalwere refused by the Rust layer andwarnby the Python layer.Records queued in the log file’s writer at process exit are no longer lost. The file is drained through an
atexithook, andshutdown_logging()drains it on demand.The
quantized_onlybreak-even warning, thequantized_with_rawwarning and the compression ratio warning attribute to yourcreate()line, and the low dimension warning is gone.
Added#
space="dot". $and, $or and $not in the filter language, and the operators nin, any, all, exists, is_missing and is_null. create(indexed_fields=[...]). len(index), id in index and count(). remove_points(), remove_where(), delete(), clear(), update_metadata(), rebuild() and shrink_to_fit(). list(after=...). get_records(strict=True). AddResult.ids. index.space, index.m, index.ef_construction, index.expected_size and index.indexed_fields. shutdown_logging(). Each is documented on the page for its operation.
Upgrading from 0.4.x#
zeusdb-vector-database 0.5.0 changed behaviour in ways an application written against the 0.4 releases can observe. Saved indexes and existing code largely keep working; the items below are the differences that matter, and the section above applies on top of them.
Search results#
A reranked quantized search returns the raw-vector distance as the score, not the distance to the reconstruction. Any threshold tuned against the old scores is wrong on a quantized_with_raw index. Pass rerank=0 to restore the unreranked scores and ordering.
Search on a quantized index over-fetches by default. A quantized_with_raw index now fetches and rescores a calibrated number of candidates per query, which holds recall at the level of an unquantized index and costs query time above roughly 10,000 records. See Product Quantization.
Filters#
An unknown filter operator now raises
ValueErrorrather than returning an empty result set.Numbers compare by magnitude across every operator, so an integer and an equal float now match under
eq,neandinas they already did underltandgte.Array conditions now compare for equality rather than being silently dropped, so
{"tags": ["ai"]}matches only records whosetagsis exactly["ai"].neagainst a field a record does not have returns false, so a filter for “status is not archived” excludes records with no status at all.
Add and overwrite#
add()withoverwrite=Falseon an existing ID reports the collision through the returnedAddResultrather than raising.Metadata is replaced wholesale on overwrite, never merged, so an overwrite with an empty metadata dict clears it.
summary()returns a plain ASCII string; the emoji prefix is gone.
Validation#
Vectors containing
NaNor an infinity are rejected onadd(), raise onsearch(), and are refused onload().mmust be at least 2.expected_sizemust be at most 100,000,000.
Changed defaults#
The default
mnow scales withexpected_size: 16 up to 25,000 and 32 above. Passmexplicitly to keep the old value.The default
subvectorsnow scales with the dimension rather than being fixed at 8, holding compression at 128x fromdim=256upward. Passsubvectors: 8explicitly to keep the old code size.The default
training_sizeis derived, 10,000 at the defaultbits, rather than 1,000.The default
ef_searchismax(2 × top_k, 150)forl1andl2;cosinekeepsmax(2 × top_k, 100).
Removed entry points#
Around a dozen entry points that were duplicate, dead or unsafe were removed, including get_next_id, which consumed an internal ID on every call. HNSWIndex can no longer be constructed directly; instances come from VectorDatabase.create() or load(). get_stats() lost memory_savings and gained graph_memory_mb, total_memory_mb and raw_vectors_memory_mb. get_performance_info() lost its insertion speedup fields, which described a parallel insert path that does not exist.
Behaviour improvements you get without code changes#
Persistence: loading now reads the saved graph back rather than rebuilding it, so a load takes about a second at 50,000 records and returns identical results to the index that was saved. An index saved by 0.4.x still loads; its graph dump carries the old distance names, so the loader rebuilds it, with no user action required.
Deletion: removed and overwritten records no longer consume result slots, so
top_kno longer degrades with churn. Acompact()method reclaims the stranded graph nodes.Concurrency: concurrent search now works and scales, measured at roughly seven times single-thread throughput at sixteen threads, and
add()andsearch()can run concurrently.
Logging#
ZEUSDB_LOG_FILEnow writes exactly the path given rather than a dated sibling. SetZEUSDB_LOG_ROTATION=dailyfor the previous behaviour.The disable flag is
ZEUSDB_DISABLE_AUTO_LOGGINGin both the Python and Rust layers, with the former Rust-only name accepted as a deprecated alias. Both requiretrue,1oryes.
Platform support#
Wheels are published for Linux x86_64 and aarch64 (glibc and musl), macOS Apple Silicon, and Windows x86_64, for Python 3.10 through 3.14. macOS Intel, 32-bit x86, armv7, s390x and ppc64le are no longer published.
The zeusdb package#
zeusdb re-exports the full public surface of the vector database (VectorDatabase, HNSWIndex, AddResult, init_logging, init_file_logging, is_logging_initialized, shutdown_logging) and provides the zeusdb.logging_config helpers the integration packages import, documented under Logging. It pins zeusdb-vector-database>=0.8.0,<0.9.0, so a future breaking release will not reach you automatically.