karawaci.kode

2026-06-24 · 7 min

Mongo vs Postgres JSONB di SaaS 2026: Pilih Mana

Klien startup Jakarta minta saya bantu pilih DB untuk SaaS baru: form builder + report generator dengan schema super flexible per tenant. “Mongo atau Postgres dengan JSONB?” Saya benchmark keduanya di sample workload nyata 2 minggu. Hasil terukur, plus context untuk decision.

Setup

Workload yang saya simulate:

  • 1000 tenant, masing-masing punya 5-20 form unique schema
  • 50,000 submission/hari total (rata-rata)
  • Peak hour: 8000 submission/jam (lunch time enterprise customer)
  • Query pattern: filter by tenant, by date range, by JSONB field value, plus aggregate

Mongo 7.0 setup:

  • Self-hosted di Hetzner cx51 (8 vCPU, 32GB RAM)
  • WiredTiger storage engine
  • Replica set 1 primary + 1 secondary
  • Indexes pada tenantId, submittedAt, dan compound untuk common query

Postgres 17 setup:

  • Self-hosted di Hetzner cx51 (same spec)
  • Table submissions dengan column JSONB untuk dynamic field
  • GIN index pada JSONB column + B-tree pada tenantId, submittedAt
  • Partial index untuk top 10% schema field paling sering di-query

Schema design

Mongo:

{
  _id: ObjectId(...),
  tenantId: "tenant_abc",
  formId: "form_123",
  submittedAt: ISODate(...),
  data: {
    customer_name: "Budi",
    age: 34,
    products: ["A", "B"],
    custom_field_xyz: "value"
  }
}

Postgres:

CREATE TABLE submissions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id TEXT NOT NULL,
  form_id TEXT NOT NULL,
  submitted_at TIMESTAMPTZ DEFAULT NOW(),
  data JSONB NOT NULL
);

CREATE INDEX idx_tenant_date ON submissions (tenant_id, submitted_at DESC);
CREATE INDEX idx_data_gin ON submissions USING gin (data);
CREATE INDEX idx_data_customer ON submissions ((data->>'customer_name'))
  WHERE data ? 'customer_name';

Both flexible untuk dynamic schema.

Write throughput

Test: bulk insert 100,000 submission dengan 8 worker parallel.

Mongo:

  • Sustained: 18,500 op/sec
  • P99 latency: 28ms

Postgres:

  • Sustained: 11,200 op/sec
  • P99 latency: 42ms

Mongo ~65% lebih cepat untuk pure write. Source: Mongo write path lebih lean, no WAL synchronous commit yang Postgres punya.

Untuk klien saya yang peak 8000/jam (~2,2 op/sec): both vastly over-provisioned. Beda write perf tidak material.

Read latency

Query 1: simple tenant filter + date range, return 100 row.

// Mongo
db.submissions.find({tenantId: "x", submittedAt: {$gte: ...}}).limit(100)

// Postgres
SELECT * FROM submissions WHERE tenant_id = 'x' AND submitted_at >= '...' LIMIT 100;
  • Mongo: P50 8ms, P95 22ms
  • Postgres: P50 6ms, P95 18ms

Tie. Index hit at both.

Query 2: filter by nested JSON field.

// Mongo
db.submissions.find({tenantId: "x", "data.customer_name": "Budi"})

// Postgres dengan GIN
SELECT * FROM submissions WHERE tenant_id = 'x' AND data @> '{"customer_name": "Budi"}';
  • Mongo: P50 14ms, P95 38ms
  • Postgres GIN: P50 12ms, P95 28ms (partial index hit)
  • Postgres tanpa partial index: P50 145ms (GIN scan slower)

Postgres dengan partial index pada field yang sering di-query menang. Tapi butuh schema awareness — define index per high-traffic field.

Query 3: aggregate (sum amount group by date).

// Mongo aggregation pipeline
db.submissions.aggregate([
  {$match: {tenantId: "x"}},
  {$group: {_id: {$dateToString: {...}}, total: {$sum: "$data.amount"}}}
])

