Deel:𝕏LinkedInRedditFacebookKopieer link

Lokale vectordatabase opzetten: Qdrant en Chroma

Door Ivo Donker — samengesteld met AI-ondersteuning (Claude & Gemini) · Laatst bijgewerkt: 6 augustus 2026

Wanneer is een dedicated vectordatabase nodig?

Wanneer je met Retrieval-Augmented Generation (RAG) of semantisch zoeken aan de slag gaat, is de eerste reflex vaak om direct een vectordatabase te installeren. In veel gevallen is dit echter een voorbeeld van voortijdige optimalisatie. Voordat je de complexiteit van een apart databasesysteem en de bijbehorende netwerkarchitectuur introduceert, moet je bepalen of de omvang van jouw specifieke dataset en de gebruikssituatie dit daadwerkelijk rechtvaardigen. Het opzetten van een extra service brengt immers altijd administratieve overhead, extra faalpunten en resource-gebruik met zich mee.

Als je bijvoorbeeld minder dan 1000 documenten of tekstfragmenten hebt (zoals een persoonlijke verzameling handleidingen, een klein archief van blogposts of een handvol interne PDF-bestanden), is een dedicated vectordatabase vaak overbodig. In zulke situaties kun je de vectoren direct in het werkgeheugen van je Python-script opslaan met behulp van basisbibliotheken zoals Numpy of Faiss, of ze simpelweg wegschrijven naar een plat JSON-bestand op de harde schijf. Een andere uitstekende en vaak over het hoofd geziene optie is het gebruik van een SQLite-extensie, zoals sqlite-vss of het nieuwere sqlite-vec. Dit stelt je in staat om semantische zoekopdrachten uit te voeren binnen een vertrouwde, bestand-gebaseerde relationele database zonder extra netwerkoverhead of containerbeheer. Dit is ideaal voor desktop-applicaties of kleinschalige scripts die documenten lokaal doorzoeken met AI, zoals uitgebreid beschreven in de handleiding over documenten doorzoeken lokaal. SQLite-oplossingen zijn makkelijk te backuppen en vereisen geen achtergrondprocessen die continu RAM-geheugen consumeren.

Wanneer is een dedicated vectordatabase dan wel zinvol? De noodzaak ontstaat wanneer je te maken krijgt met minstens tienduizenden documentchunks, wanneer je database constant live updates moet ontvangen (denk aan real-time toevoegingen, wijzigingen of verwijderingen van data door meerdere gebruikers), of wanneer je complexe filterbewerkingen moet uitvoeren op basis van metadata. Dedicated vectordatabases zijn specifiek geoptimaliseerd voor het snel doorzoeken van hoog-dimensionale ruimtes, het efficiënt indexeren van nieuwe vectoren via algoritmen zoals HNSW (Hierarchical Navigable Small World), en het combineren van vector-zoekacties met traditionele filters (zoals datumreeksen of categorieën). Als meerdere applicaties of scripts gelijktijdig toegang moeten hebben tot dezelfde index via een gestandaardiseerde API (HTTP of gRPC), is een losse database-opstelling de meest robuuste keuze.

De praktische verschillen tussen Chroma en Qdrant

Als je besluit dat je inderdaad een database nodig hebt, zijn voor lokale netwerken en thuisopstellingen (zoals op een Mac mini, een Intel NUC of een consumenten-NAS) Chroma en Qdrant de meest voor de hand liggende keuzes. Hoewel ze hetzelfde doel dienen — het opslaan en doorzoeken van vectoren — verschillen ze aanzienlijk in architectuur, resource-gebruik en deployment-gemak. Een algemeen overzicht van deze en andere systemen is te vinden in de vergelijking van vectordatabases in onze directory.

