Runbooks/ RAG Runbook Track A · Ingestion and chunking Deep readQuick learn
  1. 00 Start
  2. /
  3. 01 Chunking
  4. 02 Parsing
  5. 03 Identity
  6. 04 Access
  7. /
  8. 05 Models
  9. 06 Vectors
  10. 07 Limits
  11. 08 Model ops
  12. 09 Index I
  13. 10 Index II
  14. 11 Tuning
  15. 12 Capacity
  16. /
  17. 13 Sharding
  18. 14 Filtering
  19. 15 Hybrid
  20. /
  21. 16 Proof
Document 01 · Quick learn Retrieve what you embed · Seven rungs · Small to big

What one recordshould be

What one retrievable record should be, why the obvious answer is wrong, and the design that dissolves the central tradeoff instead of accepting it.

“You retrieve what you embed. Everything downstream can only reorder what chunking made findable.”
0.42 → 0.81
same text, cut differently
2 & 5
the two rungs you actually reach for
30 → 8
children retrieved → parents sent
5–15 ms
the whole cost of parent–child
Why chunking existsa vector is an average

A retrieval system finds things by comparing meaning. The catch is that a vector is an average. Feed in a forty-page HR policy and you get one vector meaning “HR policy in general”. It is close to everything and specific to nothing.

Embed the whole document

40 pages, one vector

Eligibility, notice, pay, return to work, dress code, expenses…

Two questions

Notice period? Dress code?

Two scores

0.42 and 0.41

Indistinguishable

Both questions are roughly equidistant from the single document vector. The system cannot tell them apart, because the vector is about everything at once.

And at a million documents, forty other chunks score between 0.42 and 0.48 — it is rank 41

Embed each chunk

40 chunks, 40 vectors

Each one sitting near the topic it is about.

The same questions

In the same space.

Two scores

0.81 and 0.78

One clear nearest neighbour each

The document is identical in both halves. The only thing that changed is what got embedded — and that changed what can be found.

Stable as the corpus grows, which is the part that only shows up at scale
The one-line framing

You retrieve what you embed. The chunk is your unit of retrieval, so chunking decides what your system is capable of finding at all.

Everything downstream — reranking, hybrid search, a bigger context window, a better language model — can only reorder what chunking made findable
The library version

Index the library by book title and every search returns whole books: you found the right book, and you still have to read it. Index by individual sentence and you find the exact sentence, but you have lost the chapter it belonged to.

Chunking is choosing where between those two extremes to sit — and parent–child is the trick for sitting in both places at once
The central tensionand the observation that dissolves it

Small chunks · 100–250 tokens

Focused vectors, low cost per query.

  • Embedding: the vector is about one idea, so it matches strongly and precisely
  • Context: lost. “It must be renewed within 30 days” — renewed what?
  • Cost: low. Few tokens sent to the model
  • Risk: the model lacks context, so it hedges or guesses
  • Vectors: many. Bigger index, more memory, slower to build
Two jobs.
One object.
That is the mistake.

Large chunks · 1,000+ tokens

Context preserved, and vectors that mean nothing in particular.

  • Embedding: diluted. The vector averages five topics, so it matches everything weakly
  • Context: preserved. The model can see what “it” refers to
  • Cost: high, and recurring — paid on every query, forever
  • Risk: lost in the middle. Models attend less reliably to buried content
  • Vectors: few. Smaller index
The trap inside this question

Most candidates treat chunk size as a number to tune — “512 with 50 overlap” — and stop there. The senior move is to notice that matching a query and answering a question are two different jobs, and that we have been forcing one object to do both.

That single observation leads straight to parent–child retrieval, and it is the difference between an answer that sounds read and an answer that sounds built
The strategy ladderknow all seven; reach for rung 2 and rung 5
1
Fixed size with overlapno parser needed — and wrong in two ways: it cuts mid-sentence and straight through tables, and every boundary depends on position
cheapest
2
Structure-awarecut on headings and paragraphs, which the author already marked — start here
the default
3
Layout-awarereading order, columns, tables kept whole — PDFs and scans
doc 02
4
Semanticembed every sentence and cut where meaning shifts — often not worth it
one call/sentence
5
Parent–childembed small, return large — the architect answer
dissolves the trade
6
Contextualgenerate a situating sentence per chunk before embedding
one LLM call/chunk
7
Late chunkingembed the whole document first, apply boundaries after
long-context model
Why rung 1 is wrong in two ways, not one

It cuts mid-sentence and straight through the table, so a row arrives without its header. Worse, every boundary depends on position: insert a paragraph at the top of the document and every chunk below it changes.

