ADR-01, Base de données
PostgreSQL + pgvector. Pourquoi pas une DB vectorielle dédiée. TimescaleDB en option future.
Statut : Accepté Date : 2026-04 Sujet : Choix de la stack base de données pour Reciprok
Contexte
Le projet a besoin de :
- Une base relationnelle classique pour les entités métier (Members, Requests, Organizers, etc.)
- Une recherche vectorielle efficace pour la similarité sémantique sur 70 000+ membres
- Une timeline append-only qui peut grossir significativement (millions d'événements à terme)
- Une évolutivité sans réécriture majeure
Alternatives considérées
Option 1, Postgres standard + DB vectorielle dédiée
Combiner Postgres pour les données métier avec Pinecone, Qdrant, ou Weaviate pour le vector.
Pour :
- Performance vectorielle optimale
- Outils dédiés à la recherche sémantique
Contre :
- 2 systèmes à maintenir, à backup, à monitorer
- Synchronisation Postgres ↔ Vector DB à gérer (consistency, failures)
- Coûts cumulés
- Complexité de déploiement (2 services au lieu d'1)
- Pinecone : pricing prohibitif au-delà de 100k vecteurs (~$70/mois min, monte vite)
- Qdrant self-host : nouveau service à monter
Option 2, TimescaleDB (extension Postgres)
TimescaleDB est une extension Postgres orientée time-series.
Pour :
- Excellent pour les hypertables (compression, retention policies, continuous aggregates)
- Utile pour la
timeline_eventà grosse échelle (10M+ events)
Contre :
- Pas une DB vectorielle. C'est un malentendu fréquent. TimescaleDB ne résout pas le problème de la recherche sémantique.
- Overkill pour notre volume d'events au démarrage (quelques millions par an max)
- Lock-in léger sur Timescale Cloud si on veut les meilleures features
Option 3, Postgres + pgvector (retenu)
PostgreSQL standard avec l'extension pgvector pour le stockage et la recherche vectorielle.
Pour :
- 1 seul système, 1 seule connexion, 1 seul backup
- pgvector est mature, performant, gratuit, OSS
- S'installe en une commande sur n'importe quelle Postgres récente (image officielle
pgvector/pgvector) - Distance cosinus, L2, inner product disponibles
- Index
ivfflatouhnswselon le compromis recall/perf - Suffit largement pour 70k membres (le cas qui nous occupe)
Contre :
- Performance moindre que les solutions dédiées au-delà de quelques millions de vecteurs
- Index
ivfflatnécessite unANALYZEaprès les inserts massifs
Décision
Postgres 18 avec extension pgvector, self-hosté via Dokploy sur l'infra interne Hostinger. Image officielle pgvector/pgvector:pg18 déployée comme service Docker.
Le détail "pourquoi self-host plutôt que Neon/Supabase" est documenté dans l'ADR-06.
CREATE EXTENSION IF NOT EXISTS vector;Embeddings stockés en colonne vector(1536) directement dans les tables member, knowledge_entry, etc.
Index de type ivfflat avec lists = sqrt(rowcount) :
CREATE INDEX member_embedding_idx
ON member USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);Upgrade path
Si la base grossit au-delà de 300 000 membres ou si la perf vectorielle devient un bottleneck :
pgvectorscale (Timescale OSS, gratuit)
Extension qui ajoute un index StreamingDiskANN à pgvector.
- 28x plus rapide que pgvector classique sur les gros datasets
- 16x moins cher que Pinecone à équivalence
- Compatible avec le SQL pgvector standard (drop-in upgrade)
- Gratuit, open source, fonctionne sur Postgres standard
CREATE EXTENSION IF NOT EXISTS vectorscale CASCADE;
CREATE INDEX member_embedding_diskann_idx
ON member USING diskann (embedding vector_cosine_ops);pgai (Timescale OSS, gratuit)
Extension qui permet d'appeler des LLM directement depuis SQL.
- Auto-génération d'embeddings via triggers
- Pas besoin de worker dédié pour les embeddings
- Utile pour la maintenance (refresh quand un membre change)
SELECT openai_embed('text-embedding-3-small', member.description)
FROM member WHERE id = $1;Garder cette option en tête, pas activée au démarrage pour limiter le lock-in et garder la stack simple.
TimescaleDB hypertables pour la timeline
Si timeline_event dépasse 10 millions de lignes :
SELECT create_hypertable('timeline_event', 'created_at');
ALTER TABLE timeline_event SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'request_id'
);
SELECT add_compression_policy('timeline_event', INTERVAL '90 days');Bénéfices :
- Compression auto des vieux events (10x moins de stockage)
- Retention policies
- Continuous aggregates pour les stats dashboard
Pas activé au démarrage, à considérer après 6-12 mois en prod selon les volumes réels.
Conséquences
Positives :
- Architecture simple : 1 base, 1 connexion, 1 stack
- Pas de vendor lock-in, on contrôle l'infra
- pgvector éprouvé en production sur des cas similaires
- Upgrade path clair (pgvectorscale / TimescaleDB) sans réécriture
- Coût marginal proche de zéro (l'infra Dokploy/Proxmox existe déjà)
Négatives :
- Performance vectorielle moindre que les solutions dédiées (acceptable pour notre volume)
- Si on dépasse 1M+ membres, il faudra évaluer pgvectorscale ou une migration vers Qdrant
- Les triggers de re-embedding doivent être gérés en application (worker)
- Backups, monitoring, upgrades à notre charge (voir
operations/environmentsetoperations/monitoring)
Métriques à surveiller
À monitorer dès le jour 1 pour valider la décision :
- Temps moyen d'une recherche vectorielle (cible : < 200ms sur 70k membres)
- Taille de l'index vectoriel (cible : < 1 GB pour 70k membres × 1536 dims)
- Volume de la
timeline_eventpar mois - Taux de hit du cache de connexion (pgBouncer si activé)
- Taille des dumps quotidiens uploadés sur R2