Chroma is ontworpen met eenvoud en snelle integratie als uitgangspunt. Het is van origine een Python-bibliotheek die embedded kan draaien in hetzelfde proces als je applicatie. Dit betekent dat je geen aparte server hoeft te configureren; de data wordt opgeslagen in een lokale map in een SQLite-database en enkele vlakke bestanden. Chroma is echter ook als stand-alone server te draaien via Docker. Voor een thuisopstelling is Chroma uiterst laagdrempelig en vereist het nauwelijks configuratie. De prestaties kunnen echter afnemen zodra de schaal groter wordt en er complexe filters of gelijktijdige schrijfacties plaatsvinden, omdat SQLite onder de motorkap de concurrency kan beperken.

Qdrant daarentegen is geschreven in Rust. Het is ontworpen als een standalone, high-performance service die uitsluitend als losse database draait (meestal via Docker). Qdrant is uiterst efficiënt met geheugen en processorkracht. De engine is in staat om met minimale resources miljoenen vectoren te beheren en biedt geavanceerde filtermogelijkheden direct in de index-engine. Voor een thuisopstelling op een NAS of een server die 24/7 draait, biedt Qdrant meer stabiliteit, voorspelbaarheid onder belasting en betere integratiemogelijkheden met andere programmeertalen dan Python, hoewel de initiële configuratie iets meer netwerkkennis vereist.

Eigenschap Chroma (Embedded) Qdrant (Docker)
Taal Python / C++ Rust
Architectuur In-process of Server Client-Server
Dataopslag SQLite + Parquet/bestanden Eigen opslagformaat op schijf
Resource-gebruik Laag bij start, stijgt bij grote datasets Zeer laag en voorspelbaar (Rust)
Netwerkprotocollen HTTP / REST HTTP / REST en gRPC

Chroma opzetten: embedded versus server

Chroma biedt de flexibiliteit om direct vanuit een Python-script te draaien. Dit maakt het ideaal voor prototyping en kleinschalige projecten. De eenvoudigste manier om Chroma embedded te gebruiken is via de PersistentClient. Hierbij geef je een map op waar Chroma de gegevens moet wegschrijven.

import chromadb

# Initialiseer de embedded client met persistente opslag
client = chromadb.PersistentClient(path="/absolute/pad/naar/chroma_data")

# Maak een collectie aan of haal een bestaande op
collection = client.get_or_create_collection(name="lokale_documenten")

De persistente map (in dit voorbeeld /absolute/pad/naar/chroma_data) bevat een SQLite-database (chroma.sqlite3) en mappen voor de vector-indexen (meestal HNSW-bestanden). Bij het backuppen van een embedded Chroma-installatie is het cruciaal dat er geen actieve schrijfacties plaatsvinden. Omdat SQLite gebruikmaakt van bestand-locking, kan een backup tijdens een schrijfactie leiden tot een corrupt databasebestand. De veiligste methode is om het Python-script volledig te stoppen en vervolgens de gehele map te kopiëren of te archiveren.

Als je Chroma als een losse server wilt draaien, bijvoorbeeld om toegang te krijgen vanaf meerdere scripts op je netwerk of vanaf verschillende machines, kun je de database opstarten via Docker:

docker run -d \
  -p 8000:8000 \
  -v /absolute/pad/naar/chroma_data:/chroma/chroma \
  -e IS_PERSISTENT=TRUE \
  --name chromadb \
  chromadb/chroma:latest

In deze server-modus maakt je Python-script verbinding via chromadb.HttpClient(host="127.0.0.1", port=8000). Houd er rekening mee dat de server-modus van Chroma in feite een API-schil is om de embedded database heen. De onderliggende concurrency-beperkingen van SQLite blijven in zekere mate bestaan, waardoor deze opstelling minder geschikt is voor zware parallelle schrijftaken.

Qdrant opzetten via Docker

Qdrant is specifiek ontworpen om als netwerkdienst te draaien. Voor een thuisopstelling is Docker de meest stabiele en configureerbare methode. Het draaien van databases in containers vereist specifieke aandacht voor volumemapping om te voorkomen dat data verloren gaat bij het updaten van de container. Voor een bredere context over het draaien van AI-tools in containers, zie ook ons artikel over een LLM in Docker draaien.

