Cost OptimizationVector SearchTech ArchitectureAWSAI Infrastructure

Cut the AI Chatbot Tax
Before It Cuts You

Every engineering leader shipped the enterprise AI assistant. Now the CFO wants the cloud bill explained. This is a companion to the RAG + MCP tech stack post — that post flagged OpenSearch instance-hours as the largest recurring cost once you cross a few million chunks. This one picks up exactly there: a worked case study on where that memory actually goes, the two levers every team reaches for first, and the object-storage-first architecture that cuts the bill by up to 90% for the right workloads.

Shashank PadalaFounder, Kirak Labs 16 min readJul 26, 2026
Target: Data engineers · Platform architects · Technical PMs
A glowing hot orange server rack radiating cost particles on the left, connected to a calm cool-blue tiered pyramid representing cheap cold object storage on the right.
Chapter 00

The Cost Nobody Runs the Math On

Every company that shipped a RAG chatbot in the last two years hit the same unbudgeted line item. Turn your docs, your Slack history, and your codebase into embeddings, and your data footprint doesn't grow a little — it explodes. A rough rule of thumb: 1KB of source text becomes roughly 16KB once it's chunked and embedded (the chunk text itself, plus a 1024-dimension float vector per chunk, plus metadata). Vectorize your whole knowledge base and you've 16x'd what has to be stored — and, in most vector databases, stored hot.

That distinction — hot vs. cold — is the entire story of this post. A team running a $3,000/month Postgres bill can watch it become a $30,000/month bill the moment the same data is fully vectorized into a conventional vector database, because every one of those databases was built on the same unquestioned assumption: the whole inflated footprint has to sit in RAM or on premium SSD, always on, always fast, always expensive — whether anyone is querying it or not.

The counter-example: turbopuffer
Two ex-Shopify infrastructure engineers built a vector search company on the opposite bet — most of a corpus sits cold 95% of the time, so why pay always-on prices for it? The result, turbopuffer, raised under $1M, reached roughly $100M ARR in under three years with fewer than 40 employees, and now powers search infrastructure behind Anthropic, Cursor, Notion, and Linear. The rest of this post works through the mechanics of how, and where the same idea does and doesn't apply to your own stack.
Chapter 01

Case Study: Where the Memory Actually Goes

Back-of-envelope math on a mid-size corpus, using the same OpenSearch index mapping from the companion stack post.

Say a company has 500,000 source documents — support tickets, wiki pages, transcripts — averaging 3 chunks each after semantic chunking. That's 1.5M chunks. Each one carries a 1024-dimension embedding (the same dimension used in the OpenSearch mapping from The Technology Stack Behind Production RAG + MCP), stored as 32-bit floats.

Before the math: almost every mainstream vector database — including OpenSearch — finds those embeddings using HNSW (Hierarchical Navigable Small World), an algorithm that connects every vector to a small set of its nearest neighbors, forming a searchable graph. A query walks that graph hopping toward closer and closer neighbors instead of comparing against every vector in the corpus — which is what makes vector search fast at scale. The tradeoff is the one this whole post is about: that graph only works if it stays resident in memory, so every vector you add doesn't just cost you its own storage, it costs you a permanent slot in RAM.

Back-of-envelope: raw vector footprint
1.5M chunks × 1024 dimensions × 4 bytes/float
  = 1.5M × 4,096 bytes
  = ~6.1 GB of raw vector data

HNSW graph overhead (neighbor links, per-vector, at m=16)
  adds roughly another 1.0–1.5x on top of raw vectors
  → ~9–14 GB resident in memory for the vector index alone

Add replicas for HA (typically 2–3x for a production cluster)
  → ~20–40 GB of RAM has to be provisioned,
     just to hold the vector field — before the text index,
     OS overhead, or headroom for growth

None of those numbers move based on how often the corpus is queried. A cluster serving ten queries a day and a cluster serving ten thousand pay the identical memory bill, because HNSW keeps the entire neighbor graph resident to guarantee fast lookups — as covered in the index layer chapter of the companion post. You are paying for the data existing, not for the data being used.

This is a 500K-document company. Most enterprises are bigger.
Scale the same math to 5M source documents — not unusual for a company vectorizing its full Slack history, ticket archive, and codebase — and the resident memory requirement scales linearly to roughly 200–400GB. That's the point at which OpenSearch/managed-vector-DB instance-hours stop being a line item and start being the largest recurring AWS cost in the entire stack.
Chapter 02

The Two Levers Everyone Pulls First

