Skip to content

Commit 0077599

Browse files
craig[bot]andy-kimball
andcommitted
Merge #144525
144525: vecindex: add settings and storage options for vector indexes r=drewkimball,mw5h a=andy-kimball #### vecindex: disable vector indexes by default For 25.2, vector indexes are shipping as a public preview, but disabled until users set the cluster setting feature.vector_index.enabled=true. Creating a vector index gives an error until then. Epic: CRDB-42943 Release note: None #### vecindex: add vector-search-beam-size session setting Add a new "vector-search-beam-size" session setting that controls the breadth of the vector index search. The higher the value, the more accurate is the search, but the more processing it requires. Epic: CRDB-42943 Release note: None #### vecindex: offer vector index storage params Add support for several storage parameters when creating a vector index: * build_beam_size: controls accuracy of index build * min_partition_size: min size of partition without being merged * max_partition_size: max size of partition without being split Here is an example: CREATE VECTOR INDEX ON storage_params (v) WITH (min_partition_size = 8, max_partition_size = 64) Epic: CRDB-42943 Release note: None #### vecindex: hook up StalledOpTimeout Hook up the vector index's stalled op timeout callback to the cluster setting. While the setting was added in a previous PR, it was never being used. Epic: CRDB-42943 Release note: None Co-authored-by: Andrew Kimball <[email protected]>
2 parents 939463d + 1d5cda9 commit 0077599

File tree

38 files changed

+554
-116
lines changed

38 files changed

+554
-116
lines changed

docs/generated/settings/settings-for-tenants.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ feature.import.enabled boolean true set to true to enable imports, false to disa
7575
feature.restore.enabled boolean true set to true to enable restore, false to disable; default is true application
7676
feature.schema_change.enabled boolean true set to true to enable schema changes, false to disable; default is true application
7777
feature.stats.enabled boolean true set to true to enable CREATE STATISTICS/ANALYZE, false to disable; default is true application
78+
feature.vector_index.enabled boolean false set to true to enable vector indexes, false to disable; default is false application
7879
jobs.retention_time duration 336h0m0s the amount of time for which records for completed jobs are retained application
7980
kv.bulk_sst.target_size byte size 16 MiB target size for SSTs emitted from export requests; export requests (i.e. BACKUP) may buffer up to the sum of kv.bulk_sst.target_size and kv.bulk_sst.max_allowed_overage in memory system-visible
8081
kv.closed_timestamp.follower_reads.enabled (alias: kv.closed_timestamp.follower_reads_enabled) boolean true allow (all) replicas to serve consistent historical reads based on closed timestamp information system-visible

docs/generated/settings/settings.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
<tr><td><div id="setting-feature-restore-enabled" class="anchored"><code>feature.restore.enabled</code></div></td><td>boolean</td><td><code>true</code></td><td>set to true to enable restore, false to disable; default is true</td><td>Serverless/Dedicated/Self-Hosted</td></tr>
8181
<tr><td><div id="setting-feature-schema-change-enabled" class="anchored"><code>feature.schema_change.enabled</code></div></td><td>boolean</td><td><code>true</code></td><td>set to true to enable schema changes, false to disable; default is true</td><td>Serverless/Dedicated/Self-Hosted</td></tr>
8282
<tr><td><div id="setting-feature-stats-enabled" class="anchored"><code>feature.stats.enabled</code></div></td><td>boolean</td><td><code>true</code></td><td>set to true to enable CREATE STATISTICS/ANALYZE, false to disable; default is true</td><td>Serverless/Dedicated/Self-Hosted</td></tr>
83+
<tr><td><div id="setting-feature-vector-index-enabled" class="anchored"><code>feature.vector_index.enabled</code></div></td><td>boolean</td><td><code>false</code></td><td>set to true to enable vector indexes, false to disable; default is false</td><td>Serverless/Dedicated/Self-Hosted</td></tr>
8384
<tr><td><div id="setting-jobs-retention-time" class="anchored"><code>jobs.retention_time</code></div></td><td>duration</td><td><code>336h0m0s</code></td><td>the amount of time for which records for completed jobs are retained</td><td>Serverless/Dedicated/Self-Hosted</td></tr>
8485
<tr><td><div id="setting-kv-allocator-lease-rebalance-threshold" class="anchored"><code>kv.allocator.lease_rebalance_threshold</code></div></td><td>float</td><td><code>0.05</code></td><td>minimum fraction away from the mean a store&#39;s lease count can be before it is considered for lease-transfers</td><td>Dedicated/Self-Hosted</td></tr>
8586
<tr><td><div id="setting-kv-allocator-load-based-lease-rebalancing-enabled" class="anchored"><code>kv.allocator.load_based_lease_rebalancing.enabled</code></div></td><td>boolean</td><td><code>true</code></td><td>set to enable rebalancing of range leases based on load and latency</td><td>Dedicated/Self-Hosted</td></tr>

