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