Both assume the premise is correct: that the whole corpus has to stay resident in memory. They just negotiate the price of that premise.

The first lever is two HNSW knobs on the index mapping: m controls how many neighbor connections each vector keeps in the graph (higher = better recall, more memory, slower to build); ef_construction controls how hard the graph search looks for those neighbors while building the index (higher = better graph quality, slower indexing, more memory). Both are quiet, no-recall-hit-you'd-notice ways to shrink memory per vector — which is exactly why they're usually the first thing tried.

LeverWhat it trades awayCeiling
Reduce HNSW m / ef_constructionFewer neighbor connections per vector and a less thorough graph build → a small, usually invisible drop in recall (occasionally missing a marginally relevant chunk)Shrinks memory per vector, but the corpus still has to be fully resident — this delays the next instance upgrade, it doesn't remove the need for one
Provision bigger / more instancesNothing algorithmic — pure cost. A permanent, recurring increase, not a one-time expenseScales linearly with corpus size forever; there's no point at which this gets cheaper on its own
Both levers are optimizing the wrong variable
Tuning HNSW parameters and buying bigger nodes both answer the question "how do we afford keeping everything hot?" Neither one questions whether everything needs to be hot in the first place — which is a data-access-pattern question, not a database-tuning question. For the full derivation of why vector fields are memory-hungry in the first place, see the index layer chapter of the tech stack post.
Chapter 03

The Question Nobody Asked: Does It All Need to Be Hot?

Most vectors in a RAG corpus are cold at any given moment. turbopuffer built its entire architecture around that one observation.

A RAG query distribution isn't uniform. A handful of recent documents, popular topics, or actively-open codebases get hit constantly; the long tail of a 5M-chunk corpus might go days without a single query touching it. Every mainstream vector database — OpenSearch, Pinecone, Weaviate, pgvector — ignores that distribution and keeps 100% of the corpus resident in RAM or premium SSD regardless. turbopuffer's architecture is what happens when two infrastructure engineers refuse to accept that as a given.

Instead of RAM as the default home for vectors, object storage (S3, GCS, Azure Blob) is the default — at roughly $0.02/GB versus the ~$1.40/GB effective cost of a managed vector database keeping everything resident. That's a ~70x storage cost gap per gigabyte. Data only gets promoted into faster tiers when something actually asks for it:

Object storage200–500msDefault home for every vector — cold, cheap, always durable ($0.02/GB)
NVMe SSD cache~10–20msRecently or repeatedly accessed data, auto-promoted — 4–5x slower than RAM, 100x cheaper
RAM cache<10msActively hot working set only — a small fraction of the total corpus at any moment

The index structure itself is different too — not a memory-resident HNSW graph, but clustered indexes: vectors grouped into semantic clusters with small centroid summaries kept cheap to scan. A query reads the centroids first to identify a handful of relevant clusters, then pulls only those clusters' full vectors from storage — the opposite of HNSW's assumption that the whole graph must already be in memory to be useful.

What this looks like in production: Cursor
When a developer opens a codebase in Cursor, that namespace's cache starts warming automatically. The first semantic search after opening is the slow one (cold); everything after is fast (warm), because the working set for that session is now resident in the SSD/RAM tiers. Cursor reports indexing much larger repositories while cutting the associated cost by roughly 95% versus keeping every repository's vectors permanently hot.
Chapter 04

Why 2020 and 2023 Matter

This architecture wasn't possible five years ago. It needed two specific S3 features to ship first.

You can't build a database on a storage layer that might hand you stale or conflicting data. For most of its history, S3 couldn't make either guarantee strongly enough — two changes fixed that, and both are load-bearing for treating S3 as a database backend rather than just a file dump.

4.1 — Strong read-after-write consistency (Dec 2020)

Before this, S3 was only eventually consistent for overwrites and deletes — write a new version of an object, and a read immediately after could still return the old version, or briefly 404. For a static website, that's a non-issue. For a database index, it's disqualifying.

What breaks without it
Picture an ingestion worker updating a cluster's centroid file after indexing new chunks. A query arriving milliseconds later, routed to a different S3 replica, could read the stale centroid file and silently skip the cluster containing the newest data — a query that returns confidently wrong, incomplete results with no error thrown anywhere. Strong consistency (every read reflects the most recent completed write, no exceptions) removes that failure mode entirely.

4.2 — Conditional writes / compare-and-swap (Nov 2023)

Before this, S3 had no atomic "update this object only if it hasn't changed since I last read it" primitive. Two concurrent writers to the same object raced under last-write-wins semantics — whichever write landed second silently clobbered the first, with no error, no conflict signal, nothing to retry.

