Queue dan Cache Worker Andal dengan PostgreSQL memungkinkan tim backend memanfaatkan satu sistem transaksi untuk antrean tugas, locking, dan caching sekaligus. Dalam artikel ini Anda akan menemukan pola schema, transaksi, dan strategi konsistensi yang praktis untuk menghindari race condition, mengendalikan retry, dan memastikan invalidasi cache tetap bisa dioperasikan.
Gunakan pendekatan “All you need is PostgreSQL” sebagai landasan: paket kemampuan yang sudah tersedia — transaction log, advisory lock, LISTEN/NOTIFY, materialized view — cukup untuk membangun worker reliable tanpa kompleksitas eksternal.
Arsitektur Dasar Queue dan Cache Worker
Sistem worker terdistribusi biasanya membutuhkan antrean pekerjaan dan cache hasil kerja. PostgreSQL bisa menyatukan keduanya dengan tabel queue, tabel status, dan cache materialized view. Susun schema agar worker hanya perlu satu transaksi untuk mengambil pekerjaan, memperbarui status, dan/atau menulis cache.
Contoh schema dasar:
CREATE TABLE worker_queue (
id SERIAL PRIMARY KEY,
payload JSONB NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
retry_count INT NOT NULL DEFAULT 0,
available_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE worker_locks (
target TEXT PRIMARY KEY,
locked_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Kolom available_at memungkinkan strategi backoff tanpa mengeluarkan pekerjaan dari tabel, cukup tambahkan kondisi pada SELECT. Status, retry_count, dan payload bisa di-update dalam satu transaksi untuk menghindari kondisi balapan.
Desain Schema dan Penanganan Retry
Ambil pekerjaan dengan transaksi optimis, filtering berdasarkan status dan available_at. Gunakan FOR UPDATE SKIP LOCKED agar worker lain bisa memilih pekerjaan berikutnya tanpa menunggu kunci yang sedang digunakan.
BEGIN;
SELECT id, payload
FROM worker_queue
WHERE status = 'pending' AND available_at <= NOW()
ORDER BY id
FOR UPDATE SKIP LOCKED
LIMIT 1;
UPDATE worker_queue
SET status = 'in_progress'
WHERE id = ?;
COMMIT;
Jika proses gagal, kembalikan status dan jadwalkan ulang dengan backoff:
UPDATE worker_queue
SET
status = 'pending',
retry_count = retry_count + 1,
available_at = NOW() + (retry_count * INTERVAL '5 seconds')
WHERE id = ?;
Rentang waktu ini menghindari retry agresif tanpa perlu scheduler eksternal. Pastikan worker memeriksa nilai retry_count untuk membatasi percobaan sebelum memindahkan ke dead-letter queue atau notifikasi ops.
Locking dan Konsistensi dengan Advisory Lock serta LISTEN/NOTIFY
Untuk memperkuat konsistensi lintas worker, kombinasikan advisory lock dan LISTEN/NOTIFY. Advisory lock menghindari dua worker bekerja pada konten yang sama, sementara NOTIFY memberi tahu worker agar menunggu peristiwa baru.
Contoh penggunaan advisory lock untuk memproses satu kategori tertentu:
SELECT pg_try_advisory_xact_lock(hashtext('kategori-A'));
-- Jika true, lanjutkan pemrosesan data kategori-A dalam transaksi yang sama
Dengan pg_try_advisory_xact_lock, lock dilepas otomatis saat transaksi selesai. Ini menyederhanakan cleanup dan menghindari kondisi deadlock jika worker crash.
LISTEN/NOTIFY digunakan untuk men-trigger worker saat ada pekerjaan baru tanpa polling terus-menerus:
NOTIFY worker_channel, 'new_job';
-- Worker lain menjalankan LISTEN worker_channel dan akan terbangun saat notifikasi diterima.
Perhatikan bahwa NOTIFY hanya mengirimkan payload kecil; gunakan untuk signal saja, bukan data besar. Worker bisa men-trigger SELECT setelah menerima signal untuk mengambil pekerjaan terbaru sekaligus FOR UPDATE SKIP LOCKED.
Cache Materialized View dan Invalidation
PostgreSQL materialized view bisa berfungsi sebagai cache pre-computed. Jika data dasar sering berubah, buat procedure caching yang me-refresh dengan interval atau event, lalu gabungkan dengan table queue agar worker menulis ke cache saat selesai.
Contoh pattern:
- Create materialized view yang menampung hasil intensif.
- Gunakan trigger atau worker yang memperbarui view melalui
REFRESH MATERIALIZED VIEW CONCURRENTLYagar tidak mengunci pembaca. - Sertakan kolom cache_version di table queue untuk membantu worker mencari apakah cache sudah lapuk.
Pada invalidasi, jangan refresh view setiap update kecil. Sebagai gantinya, worker bisa menulis timestamp terakhir update ke tabel tracking dan consumer cache men-trigger refresh jika selisihnya terlalu besar atau setelah batch tertentu.
Observability dan Mitigasi Latensi
Observability penting untuk mendeteksi antrian tersendat atau lock yang menumpuk. Gunakan metric berikut:
- Pending job count dan rata-rata durasi dari status 'in_progress'.
- Retry count per job dan rate kegagalan.
- Advisory lock contention ratio, misalnya berapa sering
pg_try_advisory_xact_lockmengembalikan false.
Tambahkan logging terstruktur untuk setiap transisi status, termasuk alasan retry. Gunakan EXPLAIN (ANALYZE) pada query SELECT job untuk memastikan index digunakan.
Untuk mitigasi latensi, batasi jumlah pekerjaan yang diambil dalam satu transaksi, gunakan partitioning pada tabel queue sesuai shard logika, dan monitor wal_buffers serta wal_writer jika antrean menghasilkan banyak update.
Kesimpulan
Dengan pendekatan schema, transaksi, caching, LISTEN/NOTIFY, dan advisory lock ini, PostgreSQL menjadi pusat queue, locking, dan cache worker terdistribusi yang andal. Fokuslah pada operasi atomik, instrumentasi metrik, dan invalidasi cache terkontrol agar sistem tetap dapat dioperasikan tanpa dependensi eksternal berat.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!