All writing
3 min read

pgvector untuk RAG: kenapa aku tidak pakai vector database terpisah

AI WhatsApp bot pakai pgvector di PostgreSQL, bukan Pinecone. Satu database, zero vendor baru, dan performanya cukup di scale-ku.

Context

Project AI WhatsApp bot: multi-tenant chatbot dengan RAG (Retrieval-Augmented Generation). Bot harus jawab pertanyaan customer berdasarkan data produk/layanan tiap tenant — bukan dari pengetahuan umum model AI.

RAG singkatnya: dokumen produk dipecah jadi chunk, tiap chunk diubah jadi embedding (array angka), disimpan. Saat user tanya, pertanyaannya juga di-embed, lalu dicari chunk yang paling mirip. Chunk itu dikirim ke AI sebagai konteks jawaban.

Pertanyaannya: embedding itu disimpan di mana?

Options yang dipertimbangkan

Option A: Pinecone / vector database dedicated

  • Pro: managed, dioptimasi khusus untuk vector search di jutaan vector
  • Pro: ANN index mature, metadata filtering built-in
  • Con: service baru = infra baru, billing baru, vendor lock-in baru
  • Con: data produk tetap di PostgreSQL. Sekarang ada dua sumber kebenaran yang harus disinkronkan

Option B: pgvector (yang dipilih)

Extension PostgreSQL yang menambah tipe data vector dan index untuk similarity search.

  • Pro: satu database. Produk, chunk, embedding, tenant — semua di Postgres
  • Pro: sudah jalan di stack yang ada. Tidak ada infra tambahan
  • Con: di skala jutaan vector, ANN-nya kalah cepat dari dedicated vector DB

Option C: Qdrant / Weaviate self-hosted

  • Pro: open source, fitur vector search lengkap
  • Con: tetap satu service tambahan yang harus di-deploy, dimonitor, dan di-backup

Keputusan

pgvector. Alasannya bukan performa — tapi jumlah sumber kebenaran.

Kalau embedding di Pinecone dan produk di Postgres, tiap produk di-update aku harus sync ke dua tempat. Sync gagal = bot jawab pakai data basi. Dengan pgvector, update produk dan re-embedding terjadi dalam satu transaksi database yang sama.

Setup-nya:

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE document_chunks (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  document_id uuid NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
  organization_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
  content text NOT NULL,
  embedding vector(1536) NOT NULL
);

CREATE INDEX ON document_chunks
  USING hnsw (embedding vector_cosine_ops);

Query retrieval-nya sederhana:

SELECT content
FROM document_chunks
WHERE organization_id = $1
ORDER BY embedding <=> $2
LIMIT 5;

<=> adalah operator cosine distance dari pgvector. Multi-tenant isolation tetap enforced lewat organization_id — filter dulu, baru similarity search. Ini penting: filter kolom biasa dulu supaya search space kecil, bukan sebaliknya.

Chunking strategy

Dokumen dipotong sekitar 512 token per chunk, dengan overlap 128 token antar chunk. Overlap supaya konteks yang kepotong di batas chunk tidak hilang — kalimat yang kena potong tetap utuh di salah satu chunk.

Trade-off yang aku terima

  • Di scale jutaan vector per tenant, pgvector akan jadi bottleneck. Solusiku: menerima risiko itu. Scale-ku sekarang di ribuan chunk per tenant, bukan jutaan. Kalau sampai jutaan, migrasi ke dedicated vector DB adalah masalah yang bagus untuk dimiliki.
  • Index HNSW makan RAM. Ini biaya yang harus dipantau seiring data tumbuh.
  • Re-embedding besar (misal ganti model embedding) berarti rewrite semua chunk. Aku belum pernah melakukannya di production — rencanaku: background job per tenant, bukan big-bang.

Catatan: RAG tidak dipakai untuk semua query

Satu keputusan yang sama pentingnya: query yang deterministic tidak lewat AI sama sekali. Pertanyaan “harga produk X” di-detect via keyword, langsung query database, format jawaban, kirim. Hemat 2–3 detik response time dan menghindari halusinasi.

Detail lengkapnya ada di case study, termasuk kenapa model AI tertentu gagal disiplin function-calling.

Consequences

  • Satu backup strategy untuk semua data. Embedding ikut ke-backup bareng data produk.
  • Tidak ada vendor lock-in di layer vector. Postgres jalan di mana saja.
  • Response time bot: < 1 detik untuk query keyword, 3–5 detik untuk query yang lewat AI + RAG.

References