karawaci.kode

2026-06-12 · 7 min

Supabase pgvector RAG 1k Documents: Cost + Latency Real

Klien firma hukum mid-size Jakarta (8 lawyer, ~1000 kontrak digital sejak 2023) request internal search bertenaga AI: “find me kontrak yang ada klausa indemnity untuk perusahaan listing IDX.” Saya build RAG sederhana di Supabase pgvector. Hasil terukur setelah 6 minggu.

Setup

Stack:

  • Supabase Postgres 17 + pgvector 0.7
  • Embedding: OpenAI text-embedding-3-small (1536 dim)
  • LLM: Claude Sonnet 4.7 untuk synthesis
  • App: Next.js 15 dengan Server Actions
  • Auth: Supabase Auth (related: Bun + Supabase Auth migration)

Data: 1,047 dokumen PDF kontrak, average 18 halaman, total ~15,8MB text setelah extract.

Indexing pipeline

// Pipeline pseudocode
for each pdf:
  text = extract(pdf)
  chunks = splitter.split(text, {chunkSize: 800, overlap: 100})
  for each chunk:
    embedding = openai.embeddings(chunk)
    db.insert(document_chunks, {pdf_id, chunk_text, embedding, metadata})

Total chunk: 18,420 (1,047 dokumen × rata-rata 17 chunk).

Embedding cost: 18,420 chunk × ~600 token avg = ~11M token × $0.020/M = $0.22 (~Rp 3,5k). One-time.

Index pgvector: HNSW dengan m=16, ef_construction=64. Build time 4 menit 22 detik.

CREATE INDEX ON document_chunks 
USING hnsw (embedding vector_cosine_ops) 
WITH (m = 16, ef_construction = 64);

Query latency

Test 200 sample query (real query dari lawyer klien).

Vector retrieval (top-10 chunk):

  • P50: 78ms
  • P95: 280ms
  • P99: 510ms

Source latency: pgvector HNSW + Supabase Singapore region. 200ms baseline dari Jakarta → Singapore tetap ada.

End-to-end query (retrieval + LLM synthesis):

  • P50: 2,8 detik
  • P95: 5,4 detik
  • P99: 8,2 detik

LLM dominate latency. Claude Sonnet streaming, tapi total time-to-final-token tetap 2-5 detik.

Cost (bulanan)

  • Supabase Pro: $25/mo (sudah include 8GB storage + DB + auth)
  • OpenAI embeddings (query): ~5,000 query/bulan × 50 token avg × $0.020/M = $0.005 (negligible)
  • Claude API (synthesis): ~5,000 query × 2,500 token input + 800 output × $3/M + $15/M = $52,5

Wait, mahal. Saya optimize:

  • Tambah cache layer untuk query yang serupa (Redis di Hetzner): ~30% cache hit
  • Switch sebagian query ke Claude Haiku untuk pertanyaan sederhana: 60% query
  • Final Claude API cost: ~$23/mo

Total: $25 (Supabase) + $0,01 (embedding) + $23 (Claude) = $48/mo

Klien acceptable. Per query cost: $48 / 5000 = ~$0,0096 atau Rp 154/query. Bandingkan dengan junior associate lawyer cari kontrak manual: ~15 menit × Rp 75k/jam = Rp 18,750/query.

Akurasi: chunk size matters

Initial attempt: chunk size 1500 character. Retrieval kadang miss konteks penting.

Test 50 query dengan ground truth (manual annotated):

  • Chunk 1500: precision@5 = 62%, recall@10 = 71%
  • Chunk 800 + overlap 100: precision@5 = 78%, recall@10 = 86%
  • Chunk 400 + overlap 50: precision@5 = 73%, recall@10 = 91% (tapi context per chunk kurang)

Saya pilih 800 chunk + 100 overlap sebagai best balance. Re-index satu kali = $0,22 cost.

Pitfall yang bikin malu

Klien lawyer test query: “Cari kontrak yang ada klausa force majeure terkait pandemic post-2020.”

System return: top result adalah kontrak yang punya kata “force majeure” + “pandemic” + “2020” — tapi konteks-nya negatif (klausa explicit EXCLUDE pandemic dari force majeure). Lawyer perlu kebalikan: kontrak yang INCLUDE pandemic ke force majeure.

Root cause: cosine similarity tidak understand negation. “Termasuk pandemic” dan “tidak termasuk pandemic” dekat di embedding space karena kata-katanya overlap.

Fix:

  1. Tambah re-ranking step dengan Claude: pass top-20 retrieval, minta Claude filter berdasarkan semantic intent yang true ke query
  2. Cost naik per query (extra LLM call): +~$0,005/query, atau ~$25/mo extra
  3. Precision@5 naik ke 91%

Final cost: ~$73/mo. Klien setuju.

Yang juga break

  1. Embedding rate limit: OpenAI free tier limit 3 req/min embedding. Saya pakai paid tier sejak awal, no issue. Worth-noting kalau hackathon.

  2. pgvector index rebuild lambat: kalau data growth besar (kasus saya: klien tambah 200 kontrak baru), index rebuild ~1 menit dengan SQL REINDEX INDEX CONCURRENTLY. Tidak ada downtime.

  3. PDF extract noise: text dari PDF kadang ada header/footer / page number yang noise. Saya tulis cleanup regex; tapi 5% PDF tetap noisy. Saya tag those for human review.

  4. Bahasa Indonesia + Inggris mixed: kontrak banyak hybrid (term Inggris di kalimat Indonesia). Embedding model OpenAI multilingual handle this. Test: jangan pakai monolingual Indonesian model untuk legal hybrid.

Memory

Supabase Postgres usage:

  • pgvector table size: 1,2GB (chunk text + embedding)
  • Index size: 380MB
  • Total DB: 1,7GB. Masih jauh dari Pro tier 8GB limit.

Kalau growth ke 10x (10k dokumen): estimate 17GB. Butuh upgrade Supabase Team ($599/mo) atau migrate ke self-hosted Postgres + pgvector di Hetzner.

Verdict

Untuk RAG 100-10k dokumen di Indonesian SMB context: Supabase pgvector + OpenAI embeddings + Claude synthesis sangat cost-effective.

Cost-per-query Rp 200-an, vs lawyer manual research Rp 18k+: 90x cheaper. Klien sangat happy, sekarang lawyer pakai daily, expand ke dokumen non-kontrak (legal opinion, court ruling).

Bukan magic — chunk size, re-ranking, dan negation handling butuh iterate. Setup awal 8 jam, fine-tune sampai akurasi acceptable 12 jam tambahan.

Untuk volume > 1jt vector: pertimbangkan dedicated vector DB. Untuk SMB Indonesia: pgvector lebih dari cukup.

Ditulis oleh Reza Pradipta