Optimizing Performance

turbopuffer is designed to be performant by default, but there are ways to optimize performance further. These suggestions aren't requirements for good performance--rather, they highlight opportunities for improvement when you have the flexibility to choose.

For example, while a single namespace with 100M documents works fine, splitting it into 10 namespaces of 10M documents each may yield better query performance if there's a natural way to group the documents.

Choose the Closest Region

Choose the region closest to your backend. We can't beat the speed of light. If there isn't a region close to us and the latency is paramount, contact us.

HTTP Connection Reuse

Use the same Turbopuffer client instance for as many requests as possible. This uses a connection pool behind the scenes to avoid the overhead of a TCP and TLS handshake on every request.

Use U64 or UUID IDs

The smaller the IDs, the faster the puffin'. A UUID encoded as a string is 36 bytes, whereas the UUID-native type is 16 bytes. A u64 is even smaller at 8 bytes.

Inverted Index

Attribute values that are filterable are indexed into an inverted index. Inverted indexes means large intersects can be much faster than on a traditional B-Tree index.

Disable Filtering for Unfiltered Attributes

For attributes you never intend to filter on, marking attributes as filterable: false will improve indexing performance and grant you a 50% discount. For large attribute values, e.g. storing a raw text chunk or image, this can improve performance and cost significantly.

Use Small Namespaces

The rule of thumb is to make the namespaces as small as they can be without having to routinely query more than one at a time. If documents have significantly different schemas, it's also worth splitting them. Don't try to be too clever. Smaller namespaces will be faster to query and index.

Prewarm Namespaces

If your application is latency-sensitive, consider warming the cache for the namespace before the user interacts with it (e.g. when they open the search or chat dialog).

Use Smaller Vectors

Smaller vectors will be faster to search, e.g. 512 dimensions will be faster than 1536 dimensions. f16 will be faster than f32. The tradeoff with smaller vectors is typically lower search precision. Consider the cost/performance vs precision tradeoff with your own evals. For models with quantization-aware training (voyage-4 series, voyage-context-3, embed-v4, Qwen3-VL-Embedding-8B), int8 output matches f32 precision (benchmarks), so you can pass int8 values directly as JSON integers to an f16 namespace for f16 speed with no precision loss.

Use Branching to Duplicate Namespaces

If you're creating copies of namespaces for testing, backups, or code repositories, branching creates a copy-on-write clone in constant time regardless of namespace size.

Batch Writes

If you're writing a lot of documents, consider batching them into fewer writes. This will improve performance and leverages batch discounts up to 50%. Each individual write batch request can be a maximum of 512MB.

Concurrent Writes

If you're writing a lot of documents, consider using multiple processes to write batches in parallel. Especially for single-threaded runtimes like Node.js or Python, this can be a significant performance boost as upserting is generally bottlenecked by serialization and compression.

Control include_attributes

The more data we have to return, the slower it will be. Make sure to only specify the attributes you need.

Use Eventual Consistency

If you need higher query throughput and can tolerate stale results, consider using eventual consistency for your queries.

Pin High QPS Namespaces

For sustained high query volumes over a few, large namespaces, consider namespace pinning to provision reserved compute nodes for more predictable cost and performance with always-warm cache.

Avoid Large Attributes with Frequent Patches

When using patch or patch_by_filter, turbopuffer currently reads all attributes of the documents being patched, even those not being modified. If you have large attributes (>10KB), consider storing them in a separate namespace linked by id. For example, if you have chunks with vectors and a large metadata blob that's shared across chunks, store the metadata in a separate namespace keyed by a shared id (e.g. file_id). At query time, do a vector search on chunks, then look up the metadata using the unique ids from your results. This way, patches to chunk-specific attributes never touch the large metadata.

Keep First-Stage Ranking Simple

rank_by expressions can quickly become quite sophisticated. For best performance, we recommend keeping the first-stage ranking function simple, with only a few attributes being used to compute BM25 scores and/or attribute scores, retrieving in the order of 100 to 1,000 hits, and then applying more sophisticated ranking in the second stage.

  • Understand how Glob and Regex filters are optimized. Under the hood, they use a trigram-based index to quickly narrow down the set of possibly matching candidates. As a general rule of thumb, the more specific the pattern, the better the performance. Anchored patterns (turbo* or *puffer) are much more specific than unanchored patterns (*tpuf*), and thus will perform better. Avoid unspecific patterns like [a-z]*, which require a full-table scan.
  • Separate ANN and BM25 index namespaces. If indexing performance suffers on a namespace with both ANN and BM25 indexes, we recommend splitting these indexes into separate namespaces to improve throughput. We're actively working on improving performance for combined ANN and BM25 namespaces, and this temporary workaround will be unnecessary soon.