Deep expertise in Turbopuffer — object-storage-first vector + full-text search, three-tier caching, and ~$0.02/GB cold storage economics. Use when implementing vector search, embeddings storage, or similarity queries with turbopuffer.
Installs into .claude/skills of the current project.
Are you the author of Turbopuffer Expert?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-turbopuffer-expert)
---
name: turbopuffer-expert
description: Deep expertise in Turbopuffer — object-storage-first vector + full-text search, three-tier caching, and ~$0.02/GB cold storage economics. Use when implementing vector search, embeddings storage, or similarity queries with turbopuffer.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: vector-stores
tags: [vector-db, turbopuffer, object-storage, s3, namespaces, cost-optimization]
---
# Turbopuffer Expert Mode
You are an expert in Turbopuffer — the search engine that puts S3 / GCS / Azure Blob underneath every namespace and pays back its bill in 95-100x cost reductions. You design schemas that keep hot data in NVMe cache and cold data on object storage, accepting 200-500 ms cold-query latency as the trade.
## Core Capabilities
- Namespace-per-tenant model — billions of namespaces, no per-namespace fixed cost
- Three-tier storage: object store (cold, ~$0.02/GB) → NVMe cache (warm) → RAM (hot)
- Vector + full-text + filter combined in one query
- Write-through to object storage with strong consistency on read-after-write
- Automatic tiering based on access pattern; cold namespaces drop out of cache
- Used at scale by Cursor, Notion, Linear, Superhuman
## Index/Storage Internals
Turbopuffer treats each namespace as a self-contained, append-only log of vectors + attributes on object storage. Queries:
- **Cold path**: download relevant segments from S3, decode, search → 200-500 ms
- **Warm path**: NVMe cache hit, search local → 10-50 ms
- **Hot path**: in-memory index, search → < 10 ms
Indexes are not pre-built per row; they are computed lazily over segments and cached. This makes ingest extremely cheap (just S3 PUT) and reads cheap-when-cold but expensive in latency.
## Query Patterns
### Upsert vectors with attributes
```python
import turbopuffer as tpuf
tpuf.api_key = "tpuf_…"
ns = tpuf.Namespace("tenant-acme")
ns.upsert(
vectors=[
tpuf.VectorRow(
id="doc1#0",
vector=[0.01]*1024,
attributes={
"text": "Turbopuffer keeps vectors on object storage.",
"category": "docs",
"ts": 1735689600,
},
),
],
distance_metric="cosine_distance",
schema={
"text": {"type": "string", "full_text_search": True},
"category": {"type": "string", "filterable": True},
"ts": {"type": "uint", "filterable": True},
},
)
```
### Vector + filter + FTS in one query
```python
results = ns.query(
rank_by=("vector", "ANN", [0.01]*1024),
top_k=20,
filters=("And", [
("category", "Eq", "docs"),
("ts", "Gte", 1735689600),
]),
include_attributes=["text", "category"],
)
```
### Pure full-text + BM25 ranking
```python
ns.query(
rank_by=("text", "BM25", "object storage vector database"),
top_k=10,
filters=("category", "In", ["docs", "blog"]),
)
```
### Hybrid via two queries + RRF (client-side)
```python
def rrf(results_a, results_b, k=60):
scores = {}
for rank, hit in enumerate(results_a, 1): scores[hit.id] = scores.get(hit.id, 0) + 1/(k+rank)
for rank, hit in enumerate(results_b, 1): scores[hit.id] = scores.get(hit.id, 0) + 1/(k+rank)
return sorted(scores.items(), key=lambda x: -x[1])
dense = ns.query(rank_by=("vector", "ANN", q_vec), top_k=50).rows
lex = ns.query(rank_by=("text", "BM25", q_text), top_k=50).rows
top = rrf(dense, lex)[:10]
```
### Bulk export / reindex
```python
# Iterate all rows of a namespace (paginated)
for row in ns.list(include_vectors=True, include_attributes=True):
process(row)
# Drop a namespace
ns.delete_all()
```
## Performance Tuning
- Cold-tier latency is fundamental — design UX with optimistic/skeleton states
- Pre-warm critical namespaces with a synthetic query right before user traffic
- Keep namespaces small enough that one warm cache fill is affordable (< few GB)
- Avoid scattering one tenant across many namespaces — the cache is per-namespace
- Batch writes — many tiny writes still translate to one S3 PUT each (cost adds up)
- Use BM25 instead of standing up another full-text engine alongside
## Common Pitfalls
- Treating Turbopuffer as a low-latency-everywhere DB — cold queries are SLOW by design
- Single huge namespace for all tenants — defeats per-tenant cache isolation
- Forgetting to declare `full_text_search` / `filterable` in schema — missing index = scan
- Polling stats per query for cost — costs query units; scrape periodically instead
- Mixing distance metrics across upserts — namespace metric is fixed at first write
- Expecting transactions / multi-row atomicity — namespace is the consistency boundary
## When to Use This Mode
- Multi-tenant product (note-taking, IDE, CRM, etc.) with millions of small tenants
- Long-tail access pattern — most data is touched rarely; storage cost dominates
- You'd rather pay 200 ms cold latency than $X/GB-month for hot SSDs
- Build-your-own-namespace pattern (per repo, per workspace, per user)
- Teams already accustomed to S3/GCS billing math
## Sources
- Turbopuffer site: https://turbopuffer.com/
- Architecture article: https://jxnl.co/writing/2025/09/11/turbopuffer-object-storage-first-vector-database-architecture/
- Engineering deep-dive: https://turbopuffer.com/blog/turbopuffer
- Roadmap: https://turbopuffer.com/docs/roadmap
- Cursor / Notion case study: https://www.pmf.show/blog/how-simon-eskildsen-built-turbopuffer-the-vector-db-powering-cursor-and-notion/