What breaks without it
Two ingestion pipelines both append new chunks to the same namespace's manifest at nearly the same time. Without compare-and-swap, whichever write lands last simply overwrites the other — the first pipeline's chunks vanish from the index with no error logged anywhere, a silent data-loss bug that's brutal to trace back to its cause. With conditional writes (S3's If-Match / If-None-Match headers), the second writer's request is rejected because the object changed underneath it — it detects the conflict and retries, instead of destroying the first writer's work.
Why this is the real unlock
Together, these two features let S3 support the two guarantees any database needs from its storage layer: readers always see the latest committed state, and concurrent writers can safely coordinate without corrupting each other's changes. Before Nov 2023, building this on S3 would have required bolting on a separate coordination service (a lock table in DynamoDB, a ZooKeeper cluster) just to arbitrate writes — extra infrastructure, extra latency, extra failure modes. After it, S3 alone is a sufficient, sufficiently boring foundation. That's the precondition the rest of this architecture depends on.
Chapter 05

Same Corpus, Two Architectures

Applying the published per-GB numbers to the Chapter 01 case study — directional, not a vendor quote.

ArchitectureWhat's always paid forCost driver at ~10–40GB resident
Always-hot (OpenSearch / managed vector DB)100% of the corpus, in RAM/SSD, continuously, regardless of query volume~$1.40/GB effective → the full resident footprint priced every month, whether or not it's queried
Object-storage-first + tiered cache (turbopuffer-style)100% of the corpus in object storage (cold); only the actively-queried working set promoted to SSD/RAM~$0.02/GB baseline storage, plus a much smaller hot-tier footprint sized to actual query traffic, not corpus size

The ~70x per-gigabyte gap doesn't translate into a flat 70x bill reduction — some working set still has to live in the fast tiers, and cold-query latency is a real cost paid in user experience rather than dollars (more on that next). But it explains why teams making this switch report savings in the 10–100x range rather than a marginal 20–30% optimization: the two architectures are pricing fundamentally different things — total corpus size versus actual query traffic.

Chapter 06

How the Gap Widens as the Corpus Grows

Same math as Chapter 01, extended across corpus sizes. Costs below are storage-only, order-of-magnitude estimates from the published per-GB figures — not a vendor quote, and real cluster bills also include compute nodes, redundancy, and query capacity on top of this.

DocumentsChunks (~3/doc)Resident memory (hot, w/ HNSW + replicas)Illustrative hot storage cost/moIllustrative cold storage cost/mo
500K1.5M~20–40GB~$28–56~$0.12
2M6M~80–160GB~$112–224~$0.49
5M15M~200–400GB~$280–560~$1.23
20M60M~0.8–1.6TB~$1,120–2,240~$4.92
100M300M~4–8TB~$5,680–11,180~$24.60
1B3B~40–80TB~$56,770–111,820~$245.76
100B300B~4–8PB~$5.68M–11.18M~$24,576

Two things to notice. First, the cold-storage column barely moves — it's pricing raw bytes at $0.02/GB, so even 300B chunks of vector data lands under $25K/month, which is inexpensive at object-storage rates. Second, the hot column scales linearly with corpus size forever, because that architecture has no mechanism to distinguish a chunk queried every second from one that hasn't been touched in a year — both cost the same to keep resident. By the time a corpus reaches billions of chunks, the always-hot bill is in the tens or hundreds of thousands of dollars a month for storage alone, while the cold tier is still a rounding error.

The last three rows are deliberately extreme — no single enterprise corpus realistically reaches 100B chunks — but they make the shape of the divergence obvious: the always-hot column has no ceiling, it just keeps climbing with corpus size, while the cold column grows so slowly it's barely visible on the same chart. The object-storage-first total isn't just the cold-storage column, though — you still pay for whatever working set actually gets promoted to the SSD/RAM cache tiers. That number depends on traffic, not corpus size, which is exactly the point: it can stay small and flat even as the underlying corpus grows without bound.

Chapter 07

At What Point Switching Makes Sense

Two conditions, both required — corpus size alone isn't the trigger.

Chapter 06's table makes it tempting to say "switch once you cross N chunks." That's necessary but not sufficient. The second condition is about how the corpus is queried, not just how big it is: object-storage-first only pays off when access is skewed — a small, shifting subset of the corpus gets most of the queries at any given time, and the rest sits idle. A corpus that's large but queried uniformly (every chunk equally likely to be hit at any moment) gets none of the caching benefit, because nothing stays cold long enough to matter.

