Episode ini membahas perancangan skema metadata untuk RAG: konvensi id, timestamp, source, tenant dan namespace, lalu strategi chunking yang menentukan akurasi retrieval: ukuran chunk, overlap, dan pemilihan granularity yang tepat.

Sejauh ini kita fokus pada bagaimana ChromaDB bekerja. Episode 10 membalik fokus ke apa yang kalian masukkan — data modeling dan chunking strategy. Satu kebenaran yang sering diabaikan: kualitas retrieval tidak ditentukan oleh vector database, melainkan oleh kualitas data yang masuk ke dalamnya. Embedding function terbaik pun tidak bisa menyelamatkan chunk yang buruk.
Kita akan membahas skema metadata yang disarankan untuk RAG, konvensi id yang terstruktur, lalu strategi chunking: ukuran, overlap, dan granularity. Anggap episode ini sebagai desain arsitektur data sebelum kalian membangun production.
Metadata adalah konteks yang menemani setiap dokumen. Untuk aplikasi RAG yang sehat, setidaknya simpan:
metadata = {
"source": "https://blog.example.com/docker-intro",
"timestamp": "2026-07-15T08:30:00Z",
"title": "Pengenalan Docker untuk Pemula",
"category": "devops",
"tenant": "tutorial",
}Contoh metadata di atas memungkinkan filter seperti where={"category": "devops"} sekaligus menampilkan judul di hasil retrieval tanpa membuka dokumen asli.
Godaan terbesar: menyimpan semua atribut sumber. Metadata yang tidak pernah dipakai query hanya memboroskan storage dan memperlambat serialisasi. Aturan sederhana: simpan hanya yang akan kalian filter, tampilkan di UI, atau jadikan konteks tambahan untuk LLM.
Info
Skema metadata bukan keputusan yang bisa diubah seenaknya. Ubah skema setelah data banyak berarti migrasi seluruh collection. Rancang dengan hati-hati di awal — episode 11 akan membahas alat untuk migrasi jika terlanjur.
ID di ChromaDB tidak harus numerik sekuensial — ia bebas string. Manfaatkan ini untuk membuat ID yang self-describing, terutama untuk deduplikasi saat pipeline dijalankan ulang:
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) menghasilkan id deterministik: pipeline yang sama dijalankan ulang menghasilkan id yang sama, sehingga upsert dari episode 4 otomatis memperbarui alih-alih menduplikasi. Ini pola penting untuk ingestion berulang.
Tambahkan prefix yang menandai tipe data atau tenant:
id_dok = f"{tenant}:{source}:{chunk_index}"id_dok = f"{tenant}:{source}:{chunk_index}" membuat ID seperti tutorial:docker-intro:0003. Saat troubleshooting atau audit, ID langsung menceritakan asal usul dokumen tanpa perlu membuka isinya.
Model embedding punya batas panjang konteks — teks yang terlalu panjang di-embed sebagai satu vektor akan kehilangan detail makna. Selain itu, retrieval bekerja paling baik jika tiap vektor mewakili satu ide yang fokus. Karena itu dokumen panjang harus dipecah menjadi chunk yang lebih kecil.
def chunk_paragraf(teks):
return [p.strip() for p in teks.split("\n\n") if p.strip()]
chunks = chunk_paragraf(dokumen)Fungsi chunk_paragraf(teks) memecah dokumen per paragraf — pendekatan paling sederhana yang sudah jauh lebih baik daripada menyimpan dokumen utuh.
Tidak ada angka ajaib, tapi pedoman yang umum dipakai: 250-500 token per chunk untuk konten umum, dan lebih kecil (100-200 token) untuk konten teknis yang padat. Ukuran terlalu besar membuat satu vektor mencampur banyak topik; terlalu kecil membuat konteks terpotong.
Pertimbangan utama: embedding model kalian. Periksa batas token model — chunk harus berada di bawah batas tersebut dengan ruang untuk overlap.
Saat dokumen dipecah, kalimat yang berkaitan bisa terpisah ke chunk berbeda. Overlap — menyertakan beberapa kalimat terakhir dari chunk sebelumnya — menjaga kontinuitas:
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 hasilchunk_overlap(teks, ukuran=300, overlap=50) maju 250 kata per iterasi dengan 50 kata overlap. Nilai overlap umum: 10-20 persen dari ukuran chunk.
Granularity bergantung pada jenis pertanyaan yang akan dijawab:
Cara terbaik memilih: jangan menebak. Ambil sampel 50-100 pertanyaan nyata, jalankan retrieval untuk beberapa granularity, dan ukur akurasi mana yang terbaik. Eksperimen berbiaya rendah ini membayar jauh lebih besar daripada memilih angka dari tutorial orang lain.
Merangkum episode ini menjadi satu pola ingestion yang sehat:
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,
}],
)Pola di atas — id deterministik, metadata lengkap, dan collection.upsert(...) — menghasilkan collection yang bersih, deduplikasi otomatis saat dijalankan ulang, dan mudah difilter. Inilah template yang akan kita gunakan untuk sisa series.
Episode 10 menutup fase data: skema metadata yang menunjang filter, konvensi id yang self-describing dan deterministik, serta strategi chunking dengan ukuran, overlap, dan granularity yang disesuaikan dengan jenis pertanyaan. Kualitas retrieval RAG kalian sekarang bergantung pada kualitas data yang kalian rancang — dan kalian sudah tahu cara merancangnya.
Inti yang harus dibawa pulang:
upsert tidak menduplikasi.Di episode 11 selanjutnya kita akan membahas persistence, import dan export — cara kerja PersistentClient dan lokasi storage, strategi backup dan restore, snapshot, hingga import-export data memakai parquet dan dump-load collection untuk migrasi antar environment. Data kalian mulai bernilai; saatnya menjaganya.