DEV Community

Arthur Doss
Arthur Doss

Posted on

Building a RAG Chunking Benchmark With Realistic Queries

Retrieval-augmented generation (RAG) systems depend on retrieving the right information before generating an answer. However, even a capable embedding model and a well-configured vector database can produce poor results when documents are divided into unsuitable chunks. Small chunks may lose context, while large chunks can introduce irrelevant information and consume unnecessary context-window space.

The solution is to benchmark chunking strategies against realistic user queries and source-level relevance labels. By comparing fixed-size, overlapping, and structure-aware chunking under consistent retrieval settings, engineers can measure Recall@k, Mean Reciprocal Rank (MRR), nDCG, context efficiency, latency, and indexing costs.

This replaces guesswork with reproducible evidence and helps teams select a chunking strategy suited to their actual RAG workload.

Why RAG Chunking Needs a Dedicated Benchmark?

Chunking divides source documents into smaller passages that can be embedded and retrieved independently. It influences which information each embedding represents, whether related facts remain together, and how much context reaches the language model.

Consider an employee asking, “What happens to unused vacation days when an employee leaves the company?”

The answer may depend on separate sections describing employment termination, vacation balances, and applicable policies. If these sections are split poorly, the retriever might return only part of the required information.

Smaller chunks can improve retrieval precision but separate related facts. Larger chunks preserve more context but may contain unrelated content. Overlapping chunks can protect information near boundaries, although they can also create duplicate results.

A useful benchmark must therefore answer three questions:

  • Retrieval quality: Does the system retrieve the evidence required to answer a query?
  • Context efficiency: How much relevant information is retrieved compared with irrelevant or duplicated content?
  • Operational cost: How do chunking choices affect indexing time, storage, embedding usage, and query latency?

The BEIR information retrieval benchmark demonstrates why retrieval systems should be evaluated across diverse datasets rather than a single narrow test. The same principle applies to RAG chunking: performance on a handful of straightforward questions does not establish that a strategy will work for real users.

Design the RAG Chunking Benchmark Architecture

A reproducible benchmark separates document processing, chunk generation, retrieval, and evaluation. This makes it possible to change one component while keeping the rest of the experiment consistent.

A practical architecture includes:

  • Source corpus: Documents, tables, metadata, and stable source identifiers.
  • Query dataset: Representative questions and independently labeled supporting evidence.
  • Chunking pipeline: Configurable splitting strategies and parameters.
  • Retrieval pipeline: A consistent embedding model, index, ranking method, and access filters.
  • Evaluation runner: Metrics, timing measurements, cost tracking, and result exports.

The benchmark should preserve document identifiers and character or token offsets for every generated chunk. These offsets allow retrieved passages to be mapped back to the original source, making relevance evaluation more reliable.

Keep the query dataset independent of the chunking implementation. If questions are generated specifically to match one strategy's chunk boundaries, the benchmark can favor that strategy without demonstrating genuine retrieval improvements.

Build a Realistic Query Dataset

Realistic queries are central to a useful benchmark. A test set containing only direct factual questions may favor chunking methods that perform well on short, isolated passages while overlooking failures involving context or relationships between documents.

Include several query categories.

Direct factual queries

These ask for specific facts, definitions, dates, or values.

Example: “How many days do employees have to submit expense claims?”

Semantic queries

These express an idea using terminology different from the source document.

Example: “When must staff file reimbursement paperwork?”

Multi-part queries

These require multiple facts to produce a complete answer.

Example: “Who approves a reimbursement request, and when is payment issued?”

Cross-section queries

These require information from multiple sections or documents.

Example: “Can a departing employee receive payment for unused leave, and how is the balance calculated?”

Structured-data queries

These target tables, product specifications, configuration files, or technical documentation.

Example: “Which deployment tier supports private networking?”

Ambiguous or unanswerable queries

These test whether the retrieval pipeline can recognize insufficient evidence rather than returning loosely related passages.

Where permitted, anonymized search logs and support tickets can provide realistic query patterns. Synthetic queries can expand coverage, but they should be reviewed and combined with real examples because generated questions may resemble the source text too closely.

Each query should have a stable identifier, expected answer or answer requirements, and one or more supporting source passages. These labels should be reviewed independently of the chunking configurations being tested.

Which Chunking Strategies Should You Compare?

Start with a small set of meaningful baselines before introducing more sophisticated methods.

Fixed-size chunking

Fixed-size chunking divides documents into segments of a configured length. It is straightforward to implement and makes it easy to control the number of tokens supplied to an embedding model.

Its main weakness is that boundaries may split sentences, headings, code blocks, or related facts. Character-based limits are also not equivalent to token limits, so production implementations should use the tokenizer appropriate to their model constraints.

Overlapping chunking

Overlapping chunking repeats a portion of one chunk in the next. For example, a benchmark might test 256-token chunks with 32 tokens of overlap.

