Episode ini membahas scaling dan performance ChromaDB: melayani jutaan vektor dengan server Rust, batch operations untuk ingestion cepat, tuning index, optimasi ef search, pra-filter metadata, dan sizing resource CPU serta RAM yang tepat.

Aplikasi kalian sudah berjalan, data bertambah, dan tiba saatnya pertanyaan yang menakutkan: bisakah ini melayani jutaan vektor? Episode 17 menjawabnya dengan membahas scaling dan performance: bagaimana server Rust menangani volume besar, cara mengingest data secara batch, tuning index, optimasi ef search, pra-filter metadata, dan sizing resource yang benar.
Performance di ChromaDB bukan misteri — ia terdiri dari keputusan-keputusan spesifik yang bisa kalian kendalikan. Mari kita bongkar satu per satu.
Server Rust adalah fondasi scaling ChromaDB modern. Dibanding server Python FastAPI, Rust memberikan performa yang lebih deterministik dan penggunaan memori yang lebih efisien — dua hal yang menentukan saat data tumbuh menjadi jutaan vektor.
Server Rust memuat seluruh index HNSW ke memori untuk query cepat. Inilah kenapa sizing RAM adalah keputusan paling penting di bagian ini: index yang muat di RAM jauh lebih cepat daripada yang harus diakses dari disk.
Info
HNSW adalah index in-memory. Setiap penambahan dan query berjalan di RAM. Rule of thumb: sediakan cukup RAM untuk index plus ruang untuk build. Episode ini dan episode 9 saling melengkapi untuk menemukan angka yang tepat.
Kesalahan performa paling umum: memanggil add dalam loop untuk setiap dokumen. Setiap panggilan punya overhead protokol dan reindex parsial. Solusinya: batch.
batch_ids = []
batch_docs = []
batch_meta = []
for dok in dokumen_sumber:
batch_ids.append(buat_id(dok))
batch_docs.append(dok)
batch_meta.append({"source": "ingestion"})
collection.upsert(
ids=batch_ids,
documents=batch_docs,
metadatas=batch_meta,
)Contoh di atas mengumpulkan data lalu memanggil collection.upsert(...) sekali untuk seluruh batch. Perbandingan: 10 ribu panggilan kecil versus 1 panggilan besar bisa 10-100 kali lebih cepat untuk yang terakhir.
Ukuran batch ideal tergantung dimensi embedding dan RAM. Titik awal yang umum: 1.000-10.000 item per panggilan. Jangan memaksakan batch raksasa dalam satu memori — bagi menjadi beberapa panggilan 10 ribu jika perlu:
for i in range(0, len(dokumen_sumber), 5000):
batch = dokumen_sumber[i : i + 5000]
collection.upsert(
ids=[buat_id(d) for d in batch],
documents=batch,
metadatas=[{"source": "ingestion"} for _ in batch],
)Loop di atas memanggil collection.upsert(...) per 5000 dokumen. Ini menyeimbangkan throughput dengan penggunaan memori.
Untuk dataset besar, mulai dari pengaturan yang terbukti:
collection = client.create_collection(
name="skala-besar",
metadata={
"hnsw:space": "cosine",
"hnsw:ef_construction": 200,
"hnsw:M": 16,
},
)metadata={"hnsw:ef_construction": 200, "hnsw:M": 16} adalah titik awal sehat: build yang cukup akurat tanpa memboroskan memori berlebihan. Setelah data besar masuk, sesuaikan ef_search per query untuk menyeimbangkan recall dan latensi.
Setiap penambahan besar membuat index berubah. Untuk ingestion massal, pertimbangkan: masukkan dulu semua data dengan ef_construction standar, lalu — jika akurasi dirasa kurang — bangun ulang collection dengan nilai yang lebih tinggi. Menulis ulang (reindex) lebih murah daripada membangun ulang dari nol.
ef_search adalah knob paling berpengaruh pada latensi query. Nilai rendah mempercepat query tapi menurunkan recall; nilai tinggi sebaliknya. Mulai dengan 100 dan naikkan jika hasil dirasa kurang akurat:
hasil = collection.query(
query_texts=["topik pencarian"],
n_results=10,
search_params={"ef_search": 256},
)search_params={"ef_search": 256} memakai kualitas pencarian lebih tinggi untuk query penting. Ukur dengan timing untuk menemukan titik optimal kasus kalian.
Pra-filter metadata — memfilter sebelum pencarian vektor — memangkas jumlah vektor yang harus dibandingkan. Untuk collection yang besar, ini sering memberikan pengurangan latensi paling dramatis:
hasil = collection.query(
query_texts=["deploy"],
n_results=5,
where={"category": "devops"},
search_params={"ef_search": 200},
)where={"category": "devops"} membatasi pencarian hanya pada vektor dengan kategori tersebut. Jika collection punya 5 juta vektor tapi hanya 200 ribu kategori devops, pencarian berjalan jauh lebih cepat tanpa mengorbankan akurasi.
Estimasi kasar: index HNSW membutuhkan beberapa kali ukuran vektor mentah. Untuk vektor 384 dimensi (float32), satu juta vektor kira-kira 1,5 GB — plus overhead index sekitar 2-4 kali lipat.
1 juta vektor x 384 dimensi x 4 bytes ~= 1.5 GB
estimasi index total (overhead 2-4x) ~= 3-6 GBJadikan estimasi ini titik awal, lalu ukur penggunaan aktual dengan monitoring dari episode 20. RAM yang cukup menghindarkan swap yang menghancurkan latensi.
| Skala | Saran Resource | Strategi |
|---|---|---|
| <100 ribu vektor | 2 CPU / 4 GB | Single instance |
| 100 ribu - 5 juta | 4 CPU / 16 GB | Single instance + tuning |
| 5 juta+ | 8+ CPU / 32+ GB | Multi-replica + load balancer |
Untuk melewati 5 juta vektor, kombinasi episode 15 (load balancer) dan episode 19 (advanced query patterns) menjadi penting. Jangan lupa: setiap replica membutuhkan RAM untuk index-nya sendiri.
Episode 17 membuat ChromaDB siap tumbuh: memahami fondasi server Rust untuk skala jutaan vektor, ingestion batch yang 10-100 kali lebih cepat, tuning index dari episode 9, optimasi ef_search dan pra-filter metadata untuk latensi, serta sizing CPU dan RAM berdasarkan volume data.
Inti yang harus dibawa pulang:
ef_construction: 200 dan M: 16 untuk skala.ef_search diatur per query; mulai 100, naikkan bila perlu.Di episode 18 selanjutnya kita akan membahas integrasi dengan framework LLM — menjadikan ChromaDB sebagai VectorStore dan retriever di LangChain, adapter LlamaIndex dan framework lain, serta pola RAG production: chunking, embed, store, dan query dalam satu pipeline utuh. Semua kemampuan yang sudah kalian bangun sekarang bersatu.