Een basis docker-compose configuratie voor Qdrant ziet er als volgt uit:

version: '3.8'

services:
  qdrant:
    image: qdrant/qdrant:latest
    container_name: qdrant_local
    ports:
      - "6333:6333"
      - "6334:6334"
    volumes:
      - /opt/qdrant/storage:/qdrant/storage
      - /opt/qdrant/config.yaml:/qdrant/config.yaml
    restart: unless-stopped

Qdrant maakt standaard gebruik van twee netwerkpoorten met elk hun eigen doel:

Het is essentieel om de opslagmap (/qdrant/storage binnen de container) te mappen naar een map op de host, zoals /opt/qdrant/storage. Qdrant schrijft hier zijn collecties, vectoren, metadata en indexbestanden naartoe. Als je dit volume niet configureert, wordt de data opgeslagen in de tijdelijke schrijflaag van de container. Dit betekent dat alle data verdwijnt zodra je de container stopt, updatet of verwijdert. Door de opslagmap buiten de container te houden, blijft de data veilig behouden en kun je de Qdrant-image eenvoudig bijwerken naar een nieuwere versie door de container te vernietigen en opnieuw te starten.

Collecties aanmaken en configureren

Binnen een vectordatabase worden vectoren niet in willekeurige mappen opgeslagen, maar in gestructureerde eenheden die "collecties" (of indexen) worden genoemd. Bij het aanmaken van een collectie moet je twee cruciale parameters definieren: de vector-dimensie en de afstandsmaat.

De vector-dimensie is de lengte van de getallenreeks die je embeddingmodel genereert. Elk model heeft een vaste dimensie. Zo genereert het populaire model all-MiniLM-L6-v2 vectoren met een dimensie van 384, terwijl grotere modellen zoals text-embedding-3-large tot 1536 of zelfs 3072 dimensies kunnen gaan. Het is technisch onmogelijk om vectoren van verschillende dimensies in dezelfde collectie op te slaan. Je moet de collectie dus exact configureren op basis van het model dat je gebruikt. Voor een gedetailleerd overzicht van beschikbare modellen kun je terecht bij het overzicht van embeddingmodellen vergeleken.

De afstandsmaat (distance metric) bepaalt hoe de database de gelijkenis tussen twee vectoren berekent. De meest gebruikte maten zijn:

De keuze voor de afstandsmaat hoort bij de specificaties van het embeddingmodel. Als een model is getraind met cosine similarity, zal het gebruik van L2-afstand leiden tot suboptimale zoekresultaten. Controleer daarom altijd de documentatie van het gekozen model voordat je de collectie aanmaakt.

Hier is een voorbeeld van hoe je een collectie aanmaakt in Qdrant met de Python client, geconfigureerd voor een model met 384 dimensies en Cosine similarity:

from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="lokale_kennisbank",
    vectors_config=VectorParams(
        size=384, 
        distance=Distance.COSINE
    )
)

Metadata-velden en filteren

Een veelgemaakte fout bij het opzetten van een lokale RAG-omgeving is het uitsluitend opslaan van de vectoren en de bijbehorende ruwe tekst. Hoewel dit volstaat voor eenvoudige zoekopdrachten, loop je al snel tegen grenzen aan als de database groeit en je meer controle wilt over de zoekresultaten. Het is daarom ten zeerste aan te raden om direct bij het indexeren relevante metadata-velden op te slaan.

De belangrijkste metadata-velden die je vanaf het begin moet meenemen zijn:

