Skip to content

[milvus options] Let a deployment tune a Milvus collection's vector index and its searches - #1741

Draft
edwinyyyu wants to merge 4 commits into
MemMachine:feat/horizontal-scalingfrom
edwinyyyu:feat/milvus-index-options-speedkick
Draft

edwinyyyu wants to merge 4 commits into
MemMachine:feat/horizontal-scalingfrom
edwinyyyu:feat/milvus-index-options-speedkick

Conversation

@edwinyyyu

@edwinyyyu edwinyyyu commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Purpose of the change

MilvusConf gains vector_index, the vector index of the store's collection: a plain mapping of index_type, params (build parameters) and search_params (parameters every search passes), as Milvus takes them for that index type, so pymilvus stays optional for configuration parsing; the store's params coerce it into MilvusVectorIndex, whose parameter values are scalars. Unset selects the store's own index, HNSW_SQ (M 18, efConstruction 240, SQ4U codes refined at FP16) searched with refine_k 8, as before. Startup creates the index unless the collection has one, in which case the collection keeps its index; the search parameters apply to every query from startup on. The sample configurations and the configuration docs' parameter table carry the key, and the Milvus design doc says the index and its searches are configurable as one spec.

One guard: params may not set index_type or metric_type, which pymilvus's IndexParam would let override the spec's own index type and the store's metric. Everything else is Milvus's to check: it checks the parameters the index type takes when it creates the index and when a search reaches an indexed segment, and ignores other keys. The collection isolates partitions by key, which Milvus documents for the HNSW family only, so a deployment picks an index of that family; Milvus 2.6.24 accepts another type (IVF_FLAT) on the collection without error, so the store leaves the choice to the deployment.

Two commits: the option, with its docstrings, the configuration page's parameter table row and the design doc's vector index paragraph, and the sample configurations' comments. Adaptation from #1618: pymilvus has no typed models for index or search parameters, so there is nothing to validate each mapping against; the unit of configuration is the whole index spec, so build and search parameters always come from one author for one index type. The option lands on the registry-backed Milvus store of [vector store scale-out 5/6] and [vector store scale-out 6], on its MilvusVectorStoreParams; the index it sets is the one startup creates for the collection's vector field, the partition handles the store builds carry its search parameters, and the tests build their store through the module's _params helper.

Position: on [qdrant options] #1618 directly, so the two options PRs share one edit of the configuration table and the sample configurations.

Stack

24 open PRs: three independent PRs, and the vector store tree of short parallel branches. Every open PR in the tree has feat/horizontal-scaling as its GitHub base, and the independent PRs have main. The branches are in a fork, and a pull request can target only this repository's branches, so the on column gives the order the PRs build on each other. A stacked PR's diff on GitHub includes the PRs under it until they merge.

Independent of the vector store tree, directly on main:

# PR change on
— #1624 Make no memory request create a project main
— #1786 Refuse a filter value of the wrong type for a datetime column, and answer an invalid list filter with 422 (port of #1620) main
— #1792 Refuse property values that some store refuses or alters where an episode enters main

The vector store tree. Each PR builds on the one in its on column; PRs on the same parent are parallel branches and do not depend on each other. #1631 is closed, superseded by #1733–#1736, which hold its changes split in four, with review changes since. #1675 is closed, superseded by #1627. #1841 is closed: its request packing is dropped, and its cancellation contract is in #1840. #1828 and #1839 sit side by side on the branch; #1839 and #1840 are the event memory recovery stack, and #1684 heads the event memory stack, which its own PRs index, listed because it builds on #1840, its branch also carrying #1828's commits until one of the two merges; #1871 keeps its own Stack section. Until the PRs under it merge, their changes show in a stacked PR's diff.

