Torna al blog

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 hnswlib lato 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-small offre 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:

  1. NumPy/Pickle o file .npy + manifest JSON con mapping chunk_id → offset. Funziona per demo; fragile per concorrenza scrittura.
  2. 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 per source_id.
  3. 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.

Vuoi applicare idee come queste al tuo prodotto?

Raccontaci contesto, vincoli e obiettivi: ti diciamo se ha senso lavorare insieme e come impostare il primo passo.