Tunas Akara
Kembali ke Blog

Perawatan Rutin Postgres Melumpuhkan CircleCI Dua Kali

Pembersihan rutin Postgres memenuhi disk log transaksi CircleCI pada 6 dan 9 Okt 2026 sehingga job macet berjam-jam. Ini yang perlu dipantau di database Anda.

oleh Rayhan9 menit baca

Untuk Tim Teknis

postgresqloutagevacuumwaldatabase-operations
Perawatan Rutin Postgres Melumpuhkan CircleCI Dua Kali
Daftar isi

Pada 6 dan 9 Oktober 2026, penjadwal job CircleCI berhenti karena proses pembersihan rutin PostgreSQL menulis log transaksi lebih cepat daripada log itu bisa diarsipkan. Disk tempat log itu akhirnya penuh. Tidak ada bug, tidak ada serangan. Kalau bisnis Anda berjalan di atas Postgres, pantau seberapa cepat disk log terisi dan apakah pengarsipan masih mengejar.

Di bawah ini saya uraikan isi dua laporan insiden CircleCI, kenapa pekerjaan bersih-bersih bisa menghentikan database, dan pemeriksaan apa yang menangkap pola serupa di sistem yang jauh lebih kecil.

Apa yang terjadi pada 6 Oktober?

Menurut laporan pasca-insiden CircleCI untuk 6 Oktober, sumber masalahnya ada di database layanan orkestrasi, yaitu bagian yang menentukan job mana yang jalan berikutnya. Mesin-mesin build sebenarnya sehat. Mereka hanya tidak mendapat perintah apa pun.

Waktu (UTC)Waktu (WIB)Kejadian
~12.4019.40Pembersihan di tabel-tabel terbesar mulai menulis log lebih cepat daripada pengarsipan
13.3020.30Orkestrasi melambat; keterlambatan mulai muncul putus-sambung
14.5421.54Insiden dinyatakan resmi
15.4322.43Volume log hampir penuh; tim memutuskan memindahkan log ke volume utama
15.47–16.3822.47–23.38Database mati selama pemindahan; tidak ada workflow atau job yang mulai
18.1901.19 (7 Okt)Satu pengaturan penyimpanan diubah; pemrosesan pulih dari setengah kecepatan
21.1904.19 (7 Okt)Insiden selesai

Sekitar 3.500 job gagal dimulai, dan pelanggan diminta menjalankannya ulang. Laporannya cukup terus terang soal tanda-tanda awal. Sejak pagi sudah ada peringatan singkat yang muncul lalu hilang sendiri, dan tim CircleCI menulis bahwa mereka seharusnya lebih cepat membaca gabungan peringatan itu.

Memindahkan log sebenarnya pilihan yang paling ringan risikonya. Kalau volume log sampai penuh, Postgres berhenti menerima penulisan data. Pemindahan itu memakan 50 menit, sedangkan membiarkannya berarti database hanya bisa dibaca tanpa tahu kapan pulih.

Kenapa terulang tiga hari kemudian?

Laporan 9 Oktober menyangkut database lain, milik Temporal. Temporal adalah mesin alur kerja yang mencatat sampai tahap mana setiap proses panjang sudah berjalan. Riwayat pekerjaan yang sudah selesai dihapus setelah tiga hari.

Di sinilah sambungannya. Lalu lintas berat pada 6 Oktober jatuh tempo untuk dihapus pada 9 Oktober, dan penghapusan massal itu kembali membanjiri log transaksi.

Angkanya layak dibaca pelan-pelan:

  • 08.00 UTC (15.00 WIB): ruang kosong volume log mulai turun dari 0,8 TB.
  • 10.10 UTC: tersisa 0,1 TB. Peringatan baru berbunyi sekitar 20 menit kemudian.
  • 11.15 UTC (18.15 WIB): volume penuh, Postgres beralih ke mode baca saja, dan seluruh pemrosesan Temporal berhenti.
  • Volume itu berukuran tetap 1 TB dengan 3.000 IOPS (operasi baca-tulis disk per detik). Menurut CircleCI, batas ini terkunci dan tidak bisa dinaikkan.

Setelah restart untuk memindahkan log, database hidup lagi pukul 12.59 UTC. Masalahnya belum selesai. Pembersihan baris mati tertinggal sampai puluhan juta baris, sehingga setiap query harus melewati tumpukan sampah lebih dulu. Pembersihan manual yang dijalankan tim justru berebut CPU dengan query sungguhan, lalu dibatalkan.

Di saat yang sama, sistem-sistem di hulu mengulang pekerjaan yang gagal secara serentak. Banjir pengulangan ini (retry storm) membuat database terus kewalahan. Baru setelah semua pekerjaan masuk dan pengulangan dihentikan pukul 20.48 UTC, lalu dibuka lagi sedikit demi sedikit, insiden teratasi pada 23.47 UTC (06.47 WIB, 10 Oktober). Total gangguannya sekitar 12,5 jam.

