Memenuhi tuntutan strict memory overcommit PostgreSQL

Ketika PostgreSQL dijalankan dengan strict memory overcommit, server akan menolak alokasi memori tambahan setiap kali sistem tidak memiliki cadangan yang cukup, sehingga karena itu ques dan worker yang intensif memori harus diatur ulang agar tidak memicu OOM killer. Solusi utamanya adalah menyesuaikan jumlah worker, memastikan cache warming tidak tiba-tiba mengonsumsi seluruh memori bebas, serta menyiapkan fallback job dan observabilitas untuk merespons kegagalan alokasi.

Dengan pendekatan ini kita mengurangi probabilitas Pg menolak alokasi dengan menyeimbangkan caching dan paralelisme pada tingkat aplikasi, bukan hanya mengandalkan konfigurasi PostgreSQL. Artikel ini menjelaskan langkah-langkah praktis menyesuaikan worker/queue, menjaga konsistensi cache, serta mengenali kapan sistem harus menurunkan beban.

Menentukan batas paralelisme dan cache warming

Batas paralelisme harus ditetapkan berdasarkan konsumsi memori rata-rata query utama ditambah overhead worker queue. Hitung rata-rata penggunaan working set per job, lalu hitung jumlah worker maksimal yang masih mungkin jalan bersamaan tanpa melebihi memori fisik dikurangi margin safety. Jika tidak tersedia profil, mulai dari jumlah worker kecil (misal 2-4) kemudian ukur penggunaan RSS.

Contoh konfigurasi worker pada Python menggunakan queue berbasis Redis dan modul asyncio:

WORKER_CONCURRENCY = 3
CACHE_WARMUP_QUERIES = ["SELECT id FROM tenants WHERE active"]

async def worker_loop(queue):
    while True:
        payload = await queue.get()
        await warm_cache(payload['tenant_id'])
        await process_job(payload)
        queue.task_done()

Kunci di atas adalah mengukur warming cache secara terpisah dan tidak memaksa semua worker mengeksekusi cache warming bersama. Tambahkan mekanisme rate limiting atau debounce cache warming agar tidak memicu lonjakan memori saat banyak job baru masuk bersamaan.

Gunakan queue distributive (misalnya Redis Streams atau RabbitMQ) yang memungkinkan Anda membatasi jumlah job dibawa ke worker dengan opsional prefetch_count. Bila perlu, pasang middleware yang memeriksa memori sebelum memulai job (misalnya membaca /proc/meminfo di Linux) dan menunda pengambilan job hingga ada ruang.

Cache warming, konsistensi, dan locking

Cache warming harus dilakukan dengan cara yang reproducible dan tidak menyimpan data langsung dari tiap worker ke cache utama bersamaan. Gunakan strategi seperti:

  • Cache warming terkoordinasi: worker menulis tanda bahwa ia sedang men-warm cache, worker lain menunggu atau membaca versi sebelumnya untuk mencegah cache stampede.
  • Konsistensi cache vs locking: locking berlebihan (misalnya FOR UPDATE pada ratusan baris) bisa menghambat throughput, tetapi konsistensi data penting kalau job memperbarui cache yang dibaca downstream. Gunakan optimistic locking dimana job memeriksa _version_ row sebelum menulis kembali, atau gunakan kunci distribusi ringan seperti Redlock untuk memberi prioritas pembaruan cache tanpa mengunci DB terlalu lama.
  • Cache invalidation terencana: tandai cache sebagai kadaluarsa (misal TTL 30 detik) alih-alih memaksa invalidasi manual yang menyebabkan lonjakan warming.

Trade-off utama:

  • Locking kuat: menjamin konsistensi data tetapi menurunkan paralelisme dan bisa memicu antrean PostgreSQL menunggu, yang mengakibatkan job menumpuk.
  • Locking ringan / tanpa locking: menjaga throughput tetapi harus menyiapkan logika retry untuk menangani inkonsistensi.

Pilih pendekatan yang sesuai dengan tingkat sensitivitas data dan beban sistem. Untuk cache read-heavy, konsistensi akhirnya bisa dicapai dengan strategi read-through + retry, sementara untuk update-critical, gunakan locking terbatas (misalnya per baris / per tenant).

Strategi fallback job dan kontrol queue

Ketika Postgres menolak alokasi memori, job intensif harus ditunda atau dialihkan ke queue dengan prioritas lebih rendah agar sistem tetap responsif. Strategi praktis:

  • Queue prioritas: pisahkan job berat (cache rebuild, batch report) dari job latensi rendah. Gunakan worker khusus yang dijalankan hanya jika memori cukup.
  • Fallback job: jika job utama gagal karena OOM, enqueue ulang ke queue khusus yang dieksekusi dengan tingkat paralelisme lebih rendah (misal 1 worker). Job fallback dapat mengirim notifikasi, mengurangi cakupan, atau menjalankan versi lebih ringan.
  • Backpressure control: sebelum mengeksekusi job berat, periksa status memori dan jumlah job aktif. Jika melebihi ambang, taruh job pada delayed queue dengan interval meningkat (exponential backoff).

Contoh konfigurasi fallback pada Node.js:

const worker = new QueueWorker({
  concurrency: 2,
  fallbackQueue: 'fallback-cache',
});

worker.on('oom-detected', job => {
  fallbackQueue.add({ jobId: job.id }, { delay: 60000 });
});

Observabilitas dan respons saat Postgres menolak alokasi

Observabilitas penting untuk segera mendeteksi ketika strict memory overcommit menyebabkan kegagalan. Langkah-langkah:

  • Pantau log kernel untuk pesan seperti Out of memory dan oom-killer dengan journalctl -k atau auditing.
  • Gunakan query Pg berikut untuk melihat blocking dan walsender yang mengonsumsi memori: SELECT pid, state, waiting, query FROM pg_stat_activity WHERE state <> 'idle';
  • Catat metric memory overhead worker (RSS, heap) dan jumlah job aktif di queue. Gunakan Prometheus exporter (misalnya node_exporter + custom job) untuk mencatat queue_length dan worker_memory_usage_bytes.
  • Buat alert ketika utilization mendekati 90% dari memori fisik dan job berat masih antre.

Jika Postgres menolak alokasi, kurangi sementara paralelisme dan aktifkan fallback job. Dengan observabilitas yang baik, Anda tahu apakah bus queue atau Postgres yang menjadi batasan dan bisa menyesuaikan parameter tanpa menunggu OOM killer menutup proses.

Ringkasan

Strict memory overcommit PostgreSQL menuntut kita mengendalikan worker dan queue agar tidak mengekseskusi job intensif memori secara blind. Batasi paralelisme, koordinasikan cache warming, dan siapkan fallback job untuk mengurangi tekanan ketika alokasi ditolak. Observabilitas memori dan trade-off locking-konsistensi membantu menentukan kapan mengorbankan throughput demi stabilitas. Dengan pendekatan ini, queue tetap berjalan tanpa harus bergantung pada kernel untuk menyelamatkan sistem dari OOM killer.