# PR change on
[vector store scale-out 1/6] #1671 (merged) Remove custom sharding from the Qdrant store (port of #1654) main
[vector store scale-out 2/6] #1733 (merged into feat/horizontal-scaling) Answer vector store queries with record UUIDs and scores, and refuse invalid inputs feat/horizontal-scaling
[vector store scale-out 3/6] #1734 (merged into feat/horizontal-scaling) Arbitrate vector store collections in a SQL registry, with an incarnation per collection life and a purge feat/horizontal-scaling
[vector store scale-out 4/6] #1735 (merged into feat/horizontal-scaling) Move the Qdrant store onto the collection registry feat/horizontal-scaling
[vector store scale-out 5/6] #1736 (merged into feat/horizontal-scaling) Move the Milvus store onto the collection registry, against a Milvus server feat/horizontal-scaling
— #1775 (merged into feat/horizontal-scaling) Accept attempts exhausted in the lifecycle churn contract, and say which Qdrant operations filter on the incarnation feat/horizontal-scaling
— #1779 (merged into feat/horizontal-scaling) Classify Qdrant errors by status code alone feat/horizontal-scaling
— #1813 (merged into feat/horizontal-scaling) Create Qdrant collections with strict mode off feat/horizontal-scaling
— #1820 (merged into feat/horizontal-scaling) List only active projects in projects/list feat/horizontal-scaling
[user properties 1/2] #1702 (merged into feat/horizontal-scaling) Keep undeclared properties out of the vector store feat/horizontal-scaling
[user properties 2/2] #1670 (merged into feat/horizontal-scaling) Remove per-project filterable properties (port of #1606) feat/horizontal-scaling
— #1824 (merged into feat/horizontal-scaling) Drop the unread logical key from the purge queues feat/horizontal-scaling
— #1788 Refuse a repeated record UUID or a non-finite property value at upsert, and state the datetime property contract feat/horizontal-scaling
— #1631 (closed) Superseded by #1733–#1736, which hold its changes split in four, with review changes since —
[vector store scale-out 6] #1627 (merged into feat/horizontal-scaling) Give each vector store a fixed schema and string-keyed partitions, built from the deployment's configuration feat/horizontal-scaling
[declared schema 1/2] #1628 (merged into feat/horizontal-scaling) Make a vector store filter only on the properties it declares feat/horizontal-scaling
[sqlite write transactions 1] #1844 (merged into feat/horizontal-scaling) Take SQLite's write lock at BEGIN in every store on a relational SQLite engine feat/horizontal-scaling
[session storage 1/2] #1622 Create a session's storage with the session, never on a request feat/horizontal-scaling
[session storage 2/2] #1625 Look a partition up with get_partition, and remove open-or-create from both stores and close from the segment store #1622
[selective filter 1] #1871 Route filtered queries by selectivity, and search allowlists exhaustively feat/horizontal-scaling
[search results, deferred] #1663 Score every vector search by cosine similarity, and name scores for it (port of #1598's cosine half) #1628
[declared schema 2/2] #1616 Close the filter union, and make negation the complement on every backend feat/horizontal-scaling
[sqlite store fixes 1/7] #1460 Publish vector index files atomically (but not durably) feat/horizontal-scaling
[sqlite store fixes 2/7] #1469 Never reuse a row id in SQLiteVectorStore #1460
[sqlite store fixes 3/7] #1672 Own the search engine's concurrency in the store, not in each engine (port of #1612) #1469
[sqlite store fixes 4/7] #1673 Serialize a partition's writes so the engine sees them in order (port of #1607) #1672
[sqlite store fixes 5/7] #1674 Refuse a pending row replay cannot honor, instead of dropping it (port of #1608) #1673
[sqlite store fixes 6/7] #1675 (closed) Superseded by #1627, which takes SQLite's write lock at BEGIN where the engine is created —
[sqlite store fixes 7/7] #1676 Give every write a fresh row id, so a key names one version (port of #1610) #1674
[sqlite store fixes 8] #1843 Hold the engine lock until a cancelled engine call's thread finishes #1676
[qdrant options] #1618 Let a deployment tune a Qdrant collection's HNSW, optimizers and quantization feat/horizontal-scaling
[milvus options] #1741 (this PR) Let a deployment tune a Milvus collection's vector index and its searches #1618
— #1841 (closed) Its request packing is dropped, and its cancellation contract is in #1840 —
[property keys] #1828 Refuse an illegal metadata key before any store writes, and reserve one key prefix feat/horizontal-scaling
[event memory recovery 1/2] #1839 Give EventMemory a store's shape, with a handle per partition feat/horizontal-scaling
[event memory recovery 2] #1840 Keep event memory's two stores consistent through failed ingests and forgets #1839
[event memory 1/6] #1684 Add time bounds, sources and expansion to EventMemory (port of #1597, without session) #1840
[schema migration 1/2] #1825 Add memmachine-server migrate, which creates every configured store's schema before servers start feat/horizontal-scaling
[schema migration 2/2] #1826 Make startup check what migrate created, and create nothing at startup #1825

