2026-05-17
RAG leggero: embedding, SQLite e quando evitare il vector DB
Tradeoff operativi per RAG su piccoli volumi: dimensione vettore, storage e soglie per restare senza vector database dedicato.
Un vector database dedicato (Pinecone, Weaviate, Qdrant cloud, …) ha senso quando servono milioni di vettori, filtri metadata complessi in millisecondi o team che non vogliono toccare storage. Per molti MVP con pochi migliaia di chunk, embeddings in-process + similarità su disco o in RAM sono sufficienti e riducono superficie operativa.
Range di N chunk e latenza
Ordine di grandezza (non benchmark certificati—profila sul tuo hardware):
- N < 5 000 chunk, embedding 1536 dim float32 ≈ 30 MB solo vettori; dot product o cosine in RAM è spesso < 10 ms su CPU moderna senza indice ANN.
- N tra 10k e 100k: senza indice ANN le scan lineari diventano p95 > 100 ms; serve HNSW/IVF (spesso fornito dal vector DB) o librerie come
hnswliblato server. - N > 100k con query interattive: valuta seriamente un motore vector o almeno un index serializzato on-disk.
La latenza dipende anche da batch embedding vs on-the-fly: cache degli embedding dei documenti, non ricalcolarli per ogni query se il corpus è statico.
Scelta dimensione embedding
Esempi da verificare sul catalogo aggiornato del provider:
- OpenAI
text-embedding-3-smalloffre dimensioni configurabili (es. 512 vs 1536) con tradeoff accuratezza/dimensione documentato nelle API embeddings. - Modelli open-weight (es.
bge,e5) espongono dimensione fissa—leggi la card del modello su Hugging Face.
Regola pratica: per N piccolo, dimensione più alta spesso migliora il recall; per N grande, compressione o product quantization ripaga prima di clusterizzare infra.
Storage: file, SQLite, niente vector SaaS
Tre pattern MVP:
- NumPy/Pickle o file
.npy+ manifest JSON con mappingchunk_id → offset. Funziona per demo; fragile per concorrenza scrittura. - Tabella SQL: colonne
chunk_id,embedding BLOB(float32 serializzati),content TEXT,metadata JSON. Query: caricare candidati per filtro metadata, poi similarità in app solo su quel sottoinsieme—evita scan globali se puoi pre-filtrare persource_id. - SQLite + estensione vector (es.
sqlite-vec): unifica ACID + ANN se l’estensione è accettabile nel deploy (Nitro/Node con load extension abilitato dove permesso).
Quando evitare vector DB dedicato: team piccolo, N sotto le soglie sopra, niente requisito di multi-region replica per gli embedding, budget ops limitato.
Quando migrare
Segnali oggettivi:
- p95 retrieval > budget prodotto (es. 200 ms) anche dopo batch e cache.
- Corpus cresce ogni settimana oltre la soglia che hai profilato.
- Serve hybrid search (BM25 + vector) gestito da un unico servizio.
- Più applicazioni devono condividere lo stesso index con ACL diversi.
Anti-pattern: RAG senza eval
Se non misuri hit rate o MRR su un set di domande fisse, non sai se il “RAG leggero” funziona. Tenere 20–50 query golden con chunk attesi e rilanciare lo script dopo ogni cambio embedding o splitter è il minimo sindacale.
Pseudocodice top-k (cosine, RAM)
def top_k(query_vec, matrix, k):
# matrix: float32 [n, d], riga L2-normalizzata
scores = matrix @ query_vec # [n]
return argpartition(scores, -k)[-k:]
Normalizza query e righe se usi dot product al posto di cosine esplicito.
sqlite-vec: vettori dentro SQLite senza Pinecone
Il progetto sqlite-vec (Alex Garcia) è un’estensione SQLite in C che aggiunge tipi vettoriali (vec0 virtual table), funzioni di distanza (vec_distance_cosine, vec_distance_L2, …) e query KNN stile … match :query and k = 10. Utile quando vuoi un solo file DB con metadati + ANN senza servizio esterno. Nota: è dichiarato pre-v1—previsti breaking tra release; blocca la versione dell’estensione come faresti per ogni dipendenza nativa.
In alternativa REST-only: Turso/libSQL propone embedding remoto—modello operativo diverso da file .db locale.
Per solo-forza bruta in SQL puoi anche ordinare per distanza esplicita senza vec0, ma oltre ~10k–50k righe di solito serve ANN o un motore dedicato.
Sintesi
Per pochi k chunk, embeddings cached + similarità lineare o SQLite sono spesso sufficienti. Il vector DB diventa necessario quando N, latenza o features (filtri, hybrid) superano ciò che vuoi mantenere in codice—non quando il blog dice che “tutti usano Pinecone”.
Riferimenti: documentazione del modello embedding scelto (dimensione, max tokens, lingua), e linee guida chunking del framework che usi.