Overlap can preserve context around boundaries, but it may increase vector counts, indexing costs, and duplicate content in retrieved results. Test several reasonable overlap values rather than assuming that more overlap improves retrieval.

Structure-aware chunking

Structure-aware chunking respects document organization, including Markdown headings, HTML sections, paragraphs, and code blocks.

This approach can be useful for knowledge bases and technical documentation because headings and section boundaries often provide important context. However, long sections still need a fallback splitting strategy.

LangChain provides recursive, token-oriented, and document-format-aware splitting options. These are useful implementation tools, but their availability does not establish that a particular strategy performs best for a given corpus.

Parent-child retrieval

Parent-child retrieval indexes smaller passages but can return a larger parent section when a child passage matches the query.

This separates the precision of a small retrieval unit from the broader context supplied to the language model. The trade-off is additional retrieval logic and potentially larger context payloads.

LlamaIndex documents hierarchical node parsing and relationships between smaller nodes and their parent sections. These techniques are worth evaluating when answers depend on context extending beyond an individual passage.

Implement a Reproducible Python Benchmark

A small benchmark can establish the evaluation workflow before it is connected to a production vector database.

The following example uses character-based chunking and TF-IDF retrieval to demonstrate the mechanics without external API credentials. It is an illustrative baseline, not a substitute for evaluating the dense or hybrid retrieval stack intended for production.

Install the dependency:

python -m pip install scikit-learn
Enter fullscreen mode Exit fullscreen mode

Step 1: Define the source documents and queries

DOCUMENTS = {
    "leave_policy": (
        "Employee Leave Policy. Employees accrue annual leave "
        "according to their employment terms. When employment "
        "ends, unused vacation days are handled according to "
        "the employee contract and applicable local law. "
        "Contact HR for the final leave balance."
    ),
    "expense_policy": (
        "Expense Reimbursement Policy. Employees must submit "
        "claims with supporting receipts within thirty days "
        "of the expense. Approved claims are reimbursed "
        "through the regular payroll cycle."
    ),
}

QUERIES = [
    {
        "id": "q1",
        "text": "How are unused vacation days handled after termination?",
        "relevant_doc": "leave_policy",
    },
    {
        "id": "q2",
        "text": "When must staff file reimbursement claims?",
        "relevant_doc": "expense_policy",
    },
]
Enter fullscreen mode Exit fullscreen mode

This dataset is intentionally small. Its purpose is to illustrate the benchmark structure, not to establish a reliable ranking of chunking methods.

In a real evaluation, annotate the minimum source spans needed to support each answer. Document-level labels are too coarse for determining whether a retrieved passage contains the actual evidence.

Step 2: Implement chunking with source offsets

from dataclasses import dataclass


@dataclass
class Chunk:
    chunk_id: str
    doc_id: str
    start: int
    end: int
    text: str


def chunk_document(doc_id, text, size=220, overlap=0):
    if size <= 0:
        raise ValueError("size must be positive")
    if overlap < 0 or overlap >= size:
        raise ValueError("overlap must be >= 0 and < size")

    chunks = []
    step = size - overlap

    for start in range(0, len(text), step):
        end = min(start + size, len(text))

        chunks.append(
            Chunk(
                chunk_id=f"{doc_id}:{start}:{end}",
                doc_id=doc_id,
                start=start,
                end=end,
                text=text[start:end],
            )
        )

        if end == len(text):
            break

    return chunks


def build_chunks(size, overlap):
    return [
        chunk
        for doc_id, text in DOCUMENTS.items()
        for chunk in chunk_document(
            doc_id, text, size=size, overlap=overlap
        )
    ]

Enter fullscreen mode Exit fullscreen mode

The implementation records each chunk's source document and character offsets. It also validates the size and overlap parameters.

For production use, replace character windows with token-aware or structure-aware splitting. Preserve document metadata, heading context, source permissions, and stable identifiers throughout ingestion.

Step 3: Add retrieval and ranking metrics

Use TF-IDF to establish a simple lexical retrieval baseline. Rank chunks by cosine similarity and calculate MRR and nDCG.

from math import log2

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity


def evaluate(chunks, queries, k=3):
    if not chunks or not queries:
        raise ValueError("Chunks and queries must not be empty")

    vectorizer = TfidfVectorizer()
    matrix = vectorizer.fit_transform(
        [chunk.text for chunk in chunks]
    )

    reciprocal_ranks = []
    ndcg_scores = []

    for query in queries:
        query_vector = vectorizer.transform([query["text"]])
        scores = cosine_similarity(query_vector, matrix)[0]

        order = sorted(
            range(len(chunks)),
            key=lambda i: scores[i],
            reverse=True,
        )
        ranked = [chunks[i] for i in order[:k]]

        relevant = [
            chunk.doc_id == query["relevant_doc"]
            for chunk in ranked
        ]

        first_rank = next(
            (rank for rank, hit in enumerate(relevant, 1) if hit),
            None,
        )
        reciprocal_ranks.append(
            1 / first_rank if first_rank else 0.0
        )

        dcg = sum(
            int(hit) / log2(rank + 1)
            for rank, hit in enumerate(relevant, 1)
        )

        total_relevant = sum(
            chunk.doc_id == query["relevant_doc"]
            for chunk in chunks
        )
        ideal_count = min(k, total_relevant)
        idcg = sum(
            1 / log2(rank + 1)
            for rank in range(1, ideal_count + 1)
        )

        ndcg_scores.append(dcg / idcg if idcg else 0.0)

    return {
        "mrr": sum(reciprocal_ranks) / len(reciprocal_ranks),
        "ndcg_at_k": sum(ndcg_scores) / len(ndcg_scores),
        "chunk_count": len(chunks),
    }

