Repository navigation
Add vector_index virtual table - #3
Merged
Merged
Conversation
v1.4.2 shadowed err in resultError, so errors returned from SQL functions were silently dropped. The fix (zombiezen/go-sqlite#133) is merged but not yet tagged, so this pins the main pseudo-version. Un-skips the error tests that were blocked on it and removes the obsolete stub-error test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PTvMD95uUfGT1jasgFsLdP
vector_index stores vectors in chunks of 1024 inside shadow tables and answers k-nearest neighbor queries by reading whole chunks and scanning them in Go across GOMAXPROCS goroutines, instead of evaluating a SQL function per row. Supports float32 and int8 storage, k = ? and LIMIT (with OFFSET), rowid lookups, full scans, INSERT/UPDATE/DELETE, rename, and drop. Reconnecting with a different dimension or quantization range is an error. SIFT1M p50 (k=100, default settings): 139 ms float32 and 46 ms int8, versus 363 ms and 336 ms for the scalar functions. Inserting 1M vectors takes 9.8 s (float32) and 6.5 s (int8). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PTvMD95uUfGT1jasgFsLdP
Chunks held a fixed 1024 vectors, which is 512 KiB at 128 dimensions but 6 MiB at 1536. A sweep over 128, 768, and 1536 dimensions and chunk data sizes from 64 KiB to 4 MiB found search fastest near 1 MiB and inserts 4.5-16x slower at 4 MiB than at 1 MiB, for both float32 and int8. The default capacity is now max(1, 1 MiB / vector bytes). A chunk_size=N argument overrides it. Both are stored in name_info at creation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PTvMD95uUfGT1jasgFsLdP
justintout
added a commit
that referenced
this pull request
Sep 14, 2026
* add SIFT1M benchmark and comparison Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PTvMD95uUfGT1jasgFsLdP * label int8 SIFT1M row as quantized scan Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PTvMD95uUfGT1jasgFsLdP * compute distances directly on blobs vector_distance decoded both arguments into new []float32 slices for every row scanned. It now decodes values inside an unrolled distance loop with no allocations. vector_distance_q computes the distance from int8 differences and scales once, since dequantization is affine; dequantize is removed. On SIFT1M (1M x 128, k=100, single thread) p50 query latency drops from 733 ms to 362 ms for float32 and from 659 ms to 339 ms for int8, with unchanged recall. README adds a PRAGMA mmap_size tip (246 ms float32). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PTvMD95uUfGT1jasgFsLdP * remove PLAN.md The implementation plan is complete. CLAUDE.md drops the rules that depended on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PTvMD95uUfGT1jasgFsLdP * Add vector_index virtual table (#3) * bump zombiezen.com/go/sqlite for function error fix v1.4.2 shadowed err in resultError, so errors returned from SQL functions were silently dropped. The fix (zombiezen/go-sqlite#133) is merged but not yet tagged, so this pins the main pseudo-version. Un-skips the error tests that were blocked on it and removes the obsolete stub-error test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PTvMD95uUfGT1jasgFsLdP * add vector_index virtual table vector_index stores vectors in chunks of 1024 inside shadow tables and answers k-nearest neighbor queries by reading whole chunks and scanning them in Go across GOMAXPROCS goroutines, instead of evaluating a SQL function per row. Supports float32 and int8 storage, k = ? and LIMIT (with OFFSET), rowid lookups, full scans, INSERT/UPDATE/DELETE, rename, and drop. Reconnecting with a different dimension or quantization range is an error. SIFT1M p50 (k=100, default settings): 139 ms float32 and 46 ms int8, versus 363 ms and 336 ms for the scalar functions. Inserting 1M vectors takes 9.8 s (float32) and 6.5 s (int8). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PTvMD95uUfGT1jasgFsLdP * size vector_index chunks by bytes and add chunk_size Chunks held a fixed 1024 vectors, which is 512 KiB at 128 dimensions but 6 MiB at 1536. A sweep over 128, 768, and 1536 dimensions and chunk data sizes from 64 KiB to 4 MiB found search fastest near 1 MiB and inserts 4.5-16x slower at 4 MiB than at 1 MiB, for both float32 and int8. The default capacity is now max(1, 1 MiB / vector bytes). A chunk_size=N argument overrides it. Both are stored in name_info at creation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PTvMD95uUfGT1jasgFsLdP --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #2. Review that first; this PR's diff is only the commits below.
Summary
zombiezen.com/go/sqlitetov1.4.3-0.20260620165750-ca2041ed3dd2. This picks up Fix variable shadowing in resultError that silently swallows function errors zombiezen/go-sqlite#133, which fixes errors returned from SQL functions being silently dropped. The fix is merged but not yet tagged. Since v1.4.2 there are six commits: the fix, one docs change and four CI updates. The modernc versions are unchanged. This commit un-skips the 12 error tests blocked on the bug and removes an obsolete test that expected placeholder functions to error.vector_indexvirtual table. It stores vectors in chunks of about 1 MiB inside shadow tables and answers k-nearest neighbor queries by scanning whole chunks in Go onGOMAXPROCSgoroutines. It avoids a SQL function call for every row.Usage
k = ?or asLIMIT, optionally withOFFSET.LIMITthrough joins, so a join must usekor a subquery. A join that relies onLIMITalone returns an error saying so.Chunk size
max(1, 1 MiB / vectorBytes)vectors per chunk.chunk_size=Noverrides it. Both are stored when the table is created.Storage
name_info: dimension, storage type, quantization range and chunk size.name_chunks: chunk id and occupied size. It is kept separate from the data because updating a row rewrites its blobs. The first version kept them together and took 5m44s to insert SIFT1M.name_data: one blob of rowids and one blob of vectors per chunk.name_rowids: maps each rowid to its chunk and slot.A delete moves the chunk's last vector into the freed slot, so chunks never have holes.
Results
SIFT1M: 1M vectors × 128 dimensions, k=100, Apple M3 Max, p50.
mmap_size4 GiBvector_distancevector_distance_qvector_indexvector_index(int8)vec0(reference)vector_indexsearches use all cores. The other rows are single-threaded.Known limitations
embeddingcolumn costs one lookup per row. Searches that return onlyrowidanddistancedo not pay this.Testing
go vet ./...andgo vet -tags sift ./...go test -race ./...vector_index_test.gocompares search results and distances againstvector_distance/vector_distance_qbrute force, for float32 and int8, across more than two chunks. It coversk,LIMIT,OFFSET, joins, deletes, updates, rowid changes, rollback, stored-embedding round trips, error cases (checking that failed statements leave rows intact), reopening, rename and drop, and default and explicit chunk sizes.go test -tags sift -run TestSIFT1M -timeout 0 -v -sift.dir .tmp/sift🤖 Generated with Claude Code
https://claude.ai/code/session_01PTvMD95uUfGT1jasgFsLdP