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:

text
         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_typeTugas
checkpointerMenulis halaman kotor ke data directory pada setiap checkpoint
background writerMembuang halaman kotor sedikit demi sedikit agar checkpoint tidak menumpuk
walwriterMenulis dan mem-flush log WAL
autovacuum launcherMengatur turunan autovacuum worker yang membersihkan tabel secara otomatis
logical replication launcherMenyiapkan 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:

KolomIsiCatatan
pidPID proses sistem operasiBerbeda setiap koneksi; kunci untuk pg_terminate_backend
usenameNama role pemilik koneksiBukan user sistem operasi
datnameDatabase tempat koneksi bekerjaNULL untuk proses latar
stateactive, idle, idle in transactionDibahas di chapter Connection Management
wait_event_typeKategori yang sedang ditungguNULL bila tidak menunggu apa pun
wait_eventNama spesifik peristiwa yang ditungguNULL bersama wait_event_type
queryTeks query terakhir atau yang berjalanTetap terisi meski state idle
query_start, state_changeWaktu mulai query dan pergantian stateBerubah terus — dasar alerting durasi
backend_typeJenis prosesclient 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:

text
 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_typeContoh wait_eventArti
Lockrelation, transactionidMenunggu lock table atau baris milik session lain
IODataFileRead, WALWriteMenunggu baca/tulis ke disk
LWLockWALWriteLock, BufferContentMenunggu lightweight lock struktur memori internal
BufferPinBufferPinMenunggu halaman buffer bisa diakses
ClientClientReadMenunggu klien mengirim perintah berikutnya
ActivityWaiting for main wait eventProses latar menunggu jadwal kerjanya
TimeoutPgSleepSengaja menunggu (misalnya pg_sleep)
IPCParallelFinishMenunggu 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:

text
 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:

text
 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:

  1. pg_blocking_pids pada diri sendiri mengembalikan array kosong {} — session ini tidak menunggu siapa pun, sehat.
  2. pg_locks milik sendiri memperlihatkan dua lock: relation dengan mode AccessShareLock muncul karena query ini sendiri sedang membaca table pg_locks, sedangkan virtualxid dengan ExclusiveLock adalah tanda pengenal internal yang selalu dipegang backend yang sedang bekerja. Table pg_locks inilah sumber data pg_blocking_pids; keduanya bisa di-join langsung lewat kolom pid bila kita perlu melihat mode lock yang bertabrakan, bukan sekadar siapa menahan siapa.
  3. 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_terblokir menunjukkan korban, dan array pid_pemblokir menunjuk pelakunya untuk kemudian diperiksa di pg_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
sql
-- 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
sql
-- 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
sql
-- 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 berkas
sql
SELECT usename, datname, state, backend_type, wait_event_type, wait_event, query FROM pg_stat_activity WHERE pid = pg_backend_pid();