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.
- 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 < 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
- 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.
- 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.
- Tiny upsert batches. One vector per request burns write units and latency. Batch 100-1000 per call.
- Querying without a namespace. The default namespace becomes a shared landfill across tenants โ privacy bug waiting to happen. Always pass
Namespace. - Sparse vectors as an afterthought. For factual / keyword queries, hybrid often doubles relevance. Build sparse at ingest time, not at query time.
- 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 = truewith a namespace is your tenant-offboarding primitive.- For < 5M vectors, pgvector is usually cheaper. Pick Pinecone when scale or operational simplicity wins.

