Ketika aplikasi melapor “database lambat”, pertanyaan pertama administrator bukan “parameter mana yang harus dinaikkan”, melainkan “apa yang sedang dikerjakan server saat ini”. PostgreSQL menjawabnya lewat view pg_stat_activity: satu baris untuk setiap proses server — koneksi klien, proses latar, semuanya — lengkap dengan query yang sedang berjalan, state-nya, dan peristiwa yang ditunggu. Chapter ini mengelilingi view itu dari sisi administrator: mengenali jenis proses, membaca kolom aktivitas, memfilter query yang sedang berjalan, dan mendeteksi koneksi yang saling menyumbat lewat lock.
Model Proses: Satu Postmaster, Banyak Backend #
PostgreSQL memakai arsitektur process-per-connection yang sudah kita kenal di chapter Arsitektur PostgreSQL
: satu proses induk bernama postmaster mendengarkan koneksi baru, lalu men-fork satu proses backend untuk setiap klien. Selain itu postmaster juga memelihara segelintir proses latar yang bekerja terus-menerus tanpa pernah melayani klien. Seluruh proses itu — bukan hanya milik satu database — terlihat dari pg_stat_activity karena view ini bersifat cluster-wide.
Proses Latar di pg_stat_activity #
Kolom backend_type membedakan jenis setiap proses. File berikut menghitung proses yang bukan koneksi klien:
-- Proses latar server yang bukan koneksi klien
SELECT backend_type, count(*)
FROM pg_stat_activity
WHERE backend_type <> 'client backend'
GROUP BY backend_type
ORDER BY backend_type;Output yang Dihasilkan:
backend_type | count
------------------------------+-------
autovacuum launcher | 1
background writer | 1
checkpointer | 1
logical replication launcher | 1
walwriter | 1
(5 rows)Lima proses latar itu bekerja sebagai berikut:
| backend_type | Tugas |
|---|---|
checkpointer | Menulis halaman kotor ke data directory pada setiap checkpoint |
background writer | Membuang halaman kotor sedikit demi sedikit agar checkpoint tidak menumpuk |
walwriter | Menulis dan mem-flush log WAL |
autovacuum launcher | Mengatur turunan autovacuum worker yang membersihkan tabel secara otomatis |
logical replication launcher | Menyiapkan worker replikasi logical bila ada subscription |
Daftar ini berubah bentuk saat beban berubah: proses autovacuum worker muncul sementara saat ada tabel yang perlu dibersihkan, walsender hadir ketika ada klien replikasi, dan parallel worker muncul untuk query besar yang dijalankan paralel. Filter backend_type <> 'client backend' sengaja membuang semua koneksi klien — termasuk session kita sendiri — supaya hasilnya hanya menampilkan “karyawan tetap” server.
Membaca Kolom Aktivitas #
Sebelum memakai view ini untuk diagnosis, mari kita petakan kolom-kolomnya dengan mengamati session sendiri:
| Kolom | Isi | Catatan |
|---|---|---|
pid | PID proses sistem operasi | Berbeda setiap koneksi; kunci untuk pg_terminate_backend |
usename | Nama role pemilik koneksi | Bukan user sistem operasi |
datname | Database tempat koneksi bekerja | NULL untuk proses latar |
state | active, idle, idle in transaction | Dibahas di chapter Connection Management |
wait_event_type | Kategori yang sedang ditunggu | NULL bila tidak menunggu apa pun |
wait_event | Nama spesifik peristiwa yang ditunggu | NULL bersama wait_event_type |
query | Teks query terakhir atau yang berjalan | Tetap terisi meski state idle |
query_start, state_change | Waktu mulai query dan pergantian state | Berubah terus — dasar alerting durasi |
backend_type | Jenis proses | client backend untuk koneksi biasa |
Query berikut mengamati session yang menjalankannya sendiri lewat function pg_backend_pid(). File ini dijalankan pada database postgres:
SELECT usename, datname, state, backend_type, wait_event_type, wait_event, query FROM pg_stat_activity WHERE pid = pg_backend_pid();Output yang Dihasilkan:
usename | datname | state | backend_type | wait_event_type | wait_event | query
----------+----------+--------+----------------+-----------------+------------+--------------------------------------------------------------------------------------------------------------------------------------
postgres | postgres | active | client backend | | | SELECT usename, datname, state, backend_type, wait_event_type, wait_event, query FROM pg_stat_activity WHERE pid = pg_backend_pid();
(1 row)Satu baris itu memperlihatkan state = active — wajar, karena baris tersebut dibuat oleh query yang sedang berjalan. Kolom query berisi teks lengkap query pengamat itu sendiri: inilah bukti bahwa pg_stat_activity benar-benar merekam query secara verbatim, bukan ringkasannya. Kolom wait_event_type dan wait_event kosong (NULL) karena session ini tidak sedang menunggu sumber daya apa pun — ia sibuk menjalankan query sendiri. Di lingkungan ramai, kolom dua kolom wait inilah yang membedakan query yang menunggu disk atau lock dari query yang memang menghabiskan CPU.
Wait Event: Query Menunggu Apa #
Ketika sebuah backend tidak maju, wait_event_type memberi tahu kelas penyebabnya:
| wait_event_type | Contoh wait_event | Arti |
|---|---|---|
Lock | relation, transactionid | Menunggu lock table atau baris milik session lain |
IO | DataFileRead, WALWrite | Menunggu baca/tulis ke disk |
LWLock | WALWriteLock, BufferContent | Menunggu lightweight lock struktur memori internal |
BufferPin | BufferPin | Menunggu halaman buffer bisa diakses |
Client | ClientRead | Menunggu klien mengirim perintah berikutnya |
Activity | Waiting for main wait event | Proses latar menunggu jadwal kerjanya |
Timeout | PgSleep | Sengaja menunggu (misalnya pg_sleep) |
IPC | ParallelFinish | Menunggu proses parallel worker lain |
Dua hal praktis dari tabel ini. Pertama, wait_event_type yang NULL pada query yang active biasanya berarti query sedang menghabiskan CPU, bukan menunggu — perhatikan wait_event_type sebelum menyalahkan disk. Kedua, kombinasi paling sering di produksi adalah Lock pada transactionid: satu transaction lama menahan baris, dan antrean panjang query lain menunggu di belakangnya.
Memfilter Query yang Sedang Berjalan #
Di server bersama, pg_stat_activity berisi campuran session dari banyak database. Monitoring harian biasanya berbentuk query terarah — misalnya “query aktif di database ini selain milikku”:
-- Cari query yang sedang berjalan di database ini, selain milik kita
SELECT count(*) AS query_aktif_lain
FROM pg_stat_activity
WHERE state = 'active'
AND datname = current_database()
AND pid <> pg_backend_pid();Output yang Dihasilkan:
query_aktif_lain
------------------
0
(1 row)Tiga filter di file itu adalah pola baku query monitoring: state = 'active' memilih yang benar-benar bekerja, datname = current_database() membatasi ke database kita, dan pid <> pg_backend_pid() membuang session pengamat sendiri agar tidak menghitung dirinya. Hasil 0 berarti tidak ada beban lain saat ini. Untuk laporan yang lebih kaya, pola yang sama tinggal menukar count(*) dengan daftar pid, usename, query, dan now() - query_start AS durasi — kolom durasi itulah yang biasa dipasang ambang peringatan, misalnya query yang melewati lima menit.
Deteksi Blocking #
Antrean lock adalah penyumbang kemacetan yang paling sering: UPDATE pada baris yang sama membuat session kedua menunggu diam-diam sampai transaction pertama selesai. Function pg_blocking_pids(pid) meringkas diagnosis ini — diberi sebuah PID, function mengembalikan daftar PID yang menahannya:
-- Tidak ada yang memblokir session ini
SELECT pg_blocking_pids(pg_backend_pid()) AS pemblokir;
-- Lock yang sedang dipegang session ini
SELECT locktype, mode, granted
FROM pg_locks
WHERE pid = pg_backend_pid()
ORDER BY locktype, mode;
-- Deteksi rantai blocking di database ini
SELECT a.query AS query_terblokir,
pg_blocking_pids(a.pid) AS pid_pemblokir
FROM pg_stat_activity a
WHERE cardinality(pg_blocking_pids(a.pid)) > 0
AND a.datname = current_database();Output yang Dihasilkan:
pemblokir
-----------
{}
(1 row)
locktype | mode | granted
------------+-----------------+---------
relation | AccessShareLock | t
virtualxid | ExclusiveLock | t
(2 rows)
query_terblokir | pid_pemblokir
-----------------+---------------
(0 rows)Tiga hasil di file itu saling melengkapi:
pg_blocking_pidspada diri sendiri mengembalikan array kosong{}— session ini tidak menunggu siapa pun, sehat.pg_locksmilik sendiri memperlihatkan dua lock:relationdengan modeAccessShareLockmuncul karena query ini sendiri sedang membaca tablepg_locks, sedangkanvirtualxiddenganExclusiveLockadalah tanda pengenal internal yang selalu dipegang backend yang sedang bekerja. Tablepg_locksinilah sumber datapg_blocking_pids; keduanya bisa di-join langsung lewat kolompidbila kita perlu melihat mode lock yang bertabrakan, bukan sekadar siapa menahan siapa.- Query rantai blocking mencari session yang pemblokirnya tidak kosong — hasilnya nol baris karena tidak ada antrean saat file ini berjalan. Di server macet, query ini menjadi senjata utama: kolom
query_terblokirmenunjukkan korban, dan arraypid_pemblokirmenunjuk pelakunya untuk kemudian diperiksa dipg_stat_activity.
Seluruh aktivitas yang kita amati sejauh ini — koneksi, state, wait event, lock — disimpan server di table-table sistem yang bisa kita query langsung. Keluarga table itulah pokok bahasan chapter berikutnya: System Catalog, tempat metadata seluruh objek database hidup sebagai data biasa.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
antrian-lock.sql
Unduh berkas-- Tidak ada yang memblokir session ini
SELECT pg_blocking_pids(pg_backend_pid()) AS pemblokir;
-- Lock yang sedang dipegang session ini
SELECT locktype, mode, granted
FROM pg_locks
WHERE pid = pg_backend_pid()
ORDER BY locktype, mode;
-- Deteksi rantai blocking di database ini
SELECT a.query AS query_terblokir,
pg_blocking_pids(a.pid) AS pid_pemblokir
FROM pg_stat_activity a
WHERE cardinality(pg_blocking_pids(a.pid)) > 0
AND a.datname = current_database();
proses-server.sql
Unduh berkas-- Proses latar server yang bukan koneksi klien
SELECT backend_type, count(*)
FROM pg_stat_activity
WHERE backend_type <> 'client backend'
GROUP BY backend_type
ORDER BY backend_type;
query-berjalan.sql
Unduh berkas-- Cari query yang sedang berjalan di database ini, selain milik kita
SELECT count(*) AS query_aktif_lain
FROM pg_stat_activity
WHERE state = 'active'
AND datname = current_database()
AND pid <> pg_backend_pid();
tur-aktivitas.sql
Unduh berkasSELECT usename, datname, state, backend_type, wait_event_type, wait_event, query FROM pg_stat_activity WHERE pid = pg_backend_pid();