Problem Context

Pinecone went serverless GA in early 2024 and rebuilt the cost model around storage + reads/writes instead of always-on pods. By 2026 it's the default managed vector database for teams that want to stop thinking about index sharding, replication, and capacity planning. The newer cascading retrieval (sparse + dense + reranking in one call) and integrated embeddings mean you can hand Pinecone raw text and get back ranked, reranked results.

Pinecone earns its place when you're past pgvector's comfortable ceiling (~100M vectors), need horizontal scale-to-zero, or run multi-tenant RAG where namespace isolation is a hard requirement. It loses when you have small datasets (pgvector is cheaper), need joins/transactions, or have to keep data in a specific cloud region your provider doesn't serve.

๐Ÿค” Sound familiar?
  • You're running pgvector and your index rebuilds take hours
  • You don't know whether to use one index with namespaces or many indexes
  • You're paying for pods that sit idle 80% of the day
  • Your hybrid search is two queries glued together in app code

Pinecone serverless removes the capacity-planning question โ€” but only if you partition your tenants correctly.

Concept Explanation

A Pinecone index is a logical collection with a fixed dimension and metric. Inside it, namespacespartition vectors โ€” searches are scoped to a single namespace, which makes them the right unit for multi-tenant isolation. Each vector carries an id, the numeric values, and an optional metadata object (used for filtering at query time).

  • Serverless indexes โ€” pay for storage + read/write units, scale to zero, regional (AWS, Azure, GCP).
  • Pod-based indexes โ€” predictable latency, always-on, more expensive. Legacy default.
  • Sparse-dense (hybrid) โ€” combine BM25-style sparse vectors with dense embeddings in one query.
  • Reranking โ€” call a built-in reranker (Cohere, BGE) on top-k candidates without extra infra.

flowchart LR
    APP["App"] --> NS1["Namespace<br/>tenant-42"]
    APP --> NS2["Namespace<br/>tenant-7"]
    APP --> NSN["Namespace<br/>tenant-N"]
    NS1 --> IDX["Serverless Index<br/>(dim=1536, cosine)"]
    NS2 --> IDX
    NSN --> IDX
    IDX --> S3["Object storage<br/>(scale to zero)"]

    style IDX fill:#0078D4,color:#fff,stroke:#005a9e
    style S3 fill:#16a34a,color:#fff,stroke:#15803d

Implementation

Step 1: Create a serverless index

// dotnet add package Pinecone.Client
using Pinecone;

var pc = new PineconeClient(Environment.GetEnvironmentVariable("PINECONE_API_KEY")!);

await pc.CreateIndexAsync(new CreateIndexRequest
{
    Name      = "rag-prod",
    Dimension = 1536,
    Metric    = CreateIndexRequestMetric.Cosine,
    Spec      = new ServerlessIndexSpec
    {
        Serverless = new ServerlessSpec
        {
            Cloud  = ServerlessSpecCloud.Aws,
            Region = "us-east-1"
        }
    }
});

Step 2: Upsert in batches with namespaces

var index = pc.Index("rag-prod");

// Batch up to 100-1000 per request; keep total payload &lt; 2 MB
var batch = chunks.Select(c => new Vector
{
    Id     = c.Id,
    Values = c.Embedding,                   // float[]
    Metadata = new Metadata
    {
        ["doc_id"]   = c.DocId,
        ["title"]    = c.Title,
        ["category"] = c.Category
    }
}).ToList();

await index.UpsertAsync(new UpsertRequest
{
    Vectors   = batch,
    Namespace = $"tenant-{tenantId}"        // โ† isolation boundary
});

Step 3: Query with metadata filter

var resp = await index.QueryAsync(new QueryRequest
{
    Namespace       = $"tenant-{tenantId}",
    Vector          = queryEmbedding,
    TopK            = 10,
    IncludeMetadata = true,
    Filter = new Metadata
    {
        ["category"] = new Metadata { ["$in"] = new[] { "docs", "blog" } }
    }
});

foreach (var m in resp.Matches)
    Console.WriteLine($"{m.Score:F3}  {m.Metadata?["title"]}");

Step 4: Hybrid (sparse + dense) for keyword + semantic

// Build sparse vector with BM25 / SPLADE / pinecone-text
var sparse = new SparseValues
{
    Indices = sparseIndices,    // uint[]
    Values  = sparseWeights     // float[]
};

var hybrid = await index.QueryAsync(new QueryRequest
{
    Namespace    = $"tenant-{tenantId}",
    Vector       = denseEmbedding,
    SparseVector = sparse,
    TopK         = 20,
    IncludeMetadata = true
});

Step 5: Rerank top-k with the built-in reranker

var reranked = await pc.Inference.RerankAsync(new RerankRequest
{
    Model     = "bge-reranker-v2-m3",
    Query     = userQuery,
    Documents = hybrid.Matches
        .Select(m => new Document { Text = (string)m.Metadata!["title"]! })
        .ToList(),
    TopN          = 5,
    ReturnDocuments = true
});

Step 6: Delete-by-namespace for tenant offboarding

// One call wipes all vectors for a tenant โ€” fast and atomic
await index.DeleteAsync(new DeleteRequest
{
    DeleteAll = true,
    Namespace = $"tenant-{tenantId}"
});

Common Pitfalls

  1. One namespace per index. Each index has fixed cost overhead. Use namespaces inside a shared index for tenants; reserve separate indexes for separate dimensions or metrics.
  2. No metadata limit awareness. Per-vector metadata is capped (~40 KB). Storing the full document text inflates writes and reads โ€” keep an ID and look the body up elsewhere.
  3. Tiny upsert batches. One vector per request burns write units and latency. Batch 100-1000 per call.
  4. Querying without a namespace. The default namespace becomes a shared landfill across tenants โ€” privacy bug waiting to happen. Always pass Namespace.
  5. Sparse vectors as an afterthought. For factual / keyword queries, hybrid often doubles relevance. Build sparse at ingest time, not at query time.
  6. Forgetting eventual consistency.Upserts are visible within seconds, not milliseconds. Don't write-then-immediately-read in tests.

Practical Takeaways

  • Default to a single serverless index with namespace-per-tenant.
  • Keep metadata small (IDs and filters), not the document body.
  • Batch upserts; the SDK is happy with hundreds per request.
  • Use hybrid (sparse + dense) for any query mix that includes keywords.
  • Rerank top-20 down to top-5 โ€” built-in rerankers add < 100 ms and meaningfully improve relevance.
  • DeleteAll = true with a namespace is your tenant-offboarding primitive.
  • For < 5M vectors, pgvector is usually cheaper. Pick Pinecone when scale or operational simplicity wins.