SQLite is enough for vector search at my scale
After chunking, my notes turn into roughly 21,000 chunks. Every guide I read assumed I would now set up a vector database. I almost did, then did the arithmetic first.
The arithmetic
21,000 vectors × 384 dimensions × 4 bytes is about 32 MB. That fits in memory on anything. A brute-force search is one matrix-vector multiplication, which numpy does in a few milliseconds.
import numpy as np, sqlite3
db = sqlite3.connect("twin.db")
rows = db.execute("SELECT id, vec FROM chunks").fetchall()
ids = [r[0] for r in rows]
mat = np.frombuffer(b"".join(r[1] for r in rows), dtype=np.float32).reshape(len(rows), -1)
def search(query_vec, k=8):
scores = mat @ query_vec # vectors are normalised at insert time
top = np.argpartition(-scores, k)[:k]
return [ids[i] for i in top[np.argsort(-scores[top])]]
On my 2021 laptop that's 9 ms per query, including the sort. Embedding the query itself takes longer than searching.
What SQLite does for me
- Stores chunks, vectors (as BLOBs), file paths and modification times in one file.
- Full-text search with FTS5, which I combine with the vector score for exact terms like error codes and command names.
- Incremental updates: a note whose mtime changed gets re-chunked and re-embedded, everything else stays.
When I'd change my mind
Somewhere around a few million vectors, or if I needed filtering that has to happen inside the index.
Neither is going to happen with my notes. There's also the sqlite-vec extension, which I'll
probably try once it's in the Debian packages.