Which makes chunk identity unstable — every citation, every feedback record and every audit log points at something that moves. Document 03

What overlap actually buys, and what it does not

What it buys

An answer straddling a boundary is not lost in both chunks.

10–15% is typical for fixed-size chunking
What it costs

Proportionally more vectors, more index memory, more embedding spend.

15% overlap genuinely is 15% more tokens embedded — the one place chunk size changes the embedding bill
What it does not fix

Context loss. An overlap rarely contains the antecedent three paragraphs up.

With structural chunking, overlap is often zero — sections do not split sentences. If you are paying for overlap, you are usually cutting in the wrong places
Design the record before the splitterchunk size you can re-sweep; metadata you cannot
Filtering metadata

Tenant, ACL group tags, effective dates, classification. Constrains what can be retrieved, and has to be applied inside the search.

Store groups in acl_tags, never user IDs — store user IDs and every group-membership change becomes a re-index of every affected chunk
Provenance metadata

Document ID, section path, source URL, page number, version, content hash, embedding-model version. Makes citation, audit and debugging possible.

And section_path doubles as the cheapest recall improvement in this document — see below
Filtering must be applied inside the search

Filtering afterwards can return an empty page to a user who did have permitted matches — and it means some component saw a list of documents that user is not entitled to.

Document 04 gives this its own treatment
Why this matters more than chunk size

You can recover from a mediocre chunk size: sweep it and rebuild, an afternoon of work. You cannot recover from metadata you did not capture, because it is not in the index and is often no longer obtainable from the source system either.

How to choose chunk sizethe size follows from the shape of the questions

There is no universally correct number, but there is a correct process. If you do not know the query mix, you are not choosing a chunk size — you are picking one.

Fact lookup

“How many days notice for maternity leave?”

Small chunks, 100–250 tokens, with a parent fallback. Precision matters most
Procedural

“How do I request leave?”

Section-level. A procedure split in half is worse than useless — it is confidently incomplete
Comparative

“How does our India policy differ from the UK one?”

Chunking cannot solve this alone. Needs query decomposition and several retrievals
Summarisation

“Summarise the risks in this contract.”

Retrieval by chunk is the wrong primitive entirely. Route to document-level summarisation
Table lookup

“What was South region Q2 revenue?”

Row-level chunks with the header repeated on every row
Aggregation

“Which region grew fastest?”

Not a retrieval question at all. Text-to-SQL territory — and saying so is the right answer
Say this out loud

“Chunk size is not chosen in the abstract, it is fitted to the query distribution. Before picking a number I would want to know what people actually ask. If a meaningful share of queries turn out to be summarisation or aggregation, chunk size is the wrong lever entirely and I should be designing a router, not tuning a splitter.”

Workedchild 100–250 tokens · parent cap 1,000–1,500 · overlap 10–15% fixed, often 0 structural · depth 30–50 → 3–8sensible starting points
Parent–child retrievalsearch on a small chunk, feed a large one to the model

The tension in section two does not get traded off — it gets dissolved. Matching a query and answering a question are different jobs with different requirements, so stop making one object do both.

1 · Search children

Query matched against child vectors only. The permission filter runs inside.

Take the top 30
2 · Map to parents

Every child carries a parent_id.

3 · Deduplicate

30 children collapse to 8 distinct parents.

Skip this and you send the same section three times
4 · Fetch parent text

One key-value lookup per parent. The store is never searched, only read by key.

5–15 ms
5 · Assemble to budget

8 parents at ~1,200 tokens is ~9,600 tokens.

Cap to top N after reranking; bare children below the line

Tradeoffs to name before you are asked

Context budget

8 parents × 1,200 tokens = 9,600 tokens.

Cap it: top N after reranking, bare children below
Parent size

Above ~1,500 tokens, lost-in-the-middle returns.

Use a sliding window around the child instead of the whole section
Extra fetch hop

5–15 ms.

Nothing to do — but quote the number, because it shows you measured
Duplicate storage

Text stored twice.

Nothing. Say you considered it and dismissed it — text is the cheapest thing in the system

Variants worth knowing by name

Sentence window

The child is one sentence; the parent is built on the fly from k sentences either side.

Unstructured text with no sections. Needs no parent store at all
Auto-merging / hierarchical

Three levels. Several children of one medium chunk hit → merge up; several mediums → merge to the document.

Adaptive context width — and be honest that the third tier often does not earn its complexity
Summary indexing

Embed a model-written summary of the section; return the full section.

Discovery queries — “which document covers X?”. Costs one generation call per section
How to explain it to a non-technical stakeholder

“We index the book by paragraph so we can find the exact paragraph, but when we answer we read out the whole page, so the answer makes sense in context.”

