Skip to content
Learn Agentic AI12 min read0 views

Migrating Vector Databases: Moving Embeddings Between Pinecone, pgvector, and Weaviate

Learn how to migrate vector embeddings between Pinecone, pgvector, and Weaviate. Covers export formats, re-embedding decisions, index tuning, and verification strategies.

When Vector Database Migration Makes Sense

Teams migrate vector databases for several reasons: cost optimization (Pinecone's managed pricing vs. self-hosted pgvector), consolidation (reducing infrastructure complexity by using pgvector alongside your existing PostgreSQL), or capability requirements (Weaviate's hybrid search combining vectors with BM25 keyword matching).

The critical decision in any vector migration is whether to copy existing embeddings or re-embed from source documents. This choice affects migration time, cost, and whether you can change embedding models simultaneously.

Decision: Copy Vectors or Re-Embed?

def should_re_embed(
    source_model: str,
    target_model: str,
    source_dimensions: int,
    target_dimensions: int,
    document_count: int,
) -> dict:
    """Decide whether to copy vectors or re-embed."""
    must_re_embed = (
        source_model != target_model
        or source_dimensions != target_dimensions
    )

    # Estimate re-embedding cost (OpenAI text-embedding-3-small)
    avg_tokens_per_doc = 500
    cost_per_million_tokens = 0.02
    estimated_cost = (
        document_count * avg_tokens_per_doc / 1_000_000
        * cost_per_million_tokens
    )

    return {
        "re_embed_required": must_re_embed,
        "reason": (
            "Model or dimension mismatch"
            if must_re_embed
            else "Same model, direct copy possible"
        ),
        "estimated_cost_usd": round(estimated_cost, 2),
        "estimated_time_minutes": round(document_count / 2000, 1),
    }

result = should_re_embed(
    source_model="text-embedding-ada-002",
    target_model="text-embedding-3-small",
    source_dimensions=1536,
    target_dimensions=1536,
    document_count=100_000,
)
print(result)
# Model mismatch -> must re-embed

Exporting from Pinecone

from pinecone import Pinecone

def export_from_pinecone(
    api_key: str,
    index_name: str,
    namespace: str = "",
    batch_size: int = 100,
) -> list[dict]:
    """Export all vectors and metadata from a Pinecone index."""
    pc = Pinecone(api_key=api_key)
    index = pc.Index(index_name)

    stats = index.describe_index_stats()
    total = stats.total_vector_count
    print(f"Exporting {total} vectors from Pinecone")

    all_vectors = []
    # Use list endpoint to get all IDs, then fetch in batches
    for ids_batch in index.list(namespace=namespace):
        fetch_result = index.fetch(ids=ids_batch, namespace=namespace)
        for vec_id, vec_data in fetch_result.vectors.items():
            all_vectors.append({
                "id": vec_id,
                "values": vec_data.values,
                "metadata": vec_data.metadata,
            })

    print(f"Exported {len(all_vectors)} vectors")
    return all_vectors

Importing into pgvector

import asyncpg
import json

async def import_to_pgvector(
    vectors: list[dict],
    db_url: str,
    table_name: str = "embeddings",
    dimensions: int = 1536,
):
    """Import vectors into a pgvector table."""
    conn = await asyncpg.connect(db_url)

    # Ensure extension and table exist
    await conn.execute("CREATE EXTENSION IF NOT EXISTS vector")
    await conn.execute(f"""
        CREATE TABLE IF NOT EXISTS {table_name} (
            id TEXT PRIMARY KEY,
            embedding vector({dimensions}),
            metadata JSONB,
            created_at TIMESTAMPTZ DEFAULT now()
        )
    """)

    # Batch insert
    imported = 0
    for vec in vectors:
        embedding_str = "[" + ",".join(str(v) for v in vec["values"]) + "]"
        await conn.execute(
            f"""INSERT INTO {table_name} (id, embedding, metadata)
                VALUES ($1, $2::vector, $3::jsonb)
                ON CONFLICT (id) DO NOTHING""",
            vec["id"],
            embedding_str,
            json.dumps(vec.get("metadata", {})),
        )
        imported += 1

    # Create HNSW index for fast similarity search
    await conn.execute(f"""
        CREATE INDEX IF NOT EXISTS idx_{table_name}_embedding
        ON {table_name}
        USING hnsw (embedding vector_cosine_ops)
        WITH (m = 16, ef_construction = 200)
    """)

    await conn.close()
    print(f"Imported {imported} vectors into pgvector")

Re-Embedding When Models Change

from openai import OpenAI

client = OpenAI()

def re_embed_documents(
    documents: list[dict],
    model: str = "text-embedding-3-small",
    batch_size: int = 100,
) -> list[dict]:
    """Re-embed documents with a new model."""
    results = []
    for i in range(0, len(documents), batch_size):
        batch = documents[i:i + batch_size]
        texts = [doc["text"] for doc in batch]

        response = client.embeddings.create(
            model=model,
            input=texts,
        )
        for doc, emb in zip(batch, response.data):
            results.append({
                "id": doc["id"],
                "values": emb.embedding,
                "metadata": doc.get("metadata", {}),
            })
    return results

Verification: Ensure Search Quality Is Preserved

async def verify_migration(
    test_queries: list[str],
    source_search_fn,
    target_search_fn,
    top_k: int = 10,
) -> dict:
    """Compare search results between source and target."""
    overlap_scores = []

    for query in test_queries:
        source_ids = set(source_search_fn(query, top_k))
        target_ids = set(target_search_fn(query, top_k))

        overlap = len(source_ids & target_ids) / top_k
        overlap_scores.append(overlap)

    avg_overlap = sum(overlap_scores) / len(overlap_scores)
    return {
        "avg_result_overlap": round(avg_overlap, 3),
        "queries_tested": len(test_queries),
        "perfect_matches": sum(1 for s in overlap_scores if s == 1.0),
    }

FAQ

Can I copy embeddings directly between different vector databases?

Yes, if you are keeping the same embedding model. Vectors are just arrays of floats — the database does not care which model produced them. Export the vectors with their metadata and import them into the new database. The key constraint is that dimensions must match.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

How long does re-embedding 1 million documents take?

With OpenAI's embedding API at roughly 2,000 documents per minute (respecting rate limits), re-embedding 1 million documents takes about 8-9 hours. You can parallelize with multiple API keys or use a local model like BAAI/bge-large-en to eliminate rate limits entirely.

Should I tune HNSW index parameters after migration?

Yes. The default parameters (m=16, ef_construction=64) work for most cases, but if you need higher recall, increase ef_construction to 200 and m to 24. Run benchmark queries with different ef_search values to find the right recall-speed tradeoff for your use case.


#VectorDatabase #Pinecone #Pgvector #Weaviate #Embeddings #Migration #AgenticAI #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.