Conversation
|
Important Format specification voteThis PR modifies the Lance format specification, so it requires 3 binding +1 votes from PMC members (excluding the proposer) and a minimum 72-hour voting period, weekends excluded, before it can merge. Vote by approving this PR (+1) or requesting changes (−1, a veto). See the voting process. Status: ❌ Blocked — 0 of 3 required approvals
Updated automatically by the format-spec vote gate, which re-checks every 15 minutes — just voted? Re-check now (press Run workflow; leave the input blank to re-check every open format PR). A PMC member may apply the |
A vector index segment records its build parameters so an engine can rebuild it without reading the index files, but the IVF partition count the index was asked for is not among them. An index rebuilt from its details is auto-sized instead, so one created with an explicit count comes back a different shape. `num_partitions` records that count. Absent means none was requested and the partitioning was derived from the data, which a rebuild derives again. Present means a rebuild starts from the recorded count, and may still train fewer partitions when the data cannot support them.
A count that was asked for is not the count an index holds, and the name should say so: `target_num_partitions` pairs with `target_partition_size`, the other way of asking for a partitioning. States which of the two wins when both are set.
A count a writer cannot represent must not be recorded as a truncated value, and a reader must not derive a partitioning from zero partitions.
7c63b2c to
d4cd920
Compare
Absence means no count is recorded, which a writer predating the field also produces, so it cannot establish that the original build sized automatically. The actual count may differ from the request rather than always being smaller, and the request is not the only expression of partitioning intent.
westonpace
left a comment
There was a problem hiding this comment.
What's the goal here? I worry that target_num_partitions can become a bad target as a table grows. For example, let's say I have 100Ki rows and so I want 25 partitions and I set target_num_partitions=25. Now my table grows to 200Ki rows. Should I still respect target_num_partitions=25?
Maybe it makes more sense to just think of num_partitions as an alternate way of specifying target_partition_size. For example, if I create an index with num_partitions=25 and there are 100Ki rows then I store target_partition_size=4096. If there are 200Ki rows then I store target_partition_size=8192.
| `runtime_hints` carries optional build preferences that do not affect index | ||
| structure, keyed by reverse-DNS name. Unrecognized keys must be silently ignored. |
There was a problem hiding this comment.
It might be nice to give some examples here (there are examples in the protobuf message comment we can copy over)
| reading the index files: the distance metric, the quantization scheme, and the | ||
| partitioning the index was asked for. | ||
|
|
||
| `target_num_partitions` records the IVF partition count requested for building or |
There was a problem hiding this comment.
It's odd that we use target_num_partitions here and num_partitions in all other contexts.
|
+1 to Weston's comment. We excluded |
There was a problem hiding this comment.
🟡 Gate recommendation: maintainer decision required.
The latest maintainer feedback confirms that num_partitions was intentionally excluded because a fixed count becomes a poor target as the dataset grows. The deferred-training case still needs a durable statement of creation intent, but the format contract now has two materially different choices: persist target_num_partitions as a fixed rebuild target that preserves the exact requested shape, or convert the creation-time count into target_partition_size so later rebuilds adapt as the table grows. Maintainers need to choose which invariant should survive rebuilds before this field is made part of the format.
Proposes recording the IVF partition count a vector index was asked to train, as
target_num_partitionsonVectorIndexDetails.A vector index segment records its build parameters in
index_detailsso an engine can rebuild the index without reading the index files. The partition count is not among them:target_partition_sizerecords a target size per partition, and nothing records a requested count. An index rebuilt from its details is therefore auto-sized from the data, so an index created as IVF-8 comes back with whatever count the data suggests.This matters most for a segment that covers no fragments. Such a segment carries its details with no index files at all, so the recorded parameters are the only statement of what the index is to become once its column holds enough vectors to train — and without the count it cannot reliably preserve the requested shape however far the table grows. That is the case behind #4034, where creating a vector index on a table too small to train its quantizer records the definition and fills it in later.
The field is a request, not a description of the index that was built. A build trains fewer partitions when the data cannot support the count asked for, so the partitions an index holds are read from the index itself. The name pairs with
target_partition_size, the other way of asking for a partitioning, and the spec states that an explicit count wins when both are set. Absent means no count was requested, and a rebuild derives the partitioning from the data again.runtime_hintsis not the place for this. It is specified as optional build preferences that do not affect index structure, and requires that unrecognized keys be silently ignored. A partition count is exactly index structure — it is the number that defines the index's shape — so recording it there would contradict the field's stated contract and make an ignorable key load-bearing for readers that do recognize it.A recorded count is at least 1. Zero partitions already fails badly on the build
path — KMeans indexes into an empty cluster list and panics — and a count is
easy to compute as zero, since
rows / targetis zero on a small table. What arecorded count would add is distance: a definition writes no index, so nothing
rejects the count before the details record it, and the failure would surface on
a later rebuild instead of at the call that set it. The spec therefore requires a
reader to treat zero as no request, and a writer to record nothing rather than a
truncated value.
Field number 10 rather than 9's successor 7: the existing
compressiononeof skips 7 and the message carries noreservedstatement, so I left the gap alone rather than assume it is free.The new field is set nowhere in this PR; the five exhaustive struct literals updated are the minimum needed to compile. Reading and writing it lands in a follow-up alongside #9134.