This PR is its 2 commits, 6150110b1, 8971a264b, stacked on #1618.

Verification

Restacked with the vector store tree, at 8971a264b (on #1618's 9760076f7), on 2026-10-30, with #1618 directly on feat/horizontal-scaling at 690116e37, where [declared schema 1/2] (#1628) is merged as 5c9d3ae99, with #1831 before it and #1858 after it; [sqlite write transactions 1] (#1844) is no longer beneath it, since neither PR uses any of #1844's code: its own lines are unchanged, and its two commits are re-signed, with their messages and author unchanged. Checks: ruff check and ruff format --check clean; ty check clean as CI runs it (uv run --frozen --all-extras ty check --project packages/server); uv lock --check clean; the vector store, configuration, and resource manager unit tests pass, 649 tests (5 skipped). The integration tests did not run here. This head's tree differs from 2c78deb61 only by the changes beneath it: #1702's filter routing, #1820, #1670's review fix, main's #1785, #1803, #1787, #1802, #1522, and #1774, #1824, #1627's own since then (its partition lookup named open_partition in place of get_partition, its per-partition schema record removed, require_partition_key named validate_partition_key, the store's parameters off its interface and partition_key off the partition handle, partition keys of up to 255 bytes with the SQLite stores' names hashed from them, the bool checks named is_valid_identifier and is_valid_filter, its raising checks named validate_, the retention check named _validate_tombstone_retention, its tombstones without the partition key, its partition registry document's wording, the first and second reviews' fixes, the accuracy fixes, and its changes since e38e8003e: each store's schema passed as one VectorStoreSchema, SQLite partitions named by an incarnation, the shared SQL engine setup and write transactions in common/sql.py, and every session's event memory served from the deployment's configuration), #1628's longer request timeout for its oversized Milvus upsert test, #1628's four commits from Edwin's review (no supported_filter_nodes, its checks in utils.py, sqlite_properties.py, and the strict-mode comment), #1628's second round of review commits (the identifier rule in data_types.py, which imports nothing from the package, and the interface's Raises sections sorted by name), #1628's is_valid_identifier docstring and its records table without extend_existing, #1628's last commits before its merge (property_column_names deriving the names from the naming rule, the contract's re-upsert check, and its tidied tests), #1831, and #1858, with [sqlite write transactions 1] (#1844) and [search results] (#1663) no longer beneath it. The database manager's tests last ran at 2c78deb61, on 2026-10-09: the vector store suites and the database manager's tests, unit and integration, 731 passed (5 skipped), against Qdrant 1.19.1 defaulting new collections to strict mode and Milvus 2.6.24 in containers.

Rebased onto #1618's head bb19702a1 (on feat/horizontal-scaling with #1736 merged), at d67f8af76: ruff check, ruff format --check, ty check, and uv lock --check are clean. The rebase resolved conflicts with the Milvus store's changes since 2026-10-02 (the metrics factory beside the vector index in the store's parameters and the database manager, grpc beside the pydantic imports, and the configuration and manager tests' settings beside the vector index), and the not-positive settings test now builds the parameters with model_validate, since the vector index field makes the keyword form ill-typed. The Milvus store, configuration, and database-manager tests pass against Milvus 2.6.24 in a test container (169 passed); the full suites were not rerun at this head.

Before the rebase:

At each of this PR's commits, 42c9c17d5 and d641da2aa, on 2026-10-02: ruff check and ruff format --check clean; ty check clean as CI runs it (uv run --frozen --all-extras ty check --project packages/server); the server suite without integration tests passes, 2037 tests (6 skipped). At d641da2aa, the Milvus store's integration tests, against Milvus 2.6.24 in a container, pass (62); the resource manager's tests carry no integration mark and run in the server suite above. The sample configurations' commented block, uncommented, parses into MilvusConf and MilvusVectorIndex in all three files. The behavior the docs state was checked against Milvus 2.6.24: index creation refused an unknown index type and a non-integer M and accepted an unknown build key; a search on an indexed segment refused ef of "abc", of -5 and below limit, and ignored an unknown key; IVF_FLAT was accepted on the collection. Both commits are signed.

🤖 Generated with Claude Code

@edwinyyyu
edwinyyyu changed the base branch from main to feat/horizontal-scaling October 9, 2026 00:50
@edwinyyyu
edwinyyyu force-pushed the feat/milvus-index-options-speedkick branch from d641da2 to d67f8af Compare October 9, 2026 17:59
This was referenced Oct 9, 2026
@edwinyyyu
edwinyyyu force-pushed the feat/milvus-index-options-speedkick branch 2 times, most recently from 1d4f155 to 5fd61f4 Compare October 9, 2026 20:22
edwinyyyu added a commit to edwinyyyu/MemMachine that referenced this pull request Oct 10, 2026
…achine#1741) into the final state

A Qdrant collection is created with the deployment's HNSW, optimizer and
quantization options and with the strict mode of MemMachine#1628.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@edwinyyyu
edwinyyyu force-pushed the feat/milvus-index-options-speedkick branch 2 times, most recently from 2c78deb to 2d0794b Compare October 14, 2026 00:21
edwinyyyu added a commit to edwinyyyu/MemMachine that referenced this pull request Oct 14, 2026
…achine#1741) into the final state

A Qdrant collection is created with the deployment's HNSW, optimizer and
quantization options and with the strict mode of MemMachine#1628.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
edwinyyyu and others added 4 commits October 30, 2026 16:12
…tization

`QdrantConf` gains `hnsw_config`, `optimizers_config` and
`quantization_config`, plain mappings mirroring qdrant-client's
`HnswConfigDiff`, `OptimizersConfigDiff` and `QuantizationConfig`, so
qdrant-client stays optional for configuration parsing; the store's
params validate them against qdrant's own models. They apply to the
store's one collection, created at startup.

`m` must be 0 or unset: the collection is multi-tenant and disables the
global graph in favor of per-partition payload indexing, so a deployment
tunes `payload_m`, which defaults to 16 as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
The sample configurations gain a commented block with the three keys,
checked against qdrant-client's models.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
…ches

`MilvusConf` gains `vector_index`, a plain mapping of `index_type`,
`params` (build parameters) and `search_params` (parameters every search
passes), so pymilvus stays optional for configuration parsing; the
store's params coerce it into `MilvusVectorIndex`. pymilvus has no models
for index or search parameters, so the unit is the whole spec: build and
search parameters come from one author for one index type. The index
applies when startup creates the collection's vector index, and the
search parameters to every query. Unset selects HNSW_SQ searched with
refine_k 8, as before.

`params` may not set `index_type` or `metric_type`, which pymilvus would
let override the spec's own index type and the store's metric.
Milvus checks the parameters the index type takes when it creates the
index and when a search reaches an indexed segment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sample configurations gain a commented `vector_index` block under the
Milvus alternative, checked against `MilvusConf` and `MilvusVectorIndex`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@edwinyyyu
edwinyyyu force-pushed the feat/milvus-index-options-speedkick branch from 2ea1009 to 8971a26 Compare October 30, 2026 23:17

This branch has not been deployed

No deployments
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