Het opslaan van deze metadata stelt je in staat om pre-filtering toe te passen. Bij pre-filtering selecteert de database eerst alle documenten die aan de metadata-criteria voldoen (bijvoorbeeld category == 'handleidingen'), en voert daarna pas de vector-zoekopdracht uit op die specifieke subset. Dit is vele malen sneller en nauwkeuriger dan post-filtering, waarbij je eerst de top 100 vectoren ophaalt en daar pas achteraf de ongewenste documenten uit filtert (wat kan leiden tot te weinig bruikbare resultaten als de top 100 toevallig geen documenten uit de gewenste categorie bevat).

In Qdrant kun je complexe filters definieren met behulp van filterobjecten:

from qdrant_client.models import Filter, FieldCondition, MatchValue

results = client.search(
    collection_name="lokale_kennisbank",
    query_vector=[0.1, 0.2, -0.3, 0.4], # Je query-embedding (voorbeeld)
    query_filter=Filter(
        must=[
            FieldCondition(
                key="category",
                match=MatchValue(value="handleidingen")
            )
        ]
    ),
    limit=5
)

Herindexeren en modelwissels

De wereld van embeddingmodellen ontwikkelt zich snel. Het is aannemelijk dat je in de toekomst wilt overstappen naar een nieuwer, nauwkeuriger model. Hier schuilt een fundamenteel kenmerk van vectordatabases: vectoren zijn niet uitwisselbaar tussen verschillende modellen.

Als je overstapt van bijvoorbeeld een ouder MiniLM-model naar een groter model van BGE of Nomic, moet je de gehele database opnieuw opbouwen. De vectorruimte van het nieuwe model is totaal anders opgebouwd; een vector van model A heeft geen enkele semantische relatie met een vector van model B, zelfs niet als de dimensies toevallig gelijk zijn. De database kan de oude vectoren niet omzetten naar de nieuwe ruimte.

Om chaos te voorkomen, is het verstandig om de modelnaam en de bijbehorende parameters direct vast te leggen in de naam van de collectie (bijvoorbeeld documenten_nomic_v1.5 in plaats van simpelweg documenten). Dit voorkomt dat je per ongeluk embeddings van verschillende modellen in deze index probeert te stoppen, wat zal resulteren in foutmeldingen of volstrekt willekeurige zoekresultaten.

Daarnaast is een robuuste herindexeringspipeline onmisbaar. Aangezien je de database niet simpelweg kunt converteren naar een nieuw model, moet je de brondocumenten opnieuw inlezen, chunksegmenten genereren, deze door het nieuwe embeddingmodel halen en wegschrijven naar een nieuwe collectie. Meer informatie over de levenscyclus en het beheer van dergelijke indexen vind je in de handleiding over vector-index onderhoud.

Systeembelasting en resource-beheer

Het lokaal draaien van een vectordatabase op consumentenhardware zoals een Mac mini, een oudere Intel NUC of een Synology NAS met een ARM-processor vereist realistische verwachtingen wat betreft systeembelasting en resource-beheer. Voor gebruikers van Apple Silicon kan de specifieke optimalisatie voor macOS relevant zijn, zoals besproken in de gids voor lokale RAG op de Mac.

Het geheugengebruik (RAM) van een vectordatabase wordt primair bepaald door de indexeringsmethode. Als je gebruikmaakt van een HNSW-index (de standaard in zowel Qdrant als Chroma voor snelle zoekopdrachten), worden de vectoren in het RAM-geheugen geladen om snelle grafiek-zoekacties mogelijk te maken. Je kunt het benodigde geheugen voor de ruwe vectoren schatten aan de hand van het aantal vectoren, de dimensie en de gebruikte precisie. Voor float32-vectoren gebruik je 4 bytes per dimensie.

Geheugenschatting (ruwe vectoren):
Aantal vectoren × Dimensies × 4 bytes

