MODULE 3  ยท  Embeddings & Vector Search

Chunking Strategies: Why How You Split Documents Changes Everything

You've got a 50-page PDF. You can't embed it as one vector. How you split it changes everything about retrieval quality.

๐Ÿ“… Mar 2026
โฑ 10 min read
๐ŸŽฏ Episode 19 of 98
ChunkingRAGDocument ProcessingEmbeddings
In this episode

You've got a 50-page PDF about Kubernetes. You want to search it with embeddings. But you can't embed the whole document as one vector โ€” the embedding model has a token limit (usually 512-8192 tokens), and even if it didn't, cramming 50 pages into one vector washes out all the specific details.

So you chunk it. Split the document into smaller pieces. Embed each chunk. Store them in your vector database.

Simple, right? Except: chunk too big and your retrieval is vague. Chunk too small and you lose context. Chunk at the wrong boundaries and you split a sentence in half. The chunking strategy you pick is the single biggest lever for RAG quality โ€” more impactful than your embedding model, your vector database, or your LLM.

Why Chunk Size Matters So Much

Imagine searching for "how to set resource limits for Kubernetes pods."

Chunk too large (2000 tokens): The chunk contains resource limits, plus networking, plus storage volumes, plus health checks. The embedding captures the general topic (Kubernetes) but not the specific subtopic (resource limits). Your search returns vaguely relevant chunks.

Chunk too small (50 tokens): The chunk contains "Set the memory limit using resources.limits.memory: 512Mi in the pod spec." Perfect specificity โ€” but zero context. The LLM doesn't know which pod, which deployment, or why.

Chunk just right (200-500 tokens): The chunk covers resource limits comprehensively โ€” CPU and memory limits, requests vs limits, what happens when limits are exceeded โ€” with enough context to be self-explanatory.

This is the chunking Goldilocks problem. And there's no universal "right" answer โ€” it depends on your content and your queries.

โšก

Strategy 1: Fixed-Size Chunking

The simplest approach. Split text every N characters or N tokens.

python

snippet
code
def fixed_size_chunks(text, chunk_size=500, overlap=50):
chunks = []
    for i in range(0, len(text), chunk_size - overlap):
        chunk = text[i:i + chunk_size]
        chunks.append(chunk)
    return chunks
text = "Your very long document text here..."
chunks = fixed_size_chunks(text, chunk_size=500, overlap=50)

Pros:

Dead simple to implement

Predictable chunk sizes

Works for any text format

Cons:

Splits mid-sentence, mid-paragraph, mid-thought

No respect for document structure

A chunk might start with "...continued from previous section" โ€” meaningless in isolation

When to use: Quick prototyping, unstructured text with no clear section boundaries.

๐Ÿ”ง

Strategy 2: Recursive Character Splitting

The LangChain default. Try to split on natural boundaries, falling back to smaller separators:

python

LangChain's RecursiveCharacterTextSplitter logic:

snippet
code
separators = [
"\n\n",    # Double newline (paragraph breaks) โ€” try first
    "\n",      # Single newline (line breaks)
    ". ",      # Sentence endings
    " ",       # Word boundaries
    ""         # Individual characters (last resort)

]

Algorithm:

1. Try splitting on "\n\n" (paragraphs)

2. If chunks are still too big, split on "\n"

3. If still too big, split on ". "

4. Keep going until chunks fit under the limit

python

from langchain.text_splitter import RecursiveCharacterTextSplitter

snippet
code
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", ". ", " ", ""]

)

snippet
code
chunks = splitter.split_text(document_text)

Pros:

Respects natural text boundaries

Much better than fixed-size for most content

Highly configurable separators

Cons:

Still purely character-based โ€” doesn't understand meaning

Can still split related content across chunks

When to use: The safe default for most RAG applications. Start here.

๐Ÿ“Š

Strategy 3: Semantic Chunking

Split based on meaning changes, not character counts. Use embeddings to detect when the topic shifts.

python

from langchain_experimental.text_splitter import SemanticChunker

from langchain_openai import OpenAIEmbeddings

snippet
code
splitter = SemanticChunker(
OpenAIEmbeddings(),
    breakpoint_threshold_type="percentile",
    breakpoint_threshold_amount=95

)

snippet
code
chunks = splitter.split_text(document_text)

How it works:

Sentence 1: "Kubernetes uses pods as the smallest deployable unit."

Sentence 2: "Each pod can contain one or more containers."

Sentence 3: "Pods share networking and storage."

โ† Similarity between sentences 3 and 4: 0.42 (LOW โ€” topic change!) โ†’

Sentence 4: "To deploy a service, you need to create a Service resource."

Sentence 5: "Services provide stable endpoints for pods."

Result:

Chunk 1: Sentences 1-3 (about pods)

Chunk 2: Sentences 4-5 (about services)

Pros:

Chunks align with actual topic boundaries

Variable-length chunks that match content structure

Semantically coherent chunks = better retrieval

Cons:

Requires running embeddings on every sentence (expensive during ingestion)

Slower ingestion pipeline

Can produce very uneven chunk sizes

When to use: High-quality RAG where retrieval precision matters more than ingestion speed.

Strategy 4: Document Structure-Based