Enter fullscreen mode Exit fullscreen mode

This simplified example uses document-level relevance: every chunk from the expected document is considered relevant. It demonstrates ranking evaluation but does not establish whether a chunk contains the exact evidence needed to answer a question.

That distinction matters because a chunk can come from the correct document without containing the relevant fact.

Step 4: Run comparable configurations

configurations = [
    ("fixed_220", 220, 0),
    ("overlap_220_40", 220, 40),
    ("fixed_120", 120, 0),
]

for name, size, overlap in configurations:
    chunks = build_chunks(size, overlap)
    metrics = evaluate(chunks, QUERIES, k=3)
    print(name, metrics)
Enter fullscreen mode Exit fullscreen mode

The output should be treated as a sanity check for the evaluation pipeline. With such a small corpus and coarse labels, differences between configurations are not meaningful evidence of general performance.

For a useful experiment, expand the corpus and query set, introduce reviewed source-span labels, and run the same configurations against the intended production retriever.

Make the Benchmark Representative and Reproducible

A benchmark can produce convincing numbers while measuring the wrong thing. Several engineering controls help prevent that.

Separate tuning from final evaluation

Use development queries to tune chunk size and overlap, validation queries to compare promising configurations, and a held-out test set for final reporting.

Where possible, separate document families across the splits. Near-duplicate documents appearing in both training and test data can make retrieval performance look better than it is.

Test the actual retrieval pipeline

After validating the lexical baseline, evaluate the embedding model and vector database used in production.

Run dense retrieval, lexical retrieval, hybrid search, and reranking as distinct experiments. Keep the corpus and query set fixed, and record model identifiers, similarity metrics, index settings, retrieval depth, and filtering rules.

Measure performance under controlled conditions

Run repeated measurements with documented hardware, concurrency, caching, and warm-up procedures. Compare median and tail latency rather than relying on a single query execution.

Record indexing costs separately from query costs. A strategy that improves retrieval quality but substantially increases vector storage or embedding consumption may not be appropriate for every workload.

Version the complete experiment

Store the corpus version, query dataset, relevance labels, chunking parameters, parser version, tokenizer, embedding model, retrieval configuration, dependency versions, and evaluation results.

This makes it possible to identify why performance changed when a document parser, embedding model, or chunking strategy is updated.

Common RAG Chunking Benchmark Mistakes

Avoid these frequent problems:

  • Using only synthetic questions that closely mirror source wording.
  • Comparing chunking strategies with different embedding models or retrieval settings.
  • Treating entire documents as relevant when only specific passages support the answer.
  • Counting duplicate overlapping passages as additional evidence.
  • Optimizing recall without measuring precision, context size, and latency.
  • Reporting results from a tiny dataset as generalizable findings.
  • Claiming performance improvements without executed experiments and reproducible configurations.

Conclusion

The best RAG chunking strategy depends on the source documents, query distribution, retrieval model, and operational constraints. Start with fixed-size and overlapping baselines, evaluate them using realistic queries and source-level evidence labels, and test structure-aware or hierarchical approaches when justified. Measure quality, context efficiency, and cost together, then preserve the benchmark as a regression test for future changes.

FAQs

What is the best chunk size for RAG?

Evaluate several token-based sizes against representative queries and choose based on evidence coverage, ranking quality, context efficiency, and latency.

How much overlap should RAG chunks have?

Overlap can preserve context around boundaries, but excessive overlap increases indexing costs and duplicated retrieval results. Compare zero overlap with a few justified alternatives.

Is semantic chunking better than fixed-size chunking?

Semantic chunking can help when document meaning changes substantially between sections. Its effectiveness depends on the corpus and retrieval pipeline, so benchmark it against simpler baselines before adopting it.

Can Ragas help evaluate chunking strategies?

Yes. Ragas provides retrieval and generation evaluation metrics that complement source-level relevance measurements. Validate its assumptions and any LLM-based judgments against your evaluation requirements.

When should a RAG chunking benchmark be rerun?

Rerun it when chunking, parsing, embeddings, retrieval settings, or source data change materially. Maintain a stable regression set and periodically incorporate new, reviewed production queries.

Top comments (0)