Giving a chunk its context backat search time, not just at generation time

Parent–child fixes context at generation time: the model reads the parent. It does not fix context at search time — the child vector still knows nothing about the document it came from.

Contextual chunking

Generate a short situating sentence per chunk and prepend it before embedding.

Cost: one generation call per chunk
Hosted API? Yes — any model at all
Long context needed? No
Good at: adding facts the chunk never contained — the title, the date, the product it applies to
Late chunking

Embed the whole document to token-level vectors, then apply boundaries and pool within each chunk.

Cost: longer forward passes; no second model
Hosted API? No — needs token-level outputs, so effectively self-hosted
Long context needed? Yes, on the embedding model
Good at: resolving references the chunk lost — pronouns, “the above”, “this section”
The cheap version to try first

Before either technique, prepend the section_path you are already storing: “HR Policy 2024 > 3 Leave > 3.2 Maternity Leave” in front of the chunk text before embedding.

It costs one string concatenation, needs no model, and recovers a surprising share of what contextual chunking is bought for. Measure that baseline before paying for ten million generation calls — it reads as experienced rather than fashionable
When not to chunk this wayknowing where a technique does not apply reads as senior
The corpus fits in context

Retrieval is unnecessary complexity.

Say so rather than applying the machinery reflexively
Documents are already the right unit

A one-page ticket resolution is a chunk.

Index it whole, no parent. A parent adds nothing and costs tokens
It is a known-address lookup

“Get document X, section Y.”

Chunking is just storage layout at that point
The question is aggregation

“Which region grew fastest?”

Not retrieval at all — route it
The mixed-corpus answer, which is the one actually asked

Real systems have both shapes at once. A support assistant over help-centre articles and ticket resolutions has one corpus that is long, curated and structured, and one that is short, messy and already the right unit.

Chunk them differently and keep them distinguishable — separate at the index level, or at minimum tag them. The ranking policy differs: articles are authoritative and stable; ticket resolutions are numerous and sometimes wrong
The cost modelabove a certain traffic level, chunk size stops being an accuracy decision
Worked500k docs × 30 pages × 500 tokens = 7.5B tokens · at 400 tokens that is 18.75M chunks; at 200, 37.5M; at 800, 9.4Mcorpus sizing first
Embedding the corpus is a one-time capital cost

You embed the same 7.5 billion tokens whichever chunk size you pick, so that line is a wash. Overlap is the only exception.

What changes is the number of vectors, which affects index memory and build time
Context is a recurring operational cost

Paid on every query, forever, and proportional to chunk size.

So above a certain volume, chunk size is a cost decision that happens to affect accuracy, rather than an accuracy decision that happens to cost money
How to explain the whole bill to a CFO

Three buckets. A one-time cost to process the existing corpus, dominated by document parsing rather than by anything AI-shaped. A recurring per-query cost, mostly the tokens we send to the model, so it scales with usage and with how much context we choose to send. And an ongoing cost proportional to how often documents change — which is where engineering work on incremental updates pays for itself, because done naively that bucket is roughly fifty times larger than done properly.

Then give them the lever: context size is the main dial on the recurring cost — and parent–child is the design that breaks the link between search granularity and context size
Answer it out loudeight of the thirteen, and the spine of each
Architect

How do you choose chunk size?

  • Weak answer: “512 tokens with 50 overlap.”
  • It is fitted to the query distribution, not chosen in the abstract
  • Fact lookup wants 100–250 with a parent fallback; procedural wants section-level
  • If a meaningful share is summarisation or aggregation, chunk size is the wrong lever
And the measurement: sweep size against recall@10 on 200 labelled questions. If recall is flat between 300 and 500, take the smaller one for cost.
Architect

Why not just retrieve larger chunks and skip the complexity?

  • Because embedding quality degrades with length — the vector is an average
  • A long chunk matches everything weakly and nothing strongly
  • You would trade a retrieval problem you cannot fix downstream for a context problem you can
Parent–child avoids the trade entirely, at the cost of one extra fetch of about ten milliseconds and some duplicate text storage.
Architect

What is the point of overlap, and how much?

  • It stops an answer straddling a boundary being lost in both chunks
  • 10–15% is typical for fixed-size chunking
  • It costs proportionally more vectors, index memory and embedding spend
  • And it does not solve context loss
With structural chunking I would often use zero, because sections do not split sentences. Paying for overlap is usually a sign of cutting in the wrong places.
Architect

The answer needs three separate sections of a 200-page document.

  • Chunking does not handle that, and I would not pretend otherwise
  • Chunking produces candidates; multi-hop needs query decomposition
  • Break into sub-questions, retrieve for each, synthesise — or an agentic loop