pkg/cmd/vecbench/main.go

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ var flagHideProgress = flag.Bool("hide-progress", false, "Hide progress during i
6565
// Search command options.
6666
var flagMaxResults = flag.Int("k", 10, "Number of search results, used in recall calculation.")
6767
var flagBeamSize = flag.Int(
68-
"default-beam-size",
68+
"build-beam-size",
6969
8,
70-
"Default beam size used for building and searching the index.")
70+
"Default beam size used for building the index.")
7171
var flagSearchBeamSizes = flag.String(
7272
"search-beam-sizes",
7373
"1,2,4,8,16,32,64,128,256,512",
@@ -248,30 +248,37 @@ func (vb *vectorBench) SearchIndex() {
248248
}
249249

250250
doSearch := func(beamSize int) {
251+
// Prepare to search.
252+
maxResults := *flagMaxResults
253+
state, err := vb.provider.SetupSearch(vb.ctx, maxResults, beamSize)
254+
if err != nil {
255+
panic(err)
256+
}
257+
251258
start := timeutil.Now()
252259

253260
// Search for test vectors.
254-
var sumMAP, sumVectors, sumLeafVectors, sumFullVectors, sumPartitions float64
261+
var sumRecall, sumVectors, sumLeafVectors, sumFullVectors, sumPartitions float64
255262
count := data.Test.Count
256-
for i := 0; i < count; i++ {
263+
for i := range count {
257264
// Calculate truth set for the vector.
258265
queryVector := data.Test.At(i)
259266

260267
var stats cspann.SearchStats
261-
prediction, err := vb.provider.Search(vb.ctx, queryVector, *flagMaxResults, beamSize, &stats)
268+
prediction, err := vb.provider.Search(vb.ctx, state, queryVector, &stats)
262269
if err != nil {
263270
panic(err)
264271
}
265272

266-
primaryKeys := make([]byte, len(prediction)*4)
267-
truth := make([]cspann.KeyBytes, len(prediction))
268-
for neighbor := 0; neighbor < len(prediction); neighbor++ {
273+
primaryKeys := make([]byte, maxResults*4)
274+
truth := make([]cspann.KeyBytes, maxResults)
275+
for neighbor := range maxResults {
269276
primaryKey := primaryKeys[neighbor*4 : neighbor*4+4]
270277
binary.BigEndian.PutUint32(primaryKey, uint32(data.Neighbors[i][neighbor]))
271278
truth[neighbor] = primaryKey
272279
}
273280

274-
sumMAP += findMAP(prediction, truth)
281+
sumRecall += calculateRecall(prediction, truth)
275282
sumVectors += float64(stats.QuantizedVectorCount)
276283
sumLeafVectors += float64(stats.QuantizedLeafVectorCount)
277284
sumFullVectors += float64(stats.FullVectorCount)
@@ -280,7 +287,7 @@ func (vb *vectorBench) SearchIndex() {
280287

281288
elapsed := timeutil.Since(start)
282289
fmt.Printf("%d\t%0.2f%%\t%0.0f\t%0.0f\t%0.2f\t%0.2f\t%0.2f\n",
283-
beamSize, sumMAP/float64(count)*100,
290+
beamSize, sumRecall/float64(count)*100,
284291
sumLeafVectors/float64(count), sumVectors/float64(count),
285292
sumFullVectors/float64(count), sumPartitions/float64(count),
286293
float64(count)/elapsed.Seconds())
@@ -289,14 +296,14 @@ func (vb *vectorBench) SearchIndex() {
289296
fmt.Println()
290297
fmt.Printf(White+"%s\n"+Reset, vb.datasetName)
291298
fmt.Printf(
292-
White+"%d train vectors, %d test vectors, %d dimensions, %d/%d min/max partitions, base beam size %d\n"+Reset,
299+
White+"%d train vectors, %d test vectors, %d dimensions, %d/%d min/max partitions, build beam size %d\n"+Reset,
293300
data.Count, data.Test.Count, data.Test.Dims,
294301
minPartitionSize, maxPartitionSize, *flagBeamSize)
295302
fmt.Println(vb.provider.FormatStats())
296303

297304
fmt.Printf("beam\trecall\tleaf\tall\tfull\tpartns\tqps\n")
298305

299-
// Search multiple times with different beam sizes.
306+
// Search multiple times with different search beam sizes.
300307
beamSizeStrs := strings.Split(*flagSearchBeamSizes, ",")
301308
for i := range beamSizeStrs {
302309
beamSize, err := strconv.Atoi(beamSizeStrs[i])
@@ -626,15 +633,11 @@ func loadDataset(fileName string) dataset {
626633
return data
627634
}
628635

629-
// findMAP returns mean average precision, which compares a set of predicted
630-
// results with the true set of results. Both sets are expected to be of equal
631-
// length. It returns the percentage overlap of the predicted set with the truth
632-
// set.
633-
func findMAP(prediction, truth []cspann.KeyBytes) float64 {
634-
if len(prediction) != len(truth) {
635-
panic(errors.AssertionFailedf("prediction and truth sets are not same length"))
636-
}
637-
636+
// calculateRecall returns the percentage overlap of the predicted set with the
637+
// truth set. If the predicted set has fewer items than the truth set, it is
638+
// treated as if the predicted set has missing/incorrect items that reduce the
639+
// recall rate.
640+
func calculateRecall(prediction, truth []cspann.KeyBytes) float64 {
638641
predictionMap := make(map[string]bool, len(prediction))
639642
for _, p := range prediction {
640643
predictionMap[string(p)] = true

pkg/cmd/vecbench/mem_provider.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ import (
2323

2424
const seed = 42
2525

26+
// MemSearchState holds prepared search state.
27+
type MemSearchState struct {
28+
beamSize int
29+
maxResults int
30+
}
31+
32+
// Close is a no-op for the MemProvider.
33+
func (s *MemSearchState) Close() {
34+
}
35+
2636
// MemProvider implements VectorProvider using an in-memory store.
2737
type MemProvider struct {
2838
stopper *stop.Stopper
@@ -124,16 +134,24 @@ func (m *MemProvider) InsertVectors(
124134
})
125135
}
126136

137+
// SetupSearch implements the VectorProvider interface.
138+
func (m *MemProvider) SetupSearch(
139+
ctx context.Context, maxResults int, beamSize int,
140+
) (SearchState, error) {
141+
return &MemSearchState{maxResults: maxResults, beamSize: beamSize}, nil
142+
}
143+
127144
// Search implements the VectorProvider interface.
128145
func (m *MemProvider) Search(
129-
ctx context.Context, vec vector.T, maxResults int, beamSize int, stats *cspann.SearchStats,
146+
ctx context.Context, state SearchState, vec vector.T, stats *cspann.SearchStats,
130147
) (keys []cspann.KeyBytes, err error) {
148+
memState := state.(*MemSearchState)
131149
err = m.store.RunTransaction(ctx, func(txn cspann.Txn) error {
132150
// Search the store.
133151
var idxCtx cspann.Context
134152
idxCtx.Init(txn)
135-
searchSet := cspann.SearchSet{MaxResults: maxResults}
136-
searchOptions := cspann.SearchOptions{BaseBeamSize: beamSize}
153+
searchSet := cspann.SearchSet{MaxResults: memState.maxResults}
154+
searchOptions := cspann.SearchOptions{BaseBeamSize: memState.beamSize}
137155
err = m.index.Search(ctx, &idxCtx, nil /* treeKey */, vec, &searchSet, searchOptions)
138156
if err != nil {
139157
return err

pkg/cmd/vecbench/sql_provider.go

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ import (
2424
"github.com/jackc/pgx/v5/pgxpool"
2525
)
2626

27+
// SQLSearchState holds an open connection and a prepared statement to run on
28+
// that connection in repeated calls to Search.
29+
type SQLSearchState struct {
30+
conn *pgxpool.Conn
31+
query string
32+
}
33+
34+
// Close releases the connection back to the pool.
35+
func (s *SQLSearchState) Close() {
36+
if s.conn != nil {
37+
s.conn.Release()
38+
s.conn = nil
39+
}
40+
}
41+
2742
// SQLProvider implements VectorProvider using a SQL database connection to a
2843
// CockroachDB instance.
2944
type SQLProvider struct {
@@ -135,7 +150,7 @@ func (s *SQLProvider) InsertVectors(
135150
batch := &pgx.Batch{}
136151

137152
// Insert vectors in batches.
138-
for i := 0; i < vectors.Count; i++ {
153+
for i := range vectors.Count {
139154
batch.Queue(fmt.Sprintf(
140155
"INSERT INTO %s (id, embedding) VALUES ($1, $2)",
141156
s.tableName),
@@ -165,20 +180,54 @@ func (s *SQLProvider) InsertVectors(
165180
}
166181
}
167182

168-
// Search implements the VectorProvider interface.
169-
func (s *SQLProvider) Search(
170-
ctx context.Context, vec vector.T, maxResults int, beamSize int, stats *cspann.SearchStats,
171-
) ([]cspann.KeyBytes, error) {
172-
// Construct a query that searches for similar vectors.
183+
// SetupSearch implements the VectorProvider interface.
184+
func (s *SQLProvider) SetupSearch(
185+
ctx context.Context, maxResults int, beamSize int,
186+
) (SearchState, error) {
187+
// Acquire a connection from the pool.
188+
conn, err := s.pool.Acquire(ctx)
189+
if err != nil {
190+
return nil, errors.Wrap(err, "acquiring connection from pool")
191+
}
192+
defer func() {
193+
if conn != nil {
194+
conn.Release()
195+
}
196+
}()
197+
198+
// Set the vector_search_beam_size session variable.
199+
_, err = conn.Exec(ctx, fmt.Sprintf("SET vector_search_beam_size = %d", beamSize))
200+
if err != nil {
201+
return nil, errors.Wrap(err, "setting vector_search_beam_size")
202+
}
203+
204+
// Construct the query for vector search.
173205
query := fmt.Sprintf(`
174206
SELECT id
175207
FROM %s
176208
ORDER BY embedding <-> $1
177-
LIMIT $2
178-
`, s.tableName)
209+
LIMIT %d
210+
`, s.tableName, maxResults)
211+
212+
state := &SQLSearchState{
213+
conn: conn,
214+
query: query,
215+
}
216+
conn = nil
217+
return state, nil
218+
}
219+
220+
// Search implements the VectorProvider interface.
221+
func (s *SQLProvider) Search(
222+
ctx context.Context, state SearchState, vec vector.T, stats *cspann.SearchStats,
223+
) ([]cspann.KeyBytes, error) {
224+
sqlState, ok := state.(*SQLSearchState)
225+
if !ok {
226+
return nil, errors.New("invalid search state type")
227+
}
179228

180-
// Execute the query.
181-
rows, err := s.pool.Query(ctx, query, vec, maxResults)
229+
// Execute the prepared statement.
230+
rows, err := sqlState.conn.Query(ctx, sqlState.query, vec)
182231
if err != nil {
183232
return nil, errors.Wrap(err, "executing search query")
184233
}

pkg/cmd/vecbench/vector_provider.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ type IndexMetric struct {
2121
Value float64
2222
}
2323

24+
// SearchState holds prepared state needed by a vector provider across multiple
25+
// calls to Search.
26+
type SearchState interface {
27+
// Close releases any resources held by the search state.
28+
Close()
29+
}
30+
2431
// VectorProvider abstracts the operations needed for vector storage and
2532
// retrieval. This allows different implementations (in-memory, SQL-based, etc.)
2633
// to provide the functionality needed by vecbench.
@@ -47,11 +54,16 @@ type VectorProvider interface {
4754
// identified by a key.
4855
InsertVectors(ctx context.Context, keys []cspann.KeyBytes, vectors vector.Set) error
4956

57+
// SetupSearch allows the provider to perform expensive up-front steps in
58+
// preparation for many calls to Search. It returns provider-specific state
59+
// that will be passed to Search.
60+
SetupSearch(ctx context.Context, maxResults int, beamSize int) (SearchState, error)
61+
5062
// Search searches for vectors similar to the query vector. It returns the
5163
// keys of the most similar vectors. If supported, stats are recorded in
5264
// "stats" for this search.
5365
Search(
54-
ctx context.Context, vec vector.T, maxResults int, beamSize int, stats *cspann.SearchStats,
66+
ctx context.Context, state SearchState, vec vector.T, stats *cspann.SearchStats,
5567
) ([]cspann.KeyBytes, error)
5668

5769
// GetMetrics returns interesting metrics for the vector index. Each provider

pkg/sql/backfill/backfill_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ func TestVectorColumnAndIndexBackfill(t *testing.T) {
5151
defer srv.Stopper().Stop(ctx)
5252
sqlDB := sqlutils.MakeSQLRunner(db)
5353

54+
// Enable vector indexes.
55+
sqlDB.Exec(t, `SET CLUSTER SETTING feature.vector_index.enabled = true`)
56+
5457
// Create a table with a vector column
5558
sqlDB.Exec(t, `
5659
CREATE TABLE vectors (
@@ -115,6 +118,9 @@ func TestConcurrentOperationsDuringVectorIndexCreation(t *testing.T) {
115118
defer srv.Stopper().Stop(ctx)
116119
sqlDB := sqlutils.MakeSQLRunner(db)
117120

121+
// Enable vector indexes.
122+
sqlDB.Exec(t, `SET CLUSTER SETTING feature.vector_index.enabled = true`)
123+
118124
// Create a table with a vector column
119125
sqlDB.Exec(t, `
120126
CREATE TABLE vectors (

pkg/sql/catalog/catformat/index.go

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,18 @@ func formatStorageConfigs(
275275
table catalog.TableDescriptor, index *descpb.IndexDescriptor, f *tree.FmtCtx,
276276
) error {
277277
numCustomSettings := 0
278+
writeCustomSetting := func(key, val string) {
279+
if numCustomSettings > 0 {
280+
f.WriteString(", ")
281+
} else {
282+
f.WriteString(" WITH (")
283+
}
284+
numCustomSettings++
285+
f.WriteString(key)
286+
f.WriteString("=")
287+
f.WriteString(val)
288+
}
289+
278290
if index.GeoConfig.S2Geometry != nil || index.GeoConfig.S2Geography != nil {
279291
var s2Config *geopb.S2Config
280292

@@ -297,15 +309,7 @@ func formatStorageConfigs(
297309
{`s2_max_cells`, s2Config.MaxCells, defaultS2Config.MaxCells},
298310
} {
299311
if check.val != check.defaultVal {
300-
if numCustomSettings > 0 {
301-
f.WriteString(", ")
302-
} else {
303-
f.WriteString(" WITH (")
304-
}
305-
numCustomSettings++
306-
f.WriteString(check.key)
307-
f.WriteString("=")
308-
f.WriteString(strconv.Itoa(int(check.val)))
312+
writeCustomSetting(check.key, strconv.Itoa(int(check.val)))
309313
}
310314
}
311315
}
@@ -332,29 +336,26 @@ func formatStorageConfigs(
332336
{`geometry_max_y`, cfg.MaxY, defaultConfig.S2Geometry.MaxY},
333337
} {
334338
if check.val != check.defaultVal {
335-
if numCustomSettings > 0 {
336-
f.WriteString(", ")
337-
} else {
338-
f.WriteString(" WITH (")
339-
}
340-
numCustomSettings++
341-
f.WriteString(check.key)
342-
f.WriteString("=")
343-
f.WriteString(strconv.FormatFloat(check.val, 'f', -1, 64))
339+
writeCustomSetting(check.key, strconv.FormatFloat(check.val, 'f', -1, 64))
344340
}
345341
}
346342
}
347343
}
348344

349-
if index.IsSharded() {
350-
if numCustomSettings > 0 {
351-
f.WriteString(", ")
352-
} else {
353-
f.WriteString(" WITH (")
345+
if index.Type == idxtype.VECTOR {
346+
if index.VecConfig.BuildBeamSize != 0 {
347+
writeCustomSetting(`build_beam_size`, strconv.Itoa(int(index.VecConfig.BuildBeamSize)))
354348
}
355-
f.WriteString(`bucket_count=`)
356-
f.WriteString(strconv.FormatInt(int64(index.Sharded.ShardBuckets), 10))
357-
numCustomSettings++
349+
if index.VecConfig.MinPartitionSize != 0 {
350+
writeCustomSetting(`min_partition_size`, strconv.Itoa(int(index.VecConfig.MinPartitionSize)))
351+
}
352+
if index.VecConfig.MaxPartitionSize != 0 {
353+
writeCustomSetting(`max_partition_size`, strconv.Itoa(int(index.VecConfig.MaxPartitionSize)))
354+
}
355+
}
356+
357+
if index.IsSharded() {
358+
writeCustomSetting(`bucket_count`, strconv.FormatInt(int64(index.Sharded.ShardBuckets), 10))
358359
}
359360

360361
if numCustomSettings > 0 {

0 commit comments

Comments
 (0)