Pinecone vs self-hosted (Qdrant/Weaviate) — où passe la ligne en 2026 ?
TL;DR Pinecone Serverless 2026 reste le fastest-time-to-prod : 1 jour pour avoir un index multi-région avec replication, scaling auto, SLA 99.9%, pay-per-query (et plus per-pod). C'est imbattable pour une startup early-stage. Mais : à volume (> 1M vecteurs, > 5M queries/mois) le coût décolle vite, et la souveraineté FR/EU reste partielle (Pinecone héberge en US/EU mais legal entity Delaware, donc CLOUD Act). Self-hosted Qdrant/Weaviate gagne sur (1) le prix à l'échelle (-50 à -75%), (2) la souveraineté stricte (banque/santé/admin FR), (3) la latence si tu héberges colocalisé. Le pattern freelance qui paie le mieux : migrer Pinecone → Qdrant à 1-5M vecteurs, en dual-write sans downtime.
🧠 Mental model
La courbe de décision coût/effort
Coût total (infra + ops + dev)
│
$$$ │ Pinecone
│ ╱ (per-query
│ ╱ explose à
│ ╱ fort QPS)
│ ╱
│ ╱
$$ │ ╱
│ ╱
│ ╱─────────────── self-hosted Qdrant
│ ╱ (cap fixe + ops)
│ ╱
$ │ ╱
│ ╱
└─────────────────────────────────────► volume
POC 100K 1M 5M 20M 100M Time-to-prod
│
3w │ self-hosted Qdrant/Weaviate full prod
│
1w │ self-hosted Qdrant POC
│
2d │
│
1d │ Pinecone Serverless ────────────
│
4h │ Pinecone Serverless POC
└─────────────────────────────────────► maturité projet
POC prod-ready scale-outAnalogie : Pinecone c'est Uber : tu sors de chez toi, tu cliques, tu paies un peu plus cher, c'est instantané. Self-hosted Qdrant c'est acheter une voiture : ça t'engage, ça demande d'apprendre à conduire, mais à fort kilométrage tu paies bien moins, et tu vas où tu veux (souveraineté).
La carte décision
Volume vecteurs > 5M ?
│
┌─────────┴─────────┐
│ │
NON OUI
│ │
▼ ▼
Volume queries < 1M/mois ? Souveraineté FR stricte ?
│ │
┌──────┴──────┐ ┌─────┴─────┐
OUI NON OUI NON
│ │ │ │
▼ ▼ ▼ ▼
Pinecone Pinecone self-hosted self-hosted
gagne + check Qdrant/Wea OU Pinecone
(TTV) coût mens. Scaleway/OVH (TCO 2 ans)🛠️ Code minimal — comparaison
Pinecone Serverless
# pip install pinecone
from pinecone import Pinecone, ServerlessSpec
pc = Pinecone(api_key="...")
pc.create_index(
name="docs",
dimension=1536,
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="eu-west-1"),
)
index = pc.Index("docs")
index.upsert(vectors=[
{"id": "1", "values": [0.01] * 1536, "metadata": {"tenant": "A"}},
])
hits = index.query(
vector=[0.01] * 1536,
top_k=10,
filter={"tenant": {"$eq": "A"}},
include_metadata=True,
).matchesQdrant Cloud EU (équivalent)
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance, PointStruct, Filter, FieldCondition, MatchValue
client = QdrantClient(url="https://...eu-central.cloud.qdrant.io", api_key="...")
client.create_collection(
"docs",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
)
client.create_payload_index("docs", "tenant", field_schema="keyword")
client.upsert("docs", points=[PointStruct(id=1, vector=[0.01]*1536, payload={"tenant":"A"})])
hits = client.query_points(
"docs",
query=[0.01]*1536,
limit=10,
query_filter=Filter(must=[FieldCondition(key="tenant", match=MatchValue(value="A"))]),
).pointsAPI surface équivalente. Différence côté ops : Pinecone = 0 infra à gérer. Qdrant Cloud = 0 infra côté serveur, oui-mais cluster sizing à choisir.
🎬 Cas d'usage concrets
Cas 1 — Startup AI early-stage, Pinecone is the answer
Contexte : startup 4 personnes, lève une seed de 1.5M€, construit un assistant légal pour PME. 50K docs au lancement, projection 300K à 18 mois. Stack : Next.js + FastAPI + OpenAI. Le CTO a un mois avant la démo investisseur.
Décision : Pinecone Serverless eu-west-1. Setup en 3h. Aucun ops, aucun monitoring vector DB à construire, aucune crainte de réveil PagerDuty.
Résultat : démo investisseur livrée en 4 semaines. Coût Pinecone : ~80€/mois. Quand ils atteindront 5M vecteurs et lèveront leur série A, ils migreront. Pas avant.
Conseil freelance : "Restez sur Pinecone tant que vous êtes < 1M vecteurs et < 2M queries/mois. Je reviens dans 12 mois pour évaluer." Audit 1 jour à 1300€ = 1 300€ HT.
Cas 2 — PME B2B, migration Pinecone → Qdrant à 1M vecteurs
Contexte : PME B2B legaltech, ~500 clients, 1.2M docs indexés, ~3M queries/mois. Facture Pinecone : 1 400€/mois (et qui grossit). CFO grogne. CTO veut migrer mais a peur du downtime.
Décision : migration dual-write Pinecone → Qdrant Cloud EU. Phase 1 (semaine 1-2) : provisioning Qdrant + backfill async. Phase 2 (semaine 3) : dual-write applicatif (Pinecone + Qdrant en parallèle, lectures Pinecone). Phase 3 (semaine 4) : shadow reads (lectures sur les deux, compare top-10 overlap > 95%). Phase 4 (semaine 5) : cutover lectures sur Qdrant, Pinecone en read-only fallback. Phase 5 (semaine 6) : décommissionnement Pinecone.
Résultat : 0 downtime. Coût Qdrant Cloud : 420€/mois (vs 1 400€). Économie nette : -980€/mois × 12 = -11 760€/an.
TJM facturé : 1500€/j × 8 jours = 12 000€ HT. Rentabilisé en 12-13 mois.
Cas 3 — Banque française, souveraineté = self-hosted Scaleway
Contexte : banque mutualiste régionale, projet RAG interne sur 800K documents règlementaires (ACPR, RGPD, AML). DSI refuse catégoriquement tout cloud non-EU et même Pinecone EU (legal entity US). Demande : "stack 100% FR, audit ANSSI possible".
Décision : Qdrant self-hosted sur Scaleway Elements (Paris, certifié SecNumCloud en option). 3 nodes en HA, encryption at-rest, mTLS interne. Embeddings : modèles open-source (BGE-M3) servis par Triton sur Scaleway GPU H100 (Paris). Aucun appel sortant US.
Résultat : conformité ANSSI signée. 800K docs indexés, 2.5M queries/mois en interne. Coût infra : ~1 600€/mois. Audit RGPD/CLOUD Act : "OK, données restent FR".
TJM facturé : 1500€/j × 18 jours (archi + setup + handover + doc audit) = 27 000€ HT.
🛠️ Exemple end-to-end — Migration Pinecone → Qdrant sans downtime
Contexte : SaaS B2B, 5M vecteurs (1536 dim), Pinecone Serverless, ~10M queries/mois. Objectif : migrer vers Qdrant Cloud EU sans downtime.
Architecture cible (phasée)
Phase A — État initial
app ──▶ Pinecone
Phase B — Dual-write + backfill
app ──┬─▶ Pinecone (reads)
│
└─▶ Qdrant (writes only)
backfill_job ──▶ Qdrant (paginate Pinecone via fetch)
Phase C — Shadow reads
app ──┬─▶ Pinecone (reads served)
│ │
│ ├──▶ Qdrant (shadow read, compare async)
│
└─▶ Qdrant (writes)
Phase D — Cutover
app ──┬─▶ Qdrant (reads + writes)
│
└─▶ Pinecone (shadow, fallback)
Phase E — Décommission PineconeCode de migration
# migrations/dual_write.py
from pinecone import Pinecone
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct, Filter, FieldCondition, MatchValue
import asyncio, logging
log = logging.getLogger("migration")
pc = Pinecone(api_key="...")
pc_index = pc.Index("docs")
qd = QdrantClient(url="https://...cloud.qdrant.io", api_key="...")
COLLECTION = "docs"
async def dual_write(doc_id: str, vector: list[float], metadata: dict):
"""Phase B/C : écrire dans les deux systèmes."""
pc_task = asyncio.to_thread(
pc_index.upsert,
vectors=[{"id": doc_id, "values": vector, "metadata": metadata}],
)
qd_task = asyncio.to_thread(
qd.upsert,
collection_name=COLLECTION,
points=[PointStruct(id=_id_to_int(doc_id), vector=vector, payload=metadata)],
)
results = await asyncio.gather(pc_task, qd_task, return_exceptions=True)
for sys_name, r in zip(["pinecone", "qdrant"], results):
if isinstance(r, Exception):
log.error(f"dual_write {sys_name} failed for {doc_id}: {r}")
def _id_to_int(doc_id: str) -> int:
"""Qdrant veut int ou UUID. Hash si nécessaire."""
import hashlib
return int(hashlib.md5(doc_id.encode()).hexdigest()[:15], 16)# migrations/backfill.py
"""Backfill historique : Pinecone -> Qdrant.
Pinecone n'a pas de scroll classique ; on liste par namespace + fetch par lot.
"""
def backfill(namespace: str, batch_size: int = 100):
cursor = None
total = 0
while True:
# Pinecone v6+ : list() pagine les IDs
page = pc_index.list(prefix="", namespace=namespace, limit=batch_size, pagination_token=cursor)
ids = [v.id for v in page.vectors]
if not ids:
break
fetched = pc_index.fetch(ids=ids, namespace=namespace).vectors
points = [
PointStruct(
id=_id_to_int(vid),
vector=v.values,
payload={**(v.metadata or {}), "pinecone_id": vid, "ns": namespace},
)
for vid, v in fetched.items()
]
qd.upsert(collection_name=COLLECTION, points=points)
total += len(points)
cursor = page.pagination.next if page.pagination else None
if not cursor:
break
print(f"[backfill ns={namespace}] {total} points migrated")# migrations/shadow_read.py
"""Phase C : lit dans les deux, compare top-10 overlap."""
import statistics
OVERLAP_HISTORY: list[float] = []
async def shadow_search(query_vec: list[float], filter_md: dict, k: int = 10):
pc_task = asyncio.to_thread(
pc_index.query, vector=query_vec, top_k=k, filter=filter_md, include_metadata=True
)
qd_filter = Filter(must=[
FieldCondition(key=k_, match=MatchValue(value=v_)) for k_, v_ in filter_md.items()
]) if filter_md else None
qd_task = asyncio.to_thread(
qd.query_points,
collection_name=COLLECTION,
query=query_vec,
limit=k,
query_filter=qd_filter,
)
pc_res, qd_res = await asyncio.gather(pc_task, qd_task)
pc_ids = {m.id for m in pc_res.matches}
qd_ids = {p.payload.get("pinecone_id") for p in qd_res.points}
overlap = len(pc_ids & qd_ids) / max(1, len(pc_ids))
OVERLAP_HISTORY.append(overlap)
if len(OVERLAP_HISTORY) % 1000 == 0:
print(f"[shadow] avg overlap last 1000: {statistics.mean(OVERLAP_HISTORY[-1000:]):.3f}")
return pc_res # toujours servir Pinecone tant que phase C# migrations/cutover.py
"""Phase D : feature flag pour basculer la lecture."""
from app.feature_flags import get_flag
async def search(query_vec, filter_md, k=10):
if get_flag("vector_db_primary") == "qdrant":
qd_filter = ...
return qd.query_points(COLLECTION, query=query_vec, limit=k, query_filter=qd_filter).points
return pc_index.query(vector=query_vec, top_k=k, filter=filter_md, include_metadata=True).matchesChecklist migration
| Phase | Critère de passage | Durée typique |
|---|---|---|
| A → B | Qdrant cluster provisionné, schéma OK, sanity 100 vecteurs | 2-3 jours |
| B → C | Backfill terminé, dual-write actif > 24h sans erreur | 5-7 jours |
| C → D | Overlap top-10 ≥ 95% sur 100K shadow reads | 3-5 jours |
| D → E | Cutover validé > 72h, p95 stable, rollback testé | 2-3 jours |
Numbers réels (mission 2025, 5M vecteurs) :
- Durée totale : 5 semaines, 8 jours dev facturés
- Coût : économies 11 760€/an
- Downtime : 0
🎯 Patterns courants
- Feature flag binaire pour le cutover (
vector_db_primary∈ {pinecone, qdrant}) — pas de "50/50 lecture", trop dur à débugger. - Toujours stocker
pinecone_iddans le payload Qdrant pour faciliter le rollback et le shadow read. - Dual-write avec
asyncio.gather(... return_exceptions=True)— ne jamais faire échouer la requête métier si un seul des deux fail. Log et alerte. - Shadow reads sur 1-10% du trafic pendant 72h, pas 100% (coût × 2).
- Overlap top-K > 95% comme gate de cutover, pas "byte-equal" (les ANN ne sont pas déterministes).
- Garder Pinecone en read-only 2 semaines post-cutover : rollback express possible.
- Mesurer
latency_diff_p95(qdrant) - latency_p95(pinecone)pendant le shadow — si Qdrant est plus lent, c'est un sizing à refaire, pas un blocker. - Document migration runbook côté client : c'est ce qu'ils paient.
🔄 Versions & écosystème 2026
| Composant | Version 2026 | Notes |
|---|---|---|
| Pinecone Serverless | GA depuis 2024, mature 2026 | Régions us-east-1, us-west-2, eu-west-1, eu-central-1, ap-southeast-2 |
| Pricing Pinecone | $0.33/GB stocké + $8.25/M write units + $16.5/M read units | Tarifs 2026 ajustés ; toujours pay-per-query |
| Qdrant Cloud | régions EU Frankfurt, Paris (preview), Hybrid Cloud GA | Self-managed cluster dans ton account AWS/GCP/Azure/Scaleway |
| Weaviate Cloud | EU Frankfurt + Amsterdam | Multi-tenant tier OFFLOADED |
| Scaleway SecNumCloud | option dispo sur instances Pro2/Elements | Pour banque/santé/admin FR |
| OVH AI Endpoints | hosted Qdrant managé | Souveraineté FR full-stack |
À noter 2026 :
- Pinecone a sorti Inference endpoints (embed + rerank dans le même produit) : intéressant pour les startups qui n'ont pas envie de jongler 3 providers.
- Qdrant et Weaviate offrent maintenant tous les deux des modèles d'embedding hostés (Snowflake Arctic, BGE).
- La CNIL et l'ANSSI ont publié en 2025 des recommandations sur les vector DB pour le secteur public, qui ont poussé l'adoption self-hosted dans l'admin française.
🇫🇷 Souveraineté : ce que les clients FR demandent en 2026
La souveraineté est devenue un sujet de board chez les ETI françaises. Voici les niveaux que je rencontre :
| Niveau | Demande | Solutions viables |
|---|---|---|
| 1 — Conformité RGPD | Data en EU | Pinecone EU, Qdrant Cloud EU, Weaviate EU, OVH, Scaleway |
| 2 — Pas de CLOUD Act | Legal entity hors juridiction US | Qdrant self-host Scaleway/OVH, Weaviate self-host |
| 3 — SecNumCloud / ANSSI | Audit ANSSI + isolation forte | Scaleway SecNumCloud (option) + Qdrant/Weaviate self-host |
| 4 — Air-gap | Aucune connexion internet | Self-host on-prem + modèles open-source (BGE, Mistral) |
Pour les niveaux 3-4, interdit : Pinecone (US), OpenAI embeddings (US), Cohere (CA). Tu travailles avec :
- Embeddings : Mistral Embed (hosted FR), BGE-M3 self-hosted, Voyage AI EU
- LLM : Mistral La Plateforme (FR), Claude via AWS Bedrock Paris, Anthropic European
- Vector DB : Qdrant ou Weaviate self-hosted sur Scaleway
J'ai mené 2 missions niveau 3 en 2025. Durée 4-8 semaines, TJM 1500€/j, total 30-50K€ HT par mission.
⚠️ Pitfalls
- "On migre parce que c'est cher" sans benchmark préalable → tu peux te retrouver avec un Qdrant Cloud cluster sous-dimensionné qui coûte autant et tourne plus mal. Sizing avant tout.
- Pinecone Serverless "pay-per-query" mal anticipé → un job batch qui requête 10M fois fait exploser la facture. Toujours simuler le coût avec le pattern de trafic réel.
- Migration sans dual-write (juste un copy puis switch) → drift entre les écritures qui arrivent pendant la migration et les données. Désastre garanti.
- IDs Pinecone string → Qdrant int sans mapping stable → impossible de croiser les résultats en shadow. Toujours
hashou UUID v5 stable. metadataPinecone très libre vspayload indexQdrant typé → certains filtres Pinecone ($insur string array) demandent un type explicite côté Qdrant.- Souveraineté "Pinecone EU" = data en EU, mais legal entity US Delaware = CLOUD Act applicable. Pour banque/santé FR, c'est rédhibitoire.
- Self-host "juste pour économiser" sans expérience ops → indispo nocturne, snapshots cassés, latence dégradée. Compter 1 jour/mois ops continu.
- Migrer trop tôt (à 200K vecteurs, "ça grossira") → effort ROI négatif. Attendre 1-2M.
- Pas de plan rollback → en cas de souci post-cutover, panique et perte de confiance client. Documenter le rollback en runbook.
- Oublier d'éteindre Pinecone après cutover → facture qui continue à courir 3 mois post-migration. Calendarisé.
💰 Pricing / ROI client
Comparatif coût mensuel à différents volumes
| Volume / Trafic | Pinecone Serverless | Qdrant Cloud EU | Self-hosted Scaleway |
|---|---|---|---|
| 100K vec, 200K Q/mois | ~25 € | ~70 € | ~80 € |
| 1M vec, 3M Q/mois | ~250 € | ~290 € | ~250 € |
| 5M vec, 10M Q/mois | ~1 400 € | ~600 € | ~400 € |
| 20M vec, 50M Q/mois | ~6 500 € | ~2 100 € | ~1 200 € |
| 100M vec, 200M Q/mois | ~28 000 € | ~7 500 € | ~4 200 € |
(Ordres de grandeur ; varient selon dim, replicas, retention)
Crossover point
- < 1M vecteurs + < 3M Q/mois : Pinecone gagne sur le TTV
- 1-5M vecteurs : équivalent en coût, choisir selon ops/souveraineté
- > 5M vecteurs : self-hosted ou Qdrant Cloud nettement moins cher
Pricing freelance type
| Mission | Jours | TJM | Prix HT |
|---|---|---|---|
| Audit coût/perf Pinecone vs alternatives | 2 | 1300€ | 2 600€ |
| POC Qdrant + benchmark vs Pinecone existant | 4 | 1400€ | 5 600€ |
| Migration Pinecone → Qdrant (1-5M vec) | 8 | 1500€ | 12 000€ |
| Setup Qdrant self-hosted Scaleway souverain | 12 | 1500€ | 18 000€ |
| Conseil archi multi-vector DB (hybride Pinecone + self-host) | 5 | 1500€ | 7 500€ |
🩺 Observabilité pendant migration
Dashboard à monter dès la phase B (dual-write) :
| Panel | Métrique | Seuil alerte |
|---|---|---|
| Dual-write success rate | (success_pc + success_qd) / total | < 99.5% |
| Backfill progression | rows_migrated / total_rows | n/a (visu) |
| Shadow read overlap (top-10) | overlap_count / k | < 0.90 = bloquant |
| Latence p95 Pinecone vs Qdrant | histogram_quantile | écart > 50ms |
| Coût mensuel Pinecone | facture API | trend |
| Drift count | docs in pinecone XOR qdrant | > 0.1% |
Cutover gate : "overlap > 95% pendant 7 jours d'affilée, drift < 0.05%, latence Qdrant ≤ Pinecone p95 × 1.2".
🧪 Testing / Eval
# tests/test_dual_write_consistency.py
import pytest, asyncio
from migrations.dual_write import dual_write
from migrations.shadow_read import shadow_search
@pytest.mark.asyncio
async def test_dual_write_doc_appears_in_both():
vec = [0.01] * 1536
await dual_write("test-id-1", vec, {"tenant": "A", "type": "doc"})
await asyncio.sleep(2) # eventually consistent
res = await shadow_search(vec, {"tenant": "A"}, k=10)
# Le doc doit être dans le top 10 chez les deux
assert any(m.id == "test-id-1" for m in res.matches)
@pytest.mark.parametrize("filter_md", [
{"tenant": "A"}, {"tenant": "B", "type": "contract"},
])
@pytest.mark.asyncio
async def test_shadow_overlap_above_threshold(filter_md, sample_queries):
overlaps = []
for q in sample_queries:
# ...
overlaps.append(_compute_overlap(q, filter_md))
avg = sum(overlaps) / len(overlaps)
assert avg >= 0.95, f"Overlap moyen {avg:.3f} < 0.95, migration pas prête"À monitorer en migration :
- Overlap top-K (dashboard temps réel)
- Latence p95 par DB
- Taux d'erreur dual-write (Pinecone vs Qdrant séparément)
- Coût cumulé Pinecone résiduel
- Drift count = nb docs présents dans un seul des deux
📋 Runbook migration prod
# Migration Pinecone → Qdrant — runbook
## Préparation (J-14)
- [ ] Snapshot complet Pinecone (export ID + vectors + metadata)
- [ ] Provisioning Qdrant Cloud cluster EU
- [ ] Schéma Qdrant créé (collection + payload indexes)
- [ ] Tests unitaires dual-write OK
- [ ] Feature flag `vector_db_primary` déployé (default: pinecone)
- [ ] Dashboard de monitoring migration en place
## Phase B — Dual-write (J0 → J+3)
- [ ] Activer dual-write applicatif
- [ ] Lancer backfill Pinecone → Qdrant (job batch, paginé)
- [ ] Surveiller `dual_write_success_rate` > 99.5%
- [ ] Vérifier `drift_count` < 0.1%
## Phase C — Shadow reads (J+3 → J+10)
- [ ] Activer shadow read sur 10% trafic
- [ ] Monter à 100% si overlap > 90%
- [ ] Investiguer écarts (logs, échantillons)
- [ ] Critère gate : overlap top-10 ≥ 95% sur 100K queries
## Phase D — Cutover (J+10 → J+17)
- [ ] Annonce maintenance (1h fenêtre par sécurité, pas utilisée en pratique)
- [ ] Flip `vector_db_primary=qdrant` 5% → 25% → 100%
- [ ] Pinecone reste en read-only fallback 14j
- [ ] Surveiller p95 latence search
## Phase E — Décommission (J+30)
- [ ] Confirmer 0 lecture Pinecone depuis 14j
- [ ] Export final Pinecone pour backup
- [ ] Suppression Pinecone API key
- [ ] Annulation abonnement Pinecone
- [ ] Documentation finale + handover
## Critères de rollback
- p95 Qdrant > p95 Pinecone × 1.5 pendant 1h
- error_rate Qdrant > 2%
- Discordance fonctionnelle remontée par 2+ utilisateurs
## Contacts
- Lead migration : ...
- DRI Qdrant Cloud : support EU
- DRI Pinecone : support🔁 Quand utiliser / éviter
Pinecone Serverless gagne quand :
- Startup early-stage, vitesse > tout
- Volume < 1M vecteurs et trafic prévisible modéré
- Pas d'équipe ops, pas envie d'en avoir une
- Souveraineté FR pas critique (B2C / B2B SMB sans données sensibles)
- Build & test rapide, expérimentations multiples
Self-hosted Qdrant/Weaviate gagne quand :
5M vecteurs ou > 10M queries/mois (coût)
- Souveraineté FR stricte (banque, santé, défense, admin)
- Latence ultra-prévisible (colocation possible)
- Équipe déjà à l'aise avec Kubernetes / ops
- Customisation profonde (multivector, quantization fine, sharding métier)
- TCO 2-3 ans favorable même avec ops dédié
Patron en "hybride" : startup démarre Pinecone, monte une équipe ops à 1M vecteurs, prépare la migration vers 3M, exécute la migration à 5M.
🧰 Annexes — calcul TCO, framework décisionnel, pitch client
Modèle TCO sur 24 mois (à présenter au CFO)
Coût total = (Infra + Ops humaine + Migrations + Risques) × 24
Pinecone Serverless Self-hosted Qdrant
───────────────────── ─────────────────────
Infra : variable Infra : fixe + scale
Ops humaine : ~0 Ops humaine : 0.1-0.3 FTE
Migration : 0 (lock-in si tard) Migration : 8-12 j freelance
Risques : facture qui explose Risques : indispo si mal opéré
+ souveraineté maîtriséeTableau décisionnel à 6 questions (à utiliser en RDV client)
| # | Question | Réponse → Pinecone | Réponse → Self-host |
|---|---|---|---|
| 1 | Volume vecteurs à 18 mois ? | < 3M | > 3M |
| 2 | Queries/mois projetées ? | < 10M | > 10M |
| 3 | Souveraineté FR/EU stricte ? | Non (B2C/B2B SMB) | Oui (banque/santé/admin) |
| 4 | Équipe ops disponible ? | Non | Oui (0.2 FTE OK) |
| 5 | Délai mise en prod ? | 1-4 sem | 1-3 mois |
| 6 | Stack data déjà mature (K8s/Postgres) ? | Non | Oui |
Score : compter les réponses "Pinecone" vs "Self-host". 4+ d'un côté = recommandation claire.
Le pitch freelance en RDV
"Voilà comment je vois ça : sur votre volume actuel (X) et projection (Y), Pinecone vous coûte environ Z€/mois et augmente quadratiquement avec vos lectures. Une migration vers Qdrant Cloud EU vous fait passer à W€/mois fixe, avec souveraineté EU et 0 downtime. J'ai déjà fait cette migration 3 fois (cas A, B, C), je vous propose 8 jours à 1500€/j (12K€ HT) pour le faire en dual-write, shadow read, cutover progressif. Vous rentabilisez en 10 mois, ROI net 24 mois ≈ +20K€."
Anti-pattern à éviter : Pinecone "parce que tout le monde le fait"
J'ai vu en 2025 trois clients me dire "on a pris Pinecone parce que c'était dans le tuto LangChain". Aucun n'avait fait le calcul. Deux ont migré 6 mois plus tard à des coûts évitables. Le rôle freelance : poser les 6 questions avant le code, pas après.
Patron hybride éprouvé
Phase 0 (Mois 0-3) : Pinecone Serverless, vélocité max, < 200K vecteurs
Phase 1 (Mois 3-9) : Continue Pinecone, monitoring coût mensuel
Phase 2 (Mois 9-12) : Volume atteint 1M+ ou souveraineté demandée par client
→ POC Qdrant (4 jours freelance)
Phase 3 (Mois 12-14) : Migration dual-write → cutover (8 jours freelance)
Phase 4 (Mois 14+) : Qdrant self-host ou Cloud, économies stabiliséesC'est la trajectoire qui maximise vélocité et coût long-terme.
📊 Cas concret : facture client avant/après migration
Voici les chiffres réels (anonymisés) d'une migration menée en 2025 :
Client : SaaS B2B legaltech, 600 clients PME
Volume initial : 4.2M vecteurs, 8M queries/mois
Volume après 12 mois : 5.8M vecteurs, 12M queries/mois
Mois M0 (avant migration) :
─────────────────────────────
Pinecone Serverless eu-west-1 : 1 280 €
OpenAI embeddings : 340 €
Total infra vector : 1 620 € / mois
Mois M+1 (Qdrant Cloud EU 16GB) :
─────────────────────────────
Qdrant Cloud : 420 €
OpenAI embeddings : 340 €
Total infra vector : 760 € / mois
Économie mensuelle : 860 € (−53%)
Économie annuelle : 10 320 €
Coût mission migration (8j × 1500€) : 12 000 € HT
Break-even : 14 mois
Mois M+12 (croissance, Qdrant 32GB HA cluster) :
─────────────────────────────
Qdrant Cloud HA : 920 €
OpenAI embeddings : 480 €
Total : 1 400 € / mois
Projection Pinecone à même volume :
Pinecone Serverless : ~2 100 € / mois
Total projeté : ~2 580 € / mois
Économie M+12 : 1 180 € / mois
Cumul économies 24 mois : ~13 700 €L'argument qui marche en RDV : "Vous payez ma mission en 14 mois, et vous économisez 14K€ sur 24 mois en plus de la souveraineté EU et de la maîtrise de votre coût."
🧨 Failure modes qu'un staff anticipe (et que les autres découvrent en prod)
Au-delà des pitfalls, voici les modes de défaillance systémiques d'une migration vector DB — ceux qui ne se voient pas dans un POC et qui font perdre la confiance client en cutover.
1. Dual-write partiel = corruption silencieuse
asyncio.gather(..., return_exceptions=True) ne fait PAS échouer la requête métier si Qdrant échoue — c'est voulu (le user n'attend pas la DB cible). Mais ça crée une divergence non bornée : chaque écriture ratée sur Qdrant est un doc qui n'existera jamais côté cible tant que tu ne re-backfilles pas. Le piège : tu crois être à 99.9% de succès, mais 0.1% × 50 écritures/s × 7 jours = ~30K docs manquants au cutover.
La parade staff : tout échec côté cible part dans une dead-letter queue (SQS / Redis stream / table failed_writes), rejouée par un worker idempotent. Le drift_count du dashboard doit converger vers 0, pas osciller. Le cutover gate n'est pas "success rate > 99.5%" mais "DLQ vidée ET drift_count = 0 sur 24h".
# migrations/dlq.py — reprise des écritures Qdrant ratées
async def dual_write_safe(doc_id, vector, metadata, dlq):
try:
await asyncio.to_thread(
qd.upsert, collection_name=COLLECTION,
points=[PointStruct(id=_id_to_int(doc_id), vector=vector, payload=metadata)],
)
except Exception as e:
log.error(f"qdrant write failed {doc_id}: {e}")
await dlq.push({"doc_id": doc_id, "vector": vector, "metadata": metadata})
# Pinecone reste la source de vérité tant qu'on n'a pas cutover
await asyncio.to_thread(
pc_index.upsert,
vectors=[{"id": doc_id, "values": vector, "metadata": metadata}],
)2. Le piège de l'embedding model (le tueur silencieux)
Tu ne peux pas mélanger des vecteurs produits par deux modèles d'embedding différents dans le même espace. Si pendant la migration le client passe d'OpenAI text-embedding-3-small à BGE-M3 (souveraineté !), tu changes et la DB et l'espace vectoriel. Deux migrations en une = recette pour un overlap top-K à 40% que tu vas chercher pendant 3 jours.
Règle : une migration à la fois. Migre la DB à iso-embedding d'abord (overlap doit monter à >98% car même modèle, même vecteurs). Le re-embedding vers un modèle souverain est une seconde migration, avec re-calcul complet de l'index et son propre eval de qualité retrieval (recall@10 sur un golden set), pas un simple overlap.
3. Filtres non-équivalents Pinecone ↔ Qdrant
Pinecone metadata est schemaless ; Qdrant exige des payload index typés. Les sémantiques divergent sur les cas tordus : $in sur un array de strings, valeurs null, nombres vs strings ("2024" vs 2024), bornes de range. Un filtre qui matche 1000 docs côté Pinecone peut en matcher 980 côté Qdrant — et ton overlap top-K te le cachera si les 20 manquants ne sont jamais dans le top-10. Eval dédié : un harnais qui compare count(filter) exact entre les deux sur 200 filtres représentatifs, pas seulement le top-K.
4. ANN non-déterministe = "byte-equal" est un piège
HNSW/IVF sont approximatifs : même requête, deux clusters → résultats légèrement différents. Exiger l'égalité exacte bloque ta migration pour toujours. Le bon gate est overlap top-K ≥ 95% (déjà dans le runbook) ET recall@10 vs un ground-truth brute-force mesuré sur un golden set — pas une comparaison Pinecone-vs-Qdrant qui suppose à tort que Pinecone est la vérité.
🏋️ Exercices
Stack de référence : Python 3.12,
pinecone,qdrant-client,pytest-asyncio. Monte un Qdrant local en Docker (docker run -p 6333:6333 qdrant/qdrant) pour tout faire sans facturer Pinecone — mocke la couche Pinecone derrière une interfaceVectorStore.
Exercice 1 — Le harnais de coût (facile)
Objectif : écrire un simulateur qui prend (n_vectors, dim, writes_per_month, reads_per_month, replicas) et sort le coût mensuel Pinecone Serverless, Qdrant Cloud, et self-hosted Scaleway, avec le crossover point.
Indice/Solution : modélise Pinecone comme storage_gb * 0.33 + writes/1e6 * 8.25 + reads/1e6 * 16.5 (avec storage_gb = n_vectors * dim * 4 bytes * overhead_hnsw ≈ 1.5). Qdrant Cloud = fonction en escalier de la RAM nécessaire (vectors_ram ≈ n*dim*4 + payload, +HNSW graph ~30%). Self-host = node_cost * ceil(ram_needed / node_ram) * (1 + ha_factor). Trace les 3 courbes, trouve l'intersection Pinecone/Qdrant par bissection. Validation : tes chiffres doivent retomber dans ±20% du tableau "Comparatif coût mensuel".
Exercice 2 — Dual-write idempotent avec DLQ (moyen)
Objectif : implémenter dual_write_safe + une dead-letter queue + un worker de reprise, et prouver par test que le drift_count converge vers 0 même quand Qdrant échoue 10% du temps.
Indice/Solution : injecte un qd mocké qui lève RuntimeError aléatoirement à p=0.1. Le worker DLQ rejoue avec backoff exponentiel ; l'idempotence vient du fait que upsert sur un id existant est un no-op sémantique (même id = remplacement). Test : écris 10 000 docs, laisse le worker tourner, assert drift_count == 0 à la fin. Bonus : assert que le throughput métier (latence p95 du chemin Pinecone) n'est PAS dégradé par les échecs Qdrant.
Exercice 3 — Eval de migration au-delà de l'overlap (moyen-difficile)
Objectif : construire un harnais d'eval qui détecte les 3 divergences que l'overlap top-K masque : (a) filtres non-équivalents, (b) recall réel vs brute-force, (c) drift de count.
Indice/Solution : génère un golden set de 500 requêtes avec filtres variés. Pour (a) : compare count(filter) exact Pinecone vs Qdrant, alerte si écart > 1%. Pour (b) : calcule la vérité terrain par recherche exhaustive (numpy argsort sur le cosine de tous les vecteurs) et mesure recall@10 de chaque DB CONTRE cette vérité — pas l'une contre l'autre. Pour (c) : set(ids_pinecone) XOR set(ids_qdrant). Le gate de cutover devient un AND des trois, pas juste l'overlap.
Exercice 4 — Casse le cutover, puis répare-le (difficile)
Objectif : reproduire un cutover qui part mal (latence Qdrant p95 × 1.6, error_rate 3%), déclencher le rollback automatique, et le rendre safe.
Indice/Solution : implémente le feature flag vector_db_primary avec un circuit breaker : si p95(qdrant) > p95(pinecone) × 1.5 OU error_rate > 2% sur une fenêtre glissante de 5 min, flip automatique retour pinecone + alerte. Simule la dégradation en ajoutant un sleep aléatoire dans le mock Qdrant. Le piège à corriger : pendant le rollback, les écritures sont déjà parties dans Qdrant — donc le rollback en lecture doit rester compatible avec un Qdrant qui continue à recevoir des writes (sinon re-drift). Démontre qu'un rollback + ré-avance fonctionne sans perte.
Exercice 5 — Double migration : DB + embedding souverain (difficile)
Objectif : migrer de Pinecone + OpenAI embeddings vers Qdrant self-host + BGE-M3, en séparant proprement les deux migrations, avec eval de qualité retrieval à chaque étape.
Indice/Solution : étape 1, migre Pinecone→Qdrant à iso-embedding (overlap doit être >98% car mêmes vecteurs). Étape 2, re-embed tout le corpus avec BGE-M3 dans une nouvelle collection Qdrant, calcule recall@10 et nDCG@10 sur un golden set annoté (pas un overlap inter-DB — l'espace vectoriel a changé, l'overlap est sans signification). Défends le passage si BGE-M3 fait perdre ❤️% de nDCG vs OpenAI. Le piège : ne jamais router une partie du trafic vers chaque espace en parallèle — un même query embeddé par deux modèles ne compare pas.
Exercice 6 — Défends le chiffre devant le CFO (staff)
Objectif : prendre un cas réel (5M vecteurs, 12M Q/mois), produire un TCO 24 mois Pinecone-vs-Qdrant qui inclut le coût caché (ops humaine, risque d'indispo, coût de la migration elle-même, coût d'opportunité du dev mobilisé) et survivre au contre-interrogatoire.
Indice/Solution : le piège junior est de ne comparer que la facture infra. Modélise : Ops self-host = 0.2 FTE × salaire chargé / 12 (~1500-2500€/mois de coût caché que la facture Qdrant Cloud te masque). Risque indispo = proba_incident × MTTR × coût_horaire_business. Coût migration = 12K€ amorti sur 24 mois. Coût d'opportunité = ce que le dev n'a PAS construit. La réponse de staff n'est pas "self-host est moins cher" mais "self-host est moins cher au-delà de ~5M vec SI tu as déjà une astreinte ops ; sinon Qdrant Cloud capture 70% de l'économie sans le coût caché ops". Sache défendre les deux directions.
🎤 En entretien
Q : Quand recommandes-tu de migrer de Pinecone vers du self-hosted, et comment chiffres-tu la décision ? R : Pas avant 1-5M vecteurs ; le déclencheur est soit le coût (per-query Pinecone explose à fort QPS), soit la souveraineté (legal entity US = CLOUD Act, rédhibitoire banque/santé FR). Je chiffre un TCO 24 mois qui inclut l'ops humaine cachée (0.2 FTE en self-host) et le coût de la migration, pas juste la facture infra — sinon je sur-vends le self-host.
Q : Comment migres-tu une vector DB sans downtime ni perte de données ? R : Dual-write (writes vers les deux, reads sur l'ancien) + backfill historique + shadow reads pour valider l'overlap top-K ≥ 95% + cutover progressif par feature flag (5%→25%→100%) avec l'ancien en read-only fallback 14 jours. Le gate n'est pas le success rate du dual-write mais un drift_count convergeant vers 0 (échecs en DLQ rejoués), sinon tu cutover sur une cible incomplète.
Q : Pourquoi ne pas exiger des résultats identiques entre les deux DB avant de cutover ? R : Les index ANN (HNSW/IVF) sont approximatifs et non-déterministes ; "byte-equal" est inatteignable. Le bon critère est l'overlap top-K (≥95%) ET le recall@10 contre un ground-truth brute-force sur un golden set — comparer les deux DB entre elles suppose à tort que l'ancienne est la vérité terrain.
Q : Un client veut migrer la DB ET passer d'embeddings OpenAI à un modèle souverain (BGE-M3) en même temps. Qu'en dis-tu ? R : Deux migrations distinctes, jamais simultanées. D'abord DB à iso-embedding (overlap >98% attendu, car mêmes vecteurs). Ensuite re-embedding complet dans une nouvelle collection, validé par recall@10 / nDCG@10 sur un golden set annoté — pas par overlap, puisque l'espace vectoriel a changé et que les distances ne sont plus comparables.
🔗 Liens
- Pinecone pricing : https://www.pinecone.io/pricing/
- Pinecone vs Qdrant benchmark (Qdrant blog, biais transparent) : https://qdrant.tech/benchmarks/
- ANSSI guide LLM/RAG en environnement souverain : à jour 2025
- CNIL recommandations vector DB pour secteur public : 2025
- Scaleway SecNumCloud : https://www.scaleway.com/en/secnumcloud/
- OVH AI Endpoints : https://www.ovhcloud.com/fr/public-cloud/ai-endpoints/
- Pattern dual-write : Martin Kleppmann, "Designing Data-Intensive Applications"
→ Voir aussi : 02-qdrant.md (cible self-host), 03-weaviate.md (alternative multi-tenant), 06-realtime-vectors.md (pipelines temps réel).