Files
seaweedfs/weed/pb/vector.proto
T
Chris LuandClaude Opus 5.5 4be191173a vector: keep ListVectors pages and segments flat as an index grows; read only keys in GetVectors (#12300)
* vector: scan only the fragments a ListVectors page covers

A page pushed its offset into the scan, but the scan still loaded every
fragment first, reading each one's deletion file: about 105 S3 GETs per
page at 50k rows against 25 at 10k. The page's fragments now come from the
live row counts in the manifest, so a page reads only its own. Tokens and
row order are unchanged; a manifest without the counts keeps the old scan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK

* s3vectors: list a ListVectors segment in the engine

The gateway paged through the whole index for every segment and kept one
row in segmentCount, so a segment cost segmentCount engine pages and 16
segments listed slower than one. The engine now takes segment_count and
segment_index: it reads keys across the stride, keeps the segment's rows
and takes their data and metadata alone, one call per page.

A segment is still the rows whose position modulo segmentCount is
segmentIndex, and its token still the position to resume from. An engine
that predates the fields does not set segmented, and the gateway then
pages through it as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK

* vector: read only the key column across the table in GetVectors

The key lookup scanned every column. On S3, lance reads a column during
the scan when a row of it is under 1000 bytes, so a 128-dimension vector
was read for every row of the table to return 100 of them, and data and
metadata were read even when the request did not ask for them. The scan
now projects the requested columns and materializes everything but the
key late, for the matching rows only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK

* s3vectors: start the legacy segment fallback from the page already read

With an engine that ignores segment_count, the gateway dropped the page
it had just read and fetched it again. The fallback now consumes that
page first, so a one-row segment page still costs one engine call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK

* vector: check metadata on segmented ListVectors pages

The segment test listed data only. Its rows now carry metadata on two in
three keys, and every segmented row, read through take_rows, must equal
the same row in a plain listing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK

* vector: lease the version while a segment takes its data

A segmented page with data or metadata reads keys first and then takes
the other columns from the same version, so maintenance could reclaim
its files in between. That path now holds a lease like GetVectors. A
keys-only page is one scan and stays unleased, as plain pages are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK

* vector: leave GetVectors materialization to lance

Forcing every column late made a 100-key GetVectors with data slower at
10k x 128 in the perf cluster: 54.7 ms against 46.3 ms. The take issued
a read per row, where lance's heuristic reads a narrow vector column in
one pass per fragment. The projection keeps the gain on its own: with
neither data nor metadata requested, the call went from 38.6 ms to
25.3 ms and from 150 to 89 S3 GETs. At 50k x 384 the vector is already
late under the heuristic, and a full lookup is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:57:44 -08:00

134 lines
3.3 KiB
Protocol Buffer

syntax = "proto3";
package vector_pb;
option go_package = "github.com/seaweedfs/seaweedfs/weed/pb/vector_pb";
import "google/protobuf/struct.proto";
// The shared contract between the Go S3 gateway and the vector query engine.
// The public S3 Vectors API stays an AWS contract; this RPC is the internal
// boundary the design doc gives both editions.
service VectorService {
rpc CreateIndex(CreateIndexRequest) returns (CreateIndexResponse);
rpc DropIndex(DropIndexRequest) returns (DropIndexResponse);
rpc PutVectors(PutVectorsRequest) returns (PutVectorsResponse);
rpc GetVectors(GetVectorsRequest) returns (GetVectorsResponse);
rpc DeleteVectors(DeleteVectorsRequest) returns (DeleteVectorsResponse);
rpc ListVectors(ListVectorsRequest) returns (ListVectorsResponse);
rpc QueryVectors(QueryVectorsRequest) returns (QueryVectorsResponse);
}
enum DistanceMetric {
DISTANCE_METRIC_UNSPECIFIED = 0;
L2 = 1;
COSINE = 2;
DOT_PRODUCT = 3;
}
// Table addresses a Lance table through the namespace: the id parts as the
// namespace catalog joins them.
message Table {
repeated string id = 1;
// Dataset version to pin reads to; 0 means latest.
uint64 version = 2;
}
message CreateIndexRequest {
Table table = 1;
uint32 dimension = 2;
DistanceMetric distance_metric = 3;
repeated string non_filterable_metadata_keys = 4;
}
message CreateIndexResponse {
uint64 version = 1;
}
message DropIndexRequest {
Table table = 1;
}
message DropIndexResponse {}
message Vector {
string key = 1;
repeated float data = 2;
// JSON-typed metadata: numbers carry double precision, as on the wire.
google.protobuf.Struct metadata = 3;
}
message PutVectorsRequest {
Table table = 1;
repeated Vector vectors = 2;
}
message PutVectorsResponse {
uint64 version = 1;
}
message GetVectorsRequest {
Table table = 1;
repeated string keys = 2;
bool return_data = 3;
bool return_metadata = 4;
}
message GetVectorsResponse {
repeated Vector vectors = 1;
}
message DeleteVectorsRequest {
Table table = 1;
repeated string keys = 2;
}
message DeleteVectorsResponse {
uint64 version = 1;
}
message ListVectorsRequest {
Table table = 1;
uint32 max_results = 2;
string next_token = 3;
bool return_data = 4;
bool return_metadata = 5;
// A nonzero segment_count lists only the rows whose position modulo
// segment_count is segment_index; next_token stays a row position.
uint32 segment_count = 6;
uint32 segment_index = 7;
}
message ListVectorsResponse {
repeated Vector vectors = 1;
string next_token = 2;
// Set when segment_count was applied; an older engine ignores it.
bool segmented = 3;
}
message QueryVectorsRequest {
Table table = 1;
repeated float query_vector = 2;
uint32 top_k = 3;
// Lance SQL filter; empty means unfiltered. Metadata is one JSON
// `metadata` column, so the gateway translates S3 Vectors filter
// expressions onto it before they land here.
string filter = 4;
// Lance's post-filter escape hatch; off prefilters, matching S3 Vectors.
bool postfilter = 5;
bool return_metadata = 6;
bool return_distance = 7;
bool return_data = 8;
}
message QueryResult {
string key = 1;
float distance = 2;
google.protobuf.Struct metadata = 3;
repeated float data = 4;
}
message QueryVectorsResponse {
repeated QueryResult vectors = 1;
}