turbopuffer in production
turbopuffer in production
perational gotchas from running turbopuffer (a hosted vector database) under two systems: pub-search, a search service over atproto publications, and phi, a bluesky agent with per-user memory namespaces. none of these are in the quickstart.
ids are capped at 64 bytes
at-uris routinely exceed it. pub-search sha256-hashes the uri and keeps 32 hex chars (128 bits — collision-safe at any realistic scale). the cost: you can't recover the uri from the id, so it must also live in an attribute or an external store.
schema evolution: don't name attributes you might not have
a query listing include_attributes: ["source_uris", ...] 400s against a
namespace created before that attribute existed. phi's memory queries with
include_attributes: True (all attributes) and reads defensively — old
namespaces return rows without the new fields instead of erroring the whole
query. the alternative (migrating every namespace on schema change) is worse
when namespaces are per-user and created lazily.
cold namespaces are slow
first query against a cold namespace pays ~600–900ms. pub-search runs a keepalive thread that pings every 3 minutes — dumb, effective. worth it the moment p50 matters.
attributes go stale; keep an authoritative store
attributes are written at embed time and never updated unless you patch them, so they drift from reality (a document's path changes; its tpuf attribute doesn't). pub-search stores platform/path metadata as tpuf attributes but reads the authoritative values from its local sqlite replica at query time, using the tpuf copy only when the replica misses.
missing predicates get pushed client-side
no since filtering on the ann query — date filtering happens after retrieval,
client-side, with the same semantics as the sql path (lexicographic iso-8601
compare; a row with no timestamp fails the filter, because it can't prove
it's in range). over-fetch accordingly.
the dummy-vector scan idiom
to page through rows regardless of similarity (export, centroid computation),
query with a constant vector ([0.5] * dims) and high top_k — ann as a poor
man's table scan. both phi's memory (per-user centroids for the /memory graph)
and the atlas flow use this. fine for batch paths; don't put it on a hot path.
patch, don't rewrite
soft-delete via patch_rows (phi marks superseded observations in place)
keeps history queryable and avoids re-embedding — the vector doesn't change
when the status does.
sources
- pub-search/backend/src/db/tpuf.zig — id hashing, keepalive, client-side filters
- bot/src/bot/memory/namespace_memory.py — schema-evolution defense, patch_rows, centroid scan
Did you enjoy this article?
Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.