Use the document's own structure โ€” headers, sections, code blocks โ€” as chunk boundaries.

python

For Markdown

from langchain.text_splitter import MarkdownHeaderTextSplitter

snippet
code
headers_to_split = [
("#", "h1"),
    ("##", "h2"),
    ("###", "h3"),

]

snippet
code
splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split)
chunks = splitter.split_text(markdown_text)

Each chunk includes its header hierarchy as metadata:

{ "h1": "Kubernetes Guide", "h2": "Pod Configuration", "content": "..." }

For HTML:

python

from langchain.text_splitter import HTMLHeaderTextSplitter

snippet
code
splitter = HTMLHeaderTextSplitter(
headers_to_split_on=[("h1", "h1"), ("h2", "h2"), ("h3", "h3")]

)

snippet
code
chunks = splitter.split_text(html_content)

Pros:

Leverages human-authored structure

Headers become metadata (great for filtering)

Most natural chunking for documentation

Cons:

Only works for well-structured documents

Sections can still be too large (need secondary splitting)

Doesn't work for unstructured text

When to use: Documentation, technical manuals, knowledge bases โ€” anything with headers.

๐Ÿ’ก

The Overlap Problem

Without overlap, context is lost at chunk boundaries:

Chunk 1: "...Kubernetes resource limits define the maximum CPU and memory

example
code
a container can use. The default namespace has"

Chunk 2: "no resource quotas applied. You should always set limits

example
code
in production to prevent noisy neighbor problems..."

The critical sentence "The default namespace has no resource quotas applied" is split across two chunks. Neither chunk contains the full thought.

Overlap fixes this:

Chunk 1: "...resource limits define the maximum CPU and memory

example
code
a container can use. The default namespace has
          no resource quotas applied."
                                    โ†‘ overlap zone

Chunk 2: "The default namespace has no resource quotas applied.

example
code
You should always set limits in production to prevent
          noisy neighbor problems..."
          โ†‘ overlap zone starts here

How Much Overlap?

Overlap Effect

0%Maximum information density, risk of split context
10-15%Good balance for most use cases
20-30%Safe for important documents, more storage
50%+Overkill, too much redundancy

Rule of thumb: 10-15% of chunk size. For a 500-token chunk, use 50-75 tokens of overlap.

๐Ÿ”ฌ

Chunk Size: The Numbers

Based on common practice and benchmarks:

Chunk Size Best For Trade-off

100-200 tokensFAQ, Q&A pairsVery specific but lacks context
200-500 tokensGeneral RAGBest balance for most use cases
500-1000 tokensLong-form content, articlesMore context but less precise retrieval
1000+ tokensSummarization, analysisRich context but vague embeddings

The empirical winner for most RAG: 256-512 tokens with 10-15% overlap. But always test with YOUR data and YOUR queries.

python

Quick experiment to find optimal chunk size

snippet
code
chunk_sizes = [128, 256, 512, 1024]

for size in chunk_sizes:

example
code
chunks = split_document(doc, chunk_size=size, overlap=int(size * 0.1))
    results = search(query, chunks)
    relevance = evaluate_results(results, ground_truth)
    print(f"Size: {size:4d}  Chunks: {len(chunks):4d}  Relevance: {relevance:.3f}")

๐Ÿ›ก๏ธ

Advanced: Multi-Level Chunking

The best RAG systems don't use just one chunk size. They use parent-child chunking:

Parent chunk (1000 tokens):

"This section covers Kubernetes resource management.

Resource limits define the maximum CPU and memory...

Resource requests define the minimum guaranteed...

LimitRange objects set defaults for a namespace..."

Child chunks (200 tokens each):

Child 1: "Resource limits define the maximum CPU and memory..."

Child 2: "Resource requests define the minimum guaranteed..."

Child 3: "LimitRange objects set defaults for a namespace..."

Search against child chunks (specific), but return parent chunks (contextual). Best of both worlds. We'll cover this in detail in Episode 25: Retrieval Strategies.

๐Ÿ“ฆ

Practical Takeaways

Chunking is the #1 lever for RAG quality โ€” spend more time here than on model selection

Start with RecursiveCharacterTextSplitter โ€” it's the safe default (256-512 tokens, 10% overlap)

Use document structure when available โ€” Markdown/HTML headers make natural chunk boundaries

Always use overlap โ€” 10-15% prevents losing context at boundaries

Test chunk sizes with your actual queries โ€” there's no universal optimum

Consider semantic chunking for high-value data โ€” more expensive but better retrieval

๐Ÿš€

What's Next?

Episode 20: Hybrid Search โ€” Vector search finds semantically similar results, but sometimes you need exact keyword matches. "Error code KB-4092" won't work with cosine similarity. Hybrid search combines the best of both worlds.

โ† Previous

Ep 18: Vector Databases

Next โ†’

Ep 20: Hybrid Search

Next: Episode 20 โ€” Hybrid Search

User searches 'error KB-4092'. Vector search returns semantically relevant results. None mention the actual error code.

This is part of a 98-episode series covering AI engineering from tokens to production deployment.

โ† Previous Ep 18: Vector Databases: Where Embeddings Live and How They Woโ€ฆ