// Postgres
SELECT date_trunc('day', submitted_at), SUM((data->>'amount')::numeric)
FROM submissions WHERE tenant_id = 'x' GROUP BY 1;
  • Mongo: P50 380ms, P95 980ms
  • Postgres: P50 190ms, P95 540ms

Postgres menang aggregate ~2x. SQL engine matang untuk OLAP-style.

Query power

Postgres menang besar di:

  • JOIN dengan table lain (tenant, user, form schema)
  • Window function (RANK, LAG, dll)
  • CTE untuk complex query
  • Full-text search native (to_tsvector)
  • Vector similarity (pgvector — sama infrastructure)

Mongo menang di:

  • Schema-on-read flexibility (truly schemaless)
  • Aggregation pipeline syntax compact untuk transformation
  • Native sharding (untuk scale > single node)

Untuk SaaS klien yang akan join submission dengan user, tenant, dan form metadata: Postgres clear menang. Cross-collection join di Mongo via $lookup slow + verbose.

Ops complexity

Mongo:

  • Replica set setup: 1 hari
  • Backup: mongodump / oplog tail / WiredTiger snapshot
  • Schema migration: by convention (Mongoose), no DB-level enforcement
  • Monitoring: mongostat + Prometheus exporter

Postgres:

  • Replication setup: 1 hari (lihat Postgres 17 incremental backup saya)
  • Backup: pg_basebackup + WAL archive
  • Schema migration: explicit (drizzle-kit, etc) — DB-level enforce
  • Monitoring: pg_stat_* views + Prometheus exporter

Both production-mature. Postgres ekosistem tooling sedikit lebih luas (matang sejak 1990-an).

Cost

Self-hosted Hetzner cx51 same spec: $35/mo. Equal.

Managed alternative:

  • Mongo Atlas M30 (Singapore): $389/mo
  • Supabase Pro: $25/mo (+ usage tier)
  • Neon Scale: ~$100-200/mo depending volume

For klien startup with budget constraint: Supabase Postgres wins on cost vs managed Mongo.

Yang break (kalau pilih Mongo)

Saya pernah operate Mongo di project lain. Issue yang muncul:

  1. Schema drift: tanpa enforcement, beberapa tenant accidentally write field naming inconsistent (customerName vs customer_name). Query lapse. Sekarang saya pakai Mongoose dengan strict schema, defeating purpose dari schemaless to some extent.

  2. Aggregation memory limit: 100MB sort limit di-hit pernah saat report bulanan. Saya pakai allowDiskUse: true workaround. Postgres tidak ada limit serupa di default config.

  3. Lookup performance: cross-collection join untuk report yang involve tenant + user + submission slow. Postgres native join lebih clean dan fast.

Yang break (Postgres JSONB)

  1. Index maintenance: GIN index update lambat (saat write heavy). Saya pakai fillfactor=80 + maintenance window untuk reindex.

  2. JSONB query syntax: lebih verbose dari Mongo dot-notation. data->>'customer_name' vs "data.customer_name". Team butuh ramp-up untuk pattern.

  3. Schema migration JSONB: tidak ada schema-level enforcement untuk struktur JSONB. Saya pakai zod schema di app layer untuk validate sebelum write.

Verdict untuk klien startup

Saya rekomendasi Postgres JSONB. Reasoning:

  1. Query power untuk cross-data join (submission + user + form schema)
  2. Single-store untuk OLTP + OLAP + future vector use case (pgvector)
  3. Cost lebih affordable untuk startup phase (Supabase Pro vs Mongo Atlas)
  4. Ekosistem tooling matang (backup, replication, monitoring)

Klien setuju. Sekarang 4 bulan production, no regret.

Kapan saya pilih Mongo

Kalau workload:

  • Sustained write > 50k op/sec (kasus IoT, log ingestion massive)
  • Schema benar-benar unknown / change frequent
  • Tim sudah mature Mongo expertise
  • Native horizontal sharding requirement

Untuk most SaaS Indonesia: jarang hit threshold ini.

Bukan magic — pilih DB based on workload, bukan trend. Postgres + JSONB sudah cover 90%+ “saya butuh schema flexible” case di 2026.

Ditulis oleh Reza Pradipta