Skip to content

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-out

Analogie : 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 ​

python
# 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,
).matches

Qdrant Cloud EU (équivalent) ​

python
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"))]),
).points

API 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 Pinecone

Code de migration ​

python
# 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)
python
# 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")
python
# 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
python
# 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).matches

Checklist migration ​

PhaseCritère de passageDurée typique
A → BQdrant cluster provisionné, schéma OK, sanity 100 vecteurs2-3 jours
B → CBackfill terminé, dual-write actif > 24h sans erreur5-7 jours
C → DOverlap top-10 ≥ 95% sur 100K shadow reads3-5 jours
D → ECutover 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 ​

  1. Feature flag binaire pour le cutover (vector_db_primary ∈ {pinecone, qdrant}) — pas de "50/50 lecture", trop dur à débugger.
  2. Toujours stocker pinecone_id dans le payload Qdrant pour faciliter le rollback et le shadow read.
  3. 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.
  4. Shadow reads sur 1-10% du trafic pendant 72h, pas 100% (coût × 2).
  5. Overlap top-K > 95% comme gate de cutover, pas "byte-equal" (les ANN ne sont pas déterministes).
  6. Garder Pinecone en read-only 2 semaines post-cutover : rollback express possible.
  7. Mesurer latency_diff_p95(qdrant) - latency_p95(pinecone) pendant le shadow — si Qdrant est plus lent, c'est un sizing à refaire, pas un blocker.
  8. Document migration runbook côté client : c'est ce qu'ils paient.

🔄 Versions & écosystème 2026 ​

ComposantVersion 2026Notes
Pinecone ServerlessGA depuis 2024, mature 2026Ré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 unitsTarifs 2026 ajustés ; toujours pay-per-query
Qdrant Cloudrégions EU Frankfurt, Paris (preview), Hybrid Cloud GASelf-managed cluster dans ton account AWS/GCP/Azure/Scaleway
Weaviate CloudEU Frankfurt + AmsterdamMulti-tenant tier OFFLOADED
Scaleway SecNumCloudoption dispo sur instances Pro2/ElementsPour banque/santé/admin FR
OVH AI Endpointshosted 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 :

NiveauDemandeSolutions viables
1 — Conformité RGPDData en EUPinecone EU, Qdrant Cloud EU, Weaviate EU, OVH, Scaleway
2 — Pas de CLOUD ActLegal entity hors juridiction USQdrant self-host Scaleway/OVH, Weaviate self-host
3 — SecNumCloud / ANSSIAudit ANSSI + isolation forteScaleway SecNumCloud (option) + Qdrant/Weaviate self-host
4 — Air-gapAucune connexion internetSelf-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 ​

  1. "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.
  2. 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.
  3. 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.
  4. IDs Pinecone string → Qdrant int sans mapping stable → impossible de croiser les résultats en shadow. Toujours hash ou UUID v5 stable.
  5. metadata Pinecone très libre vs payload index Qdrant typé → certains filtres Pinecone ($in sur string array) demandent un type explicite côté Qdrant.
  6. Souveraineté "Pinecone EU" = data en EU, mais legal entity US Delaware = CLOUD Act applicable. Pour banque/santé FR, c'est rédhibitoire.
  7. Self-host "juste pour économiser" sans expérience ops → indispo nocturne, snapshots cassés, latence dégradée. Compter 1 jour/mois ops continu.
  8. Migrer trop tôt (à 200K vecteurs, "ça grossira") → effort ROI négatif. Attendre 1-2M.
  9. Pas de plan rollback → en cas de souci post-cutover, panique et perte de confiance client. Documenter le rollback en runbook.
  10. 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 / TraficPinecone ServerlessQdrant Cloud EUSelf-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 ​

MissionJoursTJMPrix HT
Audit coût/perf Pinecone vs alternatives21300€2 600€
POC Qdrant + benchmark vs Pinecone existant41400€5 600€
Migration Pinecone → Qdrant (1-5M vec)81500€12 000€
Setup Qdrant self-hosted Scaleway souverain121500€18 000€
Conseil archi multi-vector DB (hybride Pinecone + self-host)51500€7 500€

🩺 Observabilité pendant migration ​

Dashboard à monter dès la phase B (dual-write) :

PanelMétriqueSeuil alerte
Dual-write success rate(success_pc + success_qd) / total< 99.5%
Backfill progressionrows_migrated / total_rowsn/a (visu)
Shadow read overlap (top-10)overlap_count / k< 0.90 = bloquant
Latence p95 Pinecone vs Qdranthistogram_quantileécart > 50ms
Coût mensuel Pineconefacture APItrend
Drift countdocs 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 ​

python
# 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 ​

markdown
# 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ée

Tableau décisionnel à 6 questions (à utiliser en RDV client) ​

#QuestionRéponse → PineconeRéponse → Self-host
1Volume vecteurs à 18 mois ?< 3M> 3M
2Queries/mois projetées ?< 10M> 10M
3Souveraineté FR/EU stricte ?Non (B2C/B2B SMB)Oui (banque/santé/admin)
4Équipe ops disponible ?NonOui (0.2 FTE OK)
5Délai mise en prod ?1-4 sem1-3 mois
6Stack data déjà mature (K8s/Postgres) ?NonOui

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ées

C'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".

python
# 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 interface VectorStore.

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 ​

→ Voir aussi : 02-qdrant.md (cible self-host), 03-weaviate.md (alternative multi-tenant), 06-realtime-vectors.md (pipelines temps réel).

Bibliothèque tech perso — Achref