Kenapa pembersihan bisa menghentikan database?

Postgres tidak pernah menimpa baris yang sudah ada. Setiap update menulis versi baru dan meninggalkan versi lama. Mirip buku kas yang koreksinya ditulis di baris baru, sementara baris lama dicoret. Proses bernama VACUUM nanti membuang baris-baris coretan itu. Versi otomatisnya, autovacuum, berjalan di latar belakang.

Ada satu tugas lain yang jarang terlihat. Setiap transaksi diberi nomor dari penghitung 32-bit, dan penghitung ini akan berputar kembali ke awal kira-kira setiap dua miliar transaksi. Sebelum itu terjadi, baris lama harus "dibekukan", yaitu ditandai permanen terlihat, supaya tidak tiba-tiba dianggap berasal dari masa depan.

Dokumentasi PostgreSQL 18 tentang vacuum rutin menetapkan pemicu bawaannya, autovacuum_freeze_max_age, di 200 juta transaksi. Lewat dari umur itu, Postgres memaksa autovacuum anti-wraparound (pembekuan wajib sebelum penghitung berputar) pada tabel itu, bahkan kalau autovacuum dimatikan. Kalau masih tertinggal, peringatan muncul saat sisa 40 juta transaksi. Di bawah 3 juta, Postgres menolak semua perintah yang butuh nomor transaksi baru.

Membekukan tabel raksasa berarti menulis ulang banyak halaman data. Setiap perubahan lebih dulu dicatat di WAL (write-ahead log), buku harian yang ditulis Postgres sebelum menyentuh data agar bisa pulih setelah mati mendadak. Kalau pengarsipan WAL aktif, dan di sistem dengan cadangan yang serius memang harus aktif, dokumentasi konfigurasi WAL menyebut potongan log lama tidak boleh dihapus sebelum diarsipkan. Saat pengarsipan kalah cepat, berkasnya menumpuk di pg_wal.

Memuat diagram…

Perhatikan bahwa tidak ada bug di rantai ini. Setiap langkah adalah Postgres yang bekerja dengan benar. Kegagalannya ada di selisih antara kecepatan log ditulis dan kecepatan log keluar.

Apakah sistem kecil juga bisa kena?

Bisa, biasanya dengan sebab yang lebih sepele. PMS hotel, backend kasir, atau Supabase yang di-host sendiri tidak menghasilkan volume sebesar CircleCI. Namun disk-nya lebih kecil, dan malam hari tidak ada yang mengawasi. Empat sebab ini yang paling sering saya temui:

PenyebabAwal mulanyaKenapa tidak ada yang sadar
Pengarsipan gagalKredensial penyimpanan kedaluwarsa, nama bucket berubah, atau server arsip kehabisan ruangPostgres tetap berjalan normal, sementara pg_wal membengkak tiap jam
Replication slot yang terlupakan, yaitu penanda yang disimpan Postgres untuk tiap salinan dataReplika, alat analitik, atau penyalin perubahan data ke sistem lain sudah tidak dipakai, tetapi penandanya masih ada, jadi Postgres menyimpan semua log yang mungkin masih dibutuhkanmax_slot_wal_keep_size bawaannya -1, artinya penanda boleh menahan WAL tanpa batas
Penghapusan massal data lamaJob bulanan menghapus log atau riwayat setahun dalam satu perintahDianggap "cuma bersih-bersih", jadi tidak pernah dihitung kapasitasnya
Tabel yang lama tidak di-vacuumTransaksi yang terbuka berminggu-minggu atau sesi menganggur menghalangi pembersihanPembekuan wajib akhirnya jalan tepat di hari tersibuk

Baris ketiga persis pemicu 9 Oktober. Aturan retensi data itu bagus. Hanya saja, menghapus jutaan baris sekaligus menulis log sebanyak saat baris-baris itu dulu dimasukkan.

Apa yang perlu dipantau?

Peringatan disk di 90% datang terlambat. Volume CircleCI berubah dari 0,8 TB kosong menjadi penuh dalam tiga jam lebih sedikit. Yang perlu diukur adalah lajunya. Bagi ruang kosong dengan kecepatan berkurangnya, maka Anda tahu berapa jam lagi disk penuh. Peringatan "kurang dari enam jam lagi" membangunkan orang selagi masih ada ruang untuk bertindak.

Empat query ini sudah mencakup sebagian besar kebutuhan:

-- 1. Apakah pengarsipan gagal? failed_count yang terus naik atau
--    last_archived_time yang sudah lama berarti WAL menumpuk.
SELECT archived_count, last_archived_time,
       failed_count, last_failed_time
