#952·qmd

vsearch takes ~70 s on an 11k-vector collection: exact scan runs per expanded query, expansions not deduplicated

Author: jowitteCreated Sep 14, 2026Updated Sep 17, 2026

Summary

qmd vsearch takes ~70 s on a single ~11k-vector collection. Two things multiply:

  1. Collection-scoped searches use the exact-scan path (exactVecScanByHashSeq, added for #791/#803) whenever the collection has ≤ COLLECTION_VEC_EXACT_SCAN_MAX (20,000) vectors. It computes vec_distance_cosine(embedding, ?) over hash_seq IN (…) lists, so sqlite-vec reads each vector individually from the chunk blobs via overflow pages.
  2. Expanded queries are not deduplicated. vectorSearchQuery runs the exact scan once per expansion. For my query, the cached expansion had 31 vec/hyde entries, only 9 of them distinct, so 32 scans instead of 10.

Environment

  • qmd 2.8.3 (bun global), node-llama-cpp 3.20.0, Apple M5 Max, macOS 26.6.2
  • Default collections: akasha (11,390 vectors, 5,787 docs) and fumu-ch (56 vectors)
  • vectors_vec holds 52,877 rows in total (the rest belong to inactive/old documents); index.sqlite 290 MB

Measurement

qmd vsearch "Delegieren" -n 4, no other qmd process running, expansion already cached. Timestamps per stderr/stdout line:

    0.1s Searching 32 vector queries...
    0.3s [node-llama-cpp] ggml_metal_library_init_from_source: error compiling source
   70.9s qmd://akasha/...   (first result)

So ~70 s is spent in the vector phase, ~2.2 s per query. Embedding the queries themselves takes a few ms each on Metal.

A sample of the process during that phase shows the main thread almost entirely in sqlite-vec column reads:

sqlite3_step
  sqlite3VdbeExec
    vec0Column
      vec0_get_vector_data
        blobReadWrite
          accessPayload
            getOverflowPage
              btreeGetPage → readDbPage → unixRead → pread

Possible directions (just ideas)

  • Deduplicate queryTexts in vectorSearchQuery (and probably in the hybrid path) before searching.
  • Try capped ANN (k up to 4096) plus post-filter first, and fall back to the exact scan only when that yields fewer than limit results for the collection. That keeps the #791/#803 fix for small collections without paying the full scan for larger ones.
  • Alternatively, reconsider the 20,000 threshold, since per-row reads from vec0 are much more expensive than the ANN scan.

Related: #791, #803.