I would flag it as a known limitation with a measurement plan attached, rather than tuning chunk size and hoping.
Architect

Would you use semantic chunking?

  • I would evaluate it and expect to reject it for most enterprise corpora
  • It costs an embedding call per sentence at ingest
  • Structural boundaries usually capture the same topic shifts for free — the author already marked them with headings
I would reach for it only on genuinely unstructured narrative text, and only if a recall sweep showed a real gain over paragraph packing.
Architect

Our context window is 128k. Why not put the whole document in?

  • Sometimes you should — if the corpus is small, retrieval is unnecessary complexity
  • Cost: you pay per token on every query, and it scales with traffic
  • Latency: time to first token grows with context length
  • Accuracy: models attend less reliably to the middle of long contexts
So past a point more context actively lowers answer quality. Retrieval is a precision tool, not a workaround for a small window.
Architect

What is the single decision most expensive to change later?

  • Chunk identity and the metadata schema
  • Chunk size I can re-sweep and rebuild. The embedding model I can migrate blue-green
  • But positional chunk IDs mean every citation, feedback record and audit log points at something unstable
And metadata not captured at ingest — ACL tags, source coordinates — means re-crawling a source system that may no longer hold it.
Eng manager

Your team wants 1,000-token chunks because “more context is better”.

  • I would not argue from theory — I would make it measurable
  • Build a fifty-question gold set in a day, sweep both configurations
  • Put recall@10 and cost per query side by side
  • If they are right we ship their config and I learned something cheaply
The more important outcome as a manager is that this becomes the norm: configuration decisions settled by a cheap experiment rather than by whoever argues longest.
Fast answersthe nine that come up most

QDoes overlap help with tables?

No, and it can make things worse. A cut table produces two chunks that each contain part of the table and part of the neighbouring prose — now both are wrong. Tables need to be atomic units with the header repeated, not overlapped.

QShould the child chunk include the section heading?

Yes, and usually the whole section path. It costs one string concatenation, adds a handful of tokens, and gives the vector a topic anchor it otherwise lacks. The cheapest recall improvement in this document, and often left out.

QIf parents are never embedded, why store them in the vector database?

You should not. Parents belong in a plain key-value store, read by key and never searched. Putting them in the vector collection means paying index memory for records that are never matched against.

QWhat if one section is 8,000 tokens?

Then it is not a parent, it is a document. Cap parents at 1,000–1,500 tokens and use a sliding window around the matched child. Sending an 8,000-token parent reintroduces exactly the lost-in-the-middle problem parent–child was meant to avoid.

QDo I need a different chunk size per document type?

Frequently yes, and it is cheap because chunking is per-source anyway. What you must not do is let them share an index without a type tag — then you cannot weight or filter by source and cannot diagnose which corpus is failing.

QIs there a rule of thumb for k?

Retrieve deep and send shallow: 30–50 candidates into the reranker, 3–8 parents into the prompt. Depth in the index is cheap; depth in the prompt is expensive and paid on every query. Getting those two backwards is one of the most costly configuration mistakes.

QHow much does chunk size affect embedding cost?

Almost not at all, which surprises people. You embed the same total tokens either way — the corpus does not change size when you cut it differently. What changes is the number of vectors. Overlap is the exception.

QOur documents are mostly one or two pages. Does any of this apply?

Much less of it. If a document fits comfortably in a chunk, index it whole and skip parent–child entirely. These techniques earn their complexity when the answer occupies a small part of a large document.

QCan I just let the language model do the chunking?

You can — but price it before you commit. On the reference stack that is ten million generation calls at ingest, repeated every time you re-chunk. Structural boundaries are free and capture most of the same signal.

You retrieve what you embed. The chunk is the unit of retrieval, so chunking decides what the system is capable of finding at all — and everything downstream can only reorder what chunking made findable. The senior move is not picking a better number: it is noticing that matching a query and answering a question are two different jobs, and that we have been forcing one object to do both. Search on a small chunk, feed a large one to the model, and the tension does not get traded off — it gets dissolved, for about ten milliseconds.

whole document · 0.42
the right child · 0.81
Where this goes next

Questions to ask them

What do people actually ask? Is there a log of real queries?

Are summarisation or aggregation questions a meaningful share of traffic?

Are chunk IDs positional, or derived from stable content?

Were ACL tags and source coordinates captured at ingest, or is that a re-crawl?

Is there a gold set, and who owns it?

Do different source types share one index without a type tag?

← 00 · Start Read the full chapter → 02 · Parsing →