Kodeloka
  1. Kodeloka
  2. Kelas PostgreSQL
  3. Materi

1 · Fondasi · Modul 1 dari 12

Arsitektur PostgreSQL: Proses, Memori & WAL

Bagaimana PostgreSQL menyimpan dan melindungi data — dan kenapa memahami WAL menjelaskan hampir semua perilaku yang tampak aneh.

Model Proses, Bukan Thread

PostgreSQL membuat satu proses sistem operasi untuk setiap koneksi. Ini berbeda dari MySQL yang memakai thread, dan punya konsekuensi praktis yang besar.

postmaster (proses induk)
   ├── backend  ← koneksi klien 1
   ├── backend  ← koneksi klien 2
   ├── backend  ← koneksi klien 3
   ├── background writer   (menulis halaman kotor ke disk)
   ├── WAL writer          (menulis log transaksi)
   ├── checkpointer        (menyinkronkan berkas data)
   ├── autovacuum launcher
   └── stats collector
🚫 Inilah kenapa Anda WAJIB memakai connection pooler

Setiap koneksi memakan beberapa megabyte memori dan satu slot proses, bahkan ketika sedang menganggur. Seribu koneksi menganggur berarti seribu proses yang membebani penjadwal sistem operasi. Aplikasi modern yang menjalankan puluhan pod dengan pool masing-masing sangat mudah menembus max_connections.

Solusinya adalah PgBouncer atau pgcat di depan PostgreSQL, yang menggabungkan ribuan koneksi aplikasi menjadi puluhan koneksi sebenarnya.

Write-Ahead Log

Aturan emas PostgreSQL: perubahan dicatat ke WAL sebelum diterapkan ke berkas data. Ini yang membuat data tetap utuh meski server mati mendadak.

1. COMMIT diterima
2. Catatan perubahan ditulis ke WAL dan di-fsync ke disk  ← titik aman
3. COMMIT dibalas ke klien
4. Halaman data di memori ditandai "kotor"
5. Nanti, checkpointer menuliskannya ke berkas data

Bila server mati di antara langkah 3 dan 5, PostgreSQL memutar ulang WAL saat menyala dan memulihkan seluruh transaksi yang sudah di-commit. WAL juga menjadi fondasi replikasi dan point-in-time recovery.

⚠️ fsync = off adalah undangan kehilangan data

Menonaktifkan fsync memang membuat penulisan jauh lebih cepat, dan sesekali disarankan orang di internet untuk "meningkatkan performa". Konsekuensinya: mati listrik berarti basis data rusak dan tidak dapat dipulihkan. Ini hanya boleh dipakai untuk memuat data awal yang bisa diulang dari nol, tidak pernah untuk sistem yang menyimpan data sungguhan.

Memori

ParameterFungsiTitik awal yang wajar
shared_buffersCache halaman data milik PostgreSQL25% RAM
effective_cache_sizePerkiraan total cache termasuk milik OS — hanya memengaruhi keputusan perencana50–75% RAM
work_memMemori per operasi sort/hash16–64 MB
maintenance_work_memUntuk VACUUM dan CREATE INDEX512 MB – 2 GB
🚫 work_mem dikalikan, bukan dibagi

work_mem berlaku per operasi, bukan per kueri dan bukan per koneksi. Satu kueri kompleks dengan lima operasi sort bisa memakai lima kali work_mem. Bila 100 koneksi menjalankan kueri semacam itu bersamaan dengan work_mem = 256MB, PostgreSQL bisa meminta ratusan gigabyte dan server terkena OOM killer. Setel nilai global yang konservatif, lalu naikkan per sesi untuk kueri berat tertentu.

MVCC: Kenapa Pembaca Tidak Pernah Memblokir Penulis

PostgreSQL memakai Multi-Version Concurrency Control. Saat sebuah baris diperbarui, versi lamanya tidak ditimpa — versi baru ditulis di sebelahnya, dan versi lama tetap ada untuk transaksi yang masih membutuhkannya.

Transaksi A: SELECT * FROM produk WHERE id = 1;   → melihat versi lama
Transaksi B: UPDATE produk SET harga = 90000 ...  → membuat versi baru
Transaksi A: SELECT lagi                          → TETAP versi lama

Tidak ada yang menunggu. Tidak ada kunci baca.
💡 Harga yang dibayar MVCC

Versi lama yang sudah tidak dibutuhkan siapa pun disebut dead tuple, dan tetap memakan ruang di disk. VACUUM bertugas membersihkannya. Inilah sebabnya tabel PostgreSQL yang sering di-update bisa membengkak jauh melebihi ukuran datanya — masalah yang dikenal sebagai bloat, dan dibahas tersendiri di modul VACUUM.

Memeriksa Keadaan

-- Versi dan konfigurasi
SELECT version();
SHOW shared_buffers;

-- Koneksi aktif dan apa yang sedang mereka kerjakan
SELECT pid, usename, state, wait_event_type,
       now() - query_start AS durasi, left(query, 60)
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY durasi DESC;

-- Ukuran basis data dan tabel terbesar
SELECT pg_size_pretty(pg_database_size(current_database()));

SELECT relname,
       pg_size_pretty(pg_total_relation_size(relid)) AS total
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 10;