Use caseWhat makes access skewedFits object-storage-first?
Cursor — codebase semantic searchA developer works in one or two repos per session; the rest of their indexed repositories sit untouched between sessionsYes — per-repo cache warms on open, reported ~95% cost reduction vs. keeping every repo permanently hot
Notion — cross-workspace document searchEach workspace has a long tail of pages nobody's opened in months, plus a small set of actively-edited or recently-viewed pagesYes — recency and edit activity naturally concentrate queries on a small hot subset
Linear — issue tracker semantic searchOpen/active issues get referenced constantly; resolved issues from a year ago are rarely pulled back upYes — issue status is itself a strong skew signal (open vs. closed)
Proprietary expert-network / research corpusWhich experts or topics are "hot" tracks active client engagements and current market questions; a multi-year transcript archive is mostly dormant at any momentYes — research demand is inherently topical and time-bound, not uniform across the archive
Real-time fraud/trading signal lookupsEvery incoming transaction or tick needs a comparison against the full reference set, continuously, with no natural "recent vs. stale" splitNo — access is close to uniform and latency-critical on every single call; this is the Chapter 08 counter-example
The breakpoint, stated plainly
Switch when both hold: (1) the corpus is large enough that Chapter 06's hot-storage cost is a real budget line — roughly a few million chunks and up — and (2) query access is skewed toward a shifting subset of recent, active, or topically relevant content rather than spread evenly across the whole corpus. Size alone, without the access-pattern skew, just gets you a smaller always-hot bill from Chapter 02's tuning levers — not the 10–100x jump this post opened with.
Chapter 08

When Not to Do This

Every 10–100x cost story has a matching latency story. Here's the honest version.

SituationWhy object-storage-first is the wrong call
Hard, tight synchronous latency SLA on every queryCold-tier reads run 200–500ms (published p90 ≈ 444ms); if every query must return in well under that, even occasionally, a memory-resident index is the safer default
Uniformly hot access pattern — the whole corpus is queried constantlyIf nothing is ever cold, there's no cost saving to capture and no cache-warming benefit; you'd just be paying object-storage latency with none of its cost upside
Small corpus (well under the case study's ~1.5M chunks)Below a certain size, the whole thing fits comfortably and cheaply in a single memory-resident instance anyway — this problem doesn't exist yet
Team has no tolerance for variable latency in the product experienceA cache-miss-shaped tail latency spike needs to be an acceptable UX tradeoff, not a surprise discovered in production
This is a fourth option, not a universal replacement
The companion stack post's index-layer alternatives table compared managed OpenSearch, Aurora + pgvector, and Pinecone/Weaviate. Object-storage-first (turbopuffer-style) belongs as a fourth row in that same table — the right call specifically when your corpus is large, your access pattern is skewed (most data cold most of the time), and your workload can tolerate a cold-cache tail. It's a sharper tool for a specific access pattern, not a strictly better database.

What We Covered

  • Why vectorizing a corpus inflates its footprint ~16x, and why that inflated data defaults to being stored hot
  • A worked case study: 1.5M chunks at 1024 dimensions costs 20–40GB of always-resident RAM once HNSW overhead and replicas are counted
  • The two levers every team reaches for first — tuning m/ef_construction and buying bigger instances — and why both accept the same flawed premise
  • turbopuffer's object-storage-first architecture: cheap cold storage by default, clustered indexes instead of a memory-resident HNSW graph, and automatic tiered caching
  • The two specific S3 features (strong consistency in 2020, compare-and-swap in 2023) that had to ship before this architecture was even possible
  • A directional cost comparison between always-hot and object-storage-first architectures on the same corpus
  • How that cost gap widens with corpus size — the hot column scales linearly forever, the cold column barely moves
  • The real breakpoint for switching: corpus size and access-pattern skew both have to hold, illustrated against Cursor, Notion, Linear, and a proprietary research-corpus use case
  • When this pattern is the wrong call — tight latency SLAs, uniformly hot access, or corpora too small to have this problem yet
Shashank Padala

Shashank Padala

Founder, Kirak Labs · AI Product Leader

AI Product & Transformation Leader with 8+ years building production LLM systems. Previously led GenAI integration into an internal content-authoring platform at a Fortune 500 enterprise, serving millions of employees globally — an AI assistant embedded in the CMS that surfaced grounded, cited insight from engagement and support-ticket data to inform what the team published next.

SYSTEM ONLINE
RAG Pipeline Active
Vector DB Connected
Guardrails Enabled