Learn ChromaDB - Data Modeling & Chunking Strategy
Episode 10 of 23

Learn ChromaDB - Data Modeling & Chunking Strategy

This episode covers designing metadata schemas for RAG: id conventions, timestamps, sources, tenants and namespaces, then chunking strategies that determine retrieval accuracy: chunk size, overlap, and choosing the right granularity.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

So far we have focused on how ChromaDB works. Episode 10 flips the focus to what you put indata modeling and chunking strategy. One truth is often overlooked: retrieval quality is not determined by the vector database, but by the quality of the data that goes into it. Even the best embedding function cannot save a bad chunk.

We will cover the recommended metadata schema for RAG, structured id conventions, then chunking strategies: size, overlap, and granularity. Treat this episode as your data architecture design before you build for production.

Designing a Metadata Schema

Metadata You Should Have for RAG

Metadata is the context that accompanies every document. For a healthy RAG application, at minimum store:

  • source: where the document came from (URL, file name, or pipeline).
  • timestamp: when the document was created or fetched.
  • title: a title for displaying friendlier results.
  • category or topic: for the filters in episode 7.
  • tenant or namespace: separators for data ownership.
PythonContoh metadata yang kaya
metadata = {
    "source": "https://blog.example.com/docker-intro",
    "timestamp": "2026-07-15T08:30:00Z",
    "title": "Pengenalan Docker untuk Pemula",
    "category": "devops",
    "tenant": "tutorial",
}

The metadata example above enables filters like where={"category": "devops"} while also displaying the title in retrieval results without opening the original document.

Avoiding Unused Metadata

The biggest temptation: storing every attribute of the source. Metadata that is never used in queries only wastes storage and slows serialization. A simple rule: store only what you will filter on, display in the UI, or use as extra context for the LLM.

Info

A metadata schema is not a decision you can change on a whim. Changing the schema after a lot of data means migrating the entire collection. Design it carefully up front — episode 11 covers the tools for migration if you are already past that point.

Structured ID Conventions

IDs as a Source of Information

IDs in ChromaDB do not have to be sequential numbers — they can be any string. Take advantage of this to create self-describing IDs, especially for deduplication when a pipeline is re-run:

PythonKonvensi id berbasis hash
import hashlib
 
def buat_id(source, chunk_index):
    hash_teks = hashlib.md5(source.encode()).hexdigest()[:8]
    return f"chunk-{hash_teks}-{chunk_index:04d}"
 
ids = [buat_id("docker-intro", i) for i in range(5)]

buat_id(source, chunk_index) produces deterministic IDs: re-running the same pipeline produces the same IDs, so the upsert from episode 4 updates instead of duplicating. This is an important pattern for repeated ingestion.

The Relationship Between IDs and Source

Add a prefix that marks the data type or tenant:

PythonPrefix tenant pada id
id_dok = f"{tenant}:{source}:{chunk_index}"

id_dok = f"{tenant}:{source}:{chunk_index}" creates IDs like tutorial:docker-intro:0003. When troubleshooting or auditing, an ID immediately tells you where a document came from without opening its contents.

Chunking Strategy: The Foundation of RAG

Why Documents Must Be Split

Embedding models have a context length limit — text that is too long embedded as a single vector loses meaning detail. Additionally, retrieval works best when each vector represents one focused idea. That is why long documents must be split into smaller chunks.

PythonChunking sederhana per paragraf
def chunk_paragraf(teks):
    return [p.strip() for p in teks.split("\n\n") if p.strip()]
 
chunks = chunk_paragraf(dokumen)

The function chunk_paragraf(teks) splits a document by paragraph — the simplest approach, already far better than storing the whole document.

Choosing the Right Chunk Size

There is no magic number, but a commonly used guideline: 250-500 tokens per chunk for general content, and smaller (100-200 tokens) for dense technical content. Too large a size makes one vector mix many topics; too small cuts off context.

The main consideration is your embedding model. Check the model's token limit — chunks should stay below that limit with room for overlap.

Overlap and Granularity

Overlap: Bridging Cut-off Context

When a document is split, related sentences can end up in different chunks. Overlap — including some of the last sentences from the previous chunk — preserves continuity:

PythonChunking dengan overlap
def chunk_overlap(teks, ukuran=300, overlap=50):
    tokens = teks.split()
    hasil = []
    for i in range(0, len(tokens), ukuran - overlap):
        potongan = tokens[i : i + ukuran]
        hasil.append(" ".join(potongan))
        if i + ukuran >= len(tokens):
            break
    return hasil

chunk_overlap(teks, ukuran=300, overlap=50) advances 250 words per iteration with 50 words of overlap. Common overlap values: 10-20 percent of the chunk size.

Granularity: How Small Should It Ideally Be

Granularity depends on the type of questions you will answer:

  • FAQ and knowledge bases: chunk per Q&A or per small topic.
  • Technical documents: chunk per section or subsection.
  • News: chunk per main paragraph.
  • Legal manuals: chunk per article or clause.

The best way to choose: do not guess. Take a sample of 50-100 real questions, run retrieval for several granularities, and measure which is most accurate. This low-cost experiment pays off far more than picking a number from someone else's tutorial.

A Complete Ingestion Pattern

Summarizing this episode into one healthy ingestion pattern:

PythonPipeline ingestion dengan metadata
for idx, chunk in enumerate(chunk_overlap(dokumen)):
    collection.upsert(
        ids=[f"{tenant}:{source}:{idx:04d}"],
        documents=[chunk],
        metadatas=[{
            "source": source,
            "timestamp": timestamp,
            "title": judul,
            "tenant": tenant,
            "chunk_index": idx,
        }],
    )

The pattern above — deterministic IDs, complete metadata, and collection.upsert(...) — produces a clean collection that deduplicates automatically when re-run and is easy to filter. This is the template we will use for the rest of the series.

Closing

Episode 10 closes out the data phase: metadata schemas that support filtering, self-describing and deterministic id conventions, and a chunking strategy with size, overlap, and granularity tailored to your question types. Your RAG retrieval quality now depends on the quality of the data you design — and you know how to design it.

Key takeaways:

  • Store the metadata you use: source, timestamp, title, category, tenant.
  • Deterministic IDs make upsert deduplicate.
  • 250-500 token chunks with 10-20 percent overlap are a healthy starting point.
  • Granularity matches your question types, not assumptions.
  • The metadata above determines the filter speed below.
  • Good ingestion can be re-run without side effects.

In the next episode, episode 11, we will discuss persistence, import and export — how PersistentClient works and its storage location, backup and restore strategies, snapshots, and import-export data using parquet and collection dump-load for migrating between environments. Your data is starting to be valuable; time to protect it.