FROM pg_stat_archiver;

-- 2. Replication slot mana yang menahan WAL, dan berapa banyak?
SELECT slot_name, active,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC;

-- 3. Seberapa dekat tiap database ke pembekuan wajib? Bandingkan
--    dengan autovacuum_freeze_max_age (bawaan 200 juta).
SELECT datname, age(datfrozenxid) AS xid_age
FROM pg_database
ORDER BY xid_age DESC;

-- 4. Berapa besar WAL di disk saat ini?
SELECT pg_size_pretty(sum(size)) FROM pg_ls_waldir();

Masukkan hasilnya ke sistem pemantauan yang sudah Anda punya. Cara menentukan ambang dan mengarahkan peringatan ke orang yang benar-benar bertindak saya bahas di pemantauan dan peringatan untuk sistem kecil.

Bagaimana merancang supaya tidak terjadi?

Beri disk log ruang untuk tumbuh, atau satukan dengan volume data. Perbaikan permanen CircleCI untuk database Temporal adalah berhenti memakai volume log berukuran tetap. Disk log terpisah tetap boleh, asalkan bisa diperbesar tanpa mematikan server dan cukup lega untuk menampung pembersihan penuh tabel terbesar Anda.

Batasi berapa banyak WAL yang boleh ditahan replication slot. Isi max_slot_wal_keep_size dengan angka yang masih sanggup ditampung disk. Replika yang tertinggal terlalu jauh memang harus dibangun ulang, tetapi itu jauh lebih murah daripada database utama berubah menjadi baca saja.

Hapus data lama sedikit demi sedikit, atau buang per partisi. Partisi memecah satu tabel menjadi beberapa laci, misalnya satu laci per bulan. Data sebulan bisa dibuang dengan satu perintah cepat, tanpa menghapus baris satu per satu. Kalau partisi belum memungkinkan, hapus beberapa ribu baris per putaran dengan jeda, dan jangan jadwalkan sehari setelah lonjakan lalu lintas.

Buat klien menunggu, jangan menggedor. Dua laporan CircleCI sama-sama mencantumkan perilaku pengulangan sebagai pekerjaan rumah. Ada sistem yang menyerah lalu menggagalkan job, ada pula yang terus mengulang sampai database tertimbun. Pengulangan butuh jeda yang makin panjang dan batas maksimal. Database yang baru pulih juga perlu diberi pekerjaan secara bertahap.

Uji bahwa arsipnya benar-benar bisa dipakai. Arsip WAL yang belum pernah dipulihkan baru sebatas harapan, belum cadangan. Latihan pemulihan di memverifikasi cadangan Postgres sekaligus membuktikan arsipnya lengkap.

Apa yang tidak boleh dilakukan saat disk log penuh?

Jangan pernah menghapus berkas di pg_wal secara manual. Postgres membutuhkannya untuk pulih, dan salah hapus satu potongan bisa membuat database tidak mau menyala. Urutan yang aman kira-kira begini:

  1. Cari apa yang menahan WAL: pengarsipan yang gagal, replication slot basi, atau vacuum yang sedang berjalan.
  2. Perbaiki pengarsipannya, atau hapus slot yang memang sudah tidak dipakai.
  3. Perbesar disk kalau platform Anda mengizinkan tanpa mematikan server.
  4. Baru setelah itu pertimbangkan memindahkan WAL ke volume lain, yang seperti di kasus CircleCI berarti ada waktu mati.

Yang perlu dicek minggu ini

  • Jalankan empat query di atas pada setiap Postgres produksi yang Anda andalkan.
  • Daftar semua replication slot dan pastikan pemiliknya jelas. Hapus yang sudah yatim.
  • Isi max_slot_wal_keep_size dan pasang peringatan "berapa jam lagi penuh" untuk disk WAL.
  • Tinjau job penghapusan terjadwal: berapa baris yang disentuh dan kapan jalannya.
  • Tanyakan ke vendor atau penyedia hosting, siapa yang menerima peringatan gagal arsip di malam hari.

Database CircleCI bekerja persis sesuai rancangannya, tetapi bisnisnya tetap kehilangan berjam-jam, dua kali dalam seminggu. Perawatan rutin juga termasuk beban produksi, jadi ukuran disk, peringatan, dan aturan pengulangan perlu disiapkan untuk menampungnya.

Artikel Terkait

Perlu bantuan menerapkan solusi ini?

Konsultasi Perencanaan Sistem

Petakan kebutuhan, integrasi, dan risiko sebelum membangun atau memperluas sistem. Dapatkan rancangan serta prioritas yang bisa dibahas bersama tim.

Lihat layanan terkait

Ceritakan kendala operasional Anda agar kita dapat membahas langkah yang sesuai.

Bagikan artikel
Diskusi Awal Gratis