Voor 100.000 vectoren met 768 dimensies komt dat neer op ongeveer 307 MB aan puur vectorgeheugen. De HNSW-index zelf introduceert echter een aanzienlijke overhead, vaak variërend van 50% tot 100% extra geheugengebruik. Qdrant biedt opties om deze voetafdruk te verkleinen door vectoren op te slaan op de harde schijf (on-disk payload) of door quantisatie toe te passen (bijvoorbeeld Scalar Quantization), waardoor het geheugengebruik met wel 75% kan dalen ten koste van een minieme afwijking in de zoeknauwkeurigheid.

Een veelvoorkomend probleem bij thuisopstellingen is het crashen van de database door te grote import-batches. Wanneer je tienduizenden documenten in één keer probeert te indexeren, kan het geheugen snel vollopen. Dit leidt tot Out-Of-Memory (OOM) errors waarbij het besturingssysteem de database-container of het Python-proces geforceerd afsluit. Het is daarom aan te raden om de data in batches van maximaal 100 tot 500 vectoren tegelijk aan te bieden en na elke batch de database de tijd te geven om de indexering te verwerken en het geheugen vrij te geven.

Backup, herstel en netwerkbeveiliging

Een goede back-upstrategie voor een lokale vectordatabase bestaat uit twee delen: het veiligstellen van de fysieke databasebestanden en het behouden van de originele gegevensbronnen.

Hoewel het verleidelijk is om alleen de opslagmap van Chroma of Qdrant te backuppen, is dit in de praktijk vaak onvoldoende. Vector-indexen kunnen corrupt raken bij stroomuitval of onverwachte crashes van de container. Bovendien ben je bij een verandering van het embeddingmodel genoodzaakt om alle documenten opnieuw te verwerken. De belangrijkste back-up is daarom de export van de originele brondocumenten inclusief hun metadata. Sla deze op in een gestandaardiseerd formaat (zoals JSON-lines of een SQL-dump) in een aparte, veilige back-upmap. Mocht de vectordatabase corrupt raken of moet je overstappen naar een ander systeem, dan kun je de index altijd vanaf nul weer opbouwen met behulp van je herindexeringspipeline.

Voor de fysieke backup van Qdrant kun je gebruikmaken van de ingebouwde snapshot-functionaliteit. Dit maakt een consistente back-up van de database zonder dat je de service hoeft te stoppen:

# Maak een snapshot van een specifieke collectie via de REST-API
curl -X POST "http://localhost:6333/collections/lokale_kennisbank/snapshots"

Het resulterende .snapshot bestand kan veilig naar een externe back-uplocatie worden gekopieerd.

Netwerkbeveiliging in de thuisomgeving

Wanneer je Docker-containers start, is de verleiding groot om poorten open te zetten voor het hele netwerk. Standaard luisteren veel configuraties op 0.0.0.0, wat betekent dat de database bereikbaar is vanaf elk apparaat in je lokale netwerk (en potentieel vanaf het internet als je router verkeerd is ingesteld of als de host een publiek IP-adres heeft).

Vectordatabases zoals Chroma en Qdrant hebben standaard geen authenticatie ingeschakeld. Dit betekent dat iedereen op het netwerk je data kan inzien, wijzigen of verwijderen. Beperk de toegang daarom als volgt:

  1. Bind aan localhost: Als de database alleen gebruikt wordt door scripts op dezelfde machine, bind de poorten dan expliciet aan 127.0.0.1 in plaats van 0.0.0.0:
    ports:
      - "127.0.0.1:6333:6333"
      - "127.0.0.1:6334:6334"
    
  2. API-sleutels inschakelen: Als je Qdrant benadert via het netwerk, activeer dan de ingebouwde API-sleutel-beveiliging door de omgevingsvariabele QDRANT__SERVICE__API_KEY in te stellen in je Docker-configuratie. Voor Chroma kun je authenticatie inschakelen via de configuratiebestanden met behulp van static API keys.

Door deze maatregelen te combineren met een goed gestructureerde herindexerings- en backup-opstelling, creëer je een stabiele en veilig beheerde opslaglaag voor al je lokale AI-projecten.

Lees ook