Autovacuum agresif pada PostgreSQL

42

Saya mencoba membuat PostgreSQL untuk secara otomatis mengosongkan database saya. Saat ini saya mengkonfigurasi vakum otomatis sebagai berikut:

  • autovacuum_vacuum_cost_delay = 0 # Matikan vakum berbasis biaya
  • autovacuum_vacuum_cost_limit = 10000 #Max value
  • autovacuum_vacuum_threshold = 50 # Nilai kerusakan
  • autovacuum_vacuum_scale_factor = 0.2 # Nilai kerusakan

Saya perhatikan bahwa vakum otomatis hanya menendang ketika basis data tidak di bawah beban, jadi saya masuk ke situasi di mana terdapat lebih banyak tupel mati daripada tupel hidup. Lihat screenshot yang terlampir sebagai contoh. Salah satu tabel memiliki 23 tupel hidup tetapi 16845 tupel mati menunggu ruang hampa. Itu gila!

Banyak Tuples Mati

Vakum otomatis dimulai ketika uji coba selesai dan server basis data idle, yang bukan itu yang saya inginkan karena saya ingin pengosongan otomatis dilakukan setiap kali jumlah tupel mati melebihi 20% tupel hidup + 50, karena basis data telah dikonfigurasi Vakum otomatis ketika server idle tidak berguna bagi saya, karena server produksi diharapkan mencapai pembaruan 1000 detik / detik untuk periode yang berkelanjutan, itulah sebabnya saya perlu vakum otomatis untuk menjalankan meskipun server sedang memuat.

Apakah ada sesuatu yang saya lewatkan? Bagaimana cara memaksa vakum otomatis untuk menjalankan saat server berada di bawah beban berat?

Memperbarui

Mungkinkah ini masalah penguncian? Tabel yang dimaksud adalah tabel ringkasan yang diisi melalui pemicu setelah insert. Tabel-tabel ini dikunci dalam mode SHARE ROW EXCLUSIVE untuk mencegah penulisan bersamaan ke baris yang sama.

CadentOrange
sumber

Jawaban:

40

Eelke hampir pasti benar bahwa penguncian Anda memblokir autovacuum. Autovacuum dirancang untuk memberi jalan bagi aktivitas pengguna, dengan sengaja. Jika tabel itu dikunci, autovacuum tidak dapat menyedotnya.

Namun, untuk anak cucu, saya ingin memberikan contoh pengaturan untuk autovacuum hiper-agresif, karena pengaturan yang Anda berikan tidak cukup melakukannya. Perhatikan bahwa membuat autovacuum lebih agresif tidak akan menyelesaikan masalah Anda. Juga perhatikan bahwa pengaturan autovacuum default didasarkan pada menjalankan lebih dari 200 uji coba menggunakan DBT2 mencari kombinasi pengaturan yang optimal, sehingga standarnya harus dianggap baik kecuali Anda memiliki alasan kuat untuk berpikir sebaliknya, atau kecuali database Anda secara signifikan di luar arus utama untuk basis data OLTP (mis. basis data kecil yang mendapat pembaruan 10 ribu per detik, atau gudang data 3TB).

Pertama, nyalakan pencatatan sehingga Anda dapat memeriksa apakah autovacuum melakukan apa yang Anda pikirkan:

log_autovacuum_min_duration = 0

Kemudian mari kita buat lebih banyak pekerja autovac dan minta mereka memeriksa tabel lebih sering:

autovacuum_max_workers = 6
autovacuum_naptime = 15s

Mari kita turunkan ambang batas untuk vakum otomatis dan analisis otomatis untuk memicu lebih cepat:

autovacuum_vacuum_threshold = 25
autovacuum_vacuum_scale_factor = 0.1

autovacuum_analyze_threshold = 10
autovacuum_analyze_scale_factor = 0.05 

Maka mari kita buat autovacuum lebih mudah, sehingga lebih cepat selesai, tetapi dengan biaya memiliki dampak yang lebih besar pada aktivitas pengguna secara bersamaan:

autovacuum_vacuum_cost_delay = 10ms
autovacuum_vacuum_cost_limit = 1000

Ada program lengkap Anda untuk autovacuum yang umumnya agresif, yang mungkin sesuai untuk database kecil yang mendapatkan tingkat pembaruan yang sangat tinggi, tetapi mungkin terlalu besar dampaknya pada aktivitas pengguna secara bersamaan.

Juga, perhatikan bahwa parameter autovacuum dapat disesuaikan per tabel , yang hampir selalu merupakan jawaban yang lebih baik karena perlu menyesuaikan perilaku autovacuum.

Namun, sekali lagi, tidak mungkin untuk mengatasi masalah Anda yang sebenarnya.

Josh Berkus
sumber
36

Hanya untuk melihat tabel mana yang memenuhi syarat untuk autovacuum sama sekali, pertanyaan berikut dapat digunakan (berdasarkan http://www.postgresql.org/docs/current/static/routine-vacuuming.html ). Namun perhatikan, bahwa kueri tidak mencari pengaturan khusus tabel:

 SELECT psut.relname,
     to_char(psut.last_vacuum, 'YYYY-MM-DD HH24:MI') as last_vacuum,
     to_char(psut.last_autovacuum, 'YYYY-MM-DD HH24:MI') as last_autovacuum,
     to_char(pg_class.reltuples, '9G999G999G999') AS n_tup,
     to_char(psut.n_dead_tup, '9G999G999G999') AS dead_tup,
     to_char(CAST(current_setting('autovacuum_vacuum_threshold') AS bigint)
         + (CAST(current_setting('autovacuum_vacuum_scale_factor') AS numeric)
            * pg_class.reltuples), '9G999G999G999') AS av_threshold,
     CASE
         WHEN CAST(current_setting('autovacuum_vacuum_threshold') AS bigint)
             + (CAST(current_setting('autovacuum_vacuum_scale_factor') AS numeric)
                * pg_class.reltuples) < psut.n_dead_tup
         THEN '*'
         ELSE ''
     END AS expect_av
 FROM pg_stat_user_tables psut
     JOIN pg_class on psut.relid = pg_class.oid
 ORDER BY 1;
pygrac
sumber
11

Ya itu adalah masalah penguncian. Menurut halaman ini (tidak penuh) VACUUM membutuhkan akses SHARE UPDATE EXCLUSIVE yang diblokir oleh level kunci yang Anda gunakan.

Anda yakin perlu kunci ini? PostgreSQL adalah ACID compliant sehingga penulisan bersamaan dalam banyak kasus bukan masalah karena PostgreSQL akan membatalkan salah satu transaksi jika pelanggaran serialisasi akan terjadi.

Anda juga bisa mengunci baris dengan menggunakan SELECT FOR UPDATE untuk mengunci baris, bukan seluruh tabel.

Alternatif lain tanpa penguncian akan menggunakan tingkat isolasi transaksi serializable. Namun ini dapat mempengaruhi kinerja transaksi lain dan Anda harus siap untuk kegagalan serialisasi yang lebih banyak.

Eelke
sumber
Ini karena penguncian tabel ringkasan, karena ini dikunci menggunakan MODE EKSKLUSIF SHARE ROW. Menulis bersamaan tanpa kunci mungkin berhasil, tetapi mereka pasti akan berakhir dengan nilai yang salah. Bayangkan saya mempertahankan hitungan N baris dari tipe X. Jika saya secara bersamaan memasukkan 2 baris tipe X, tanpa mengunci saya akan berakhir dengan N +1 di tabel ringkasan saya alih-alih N + 2. Solusi yang saya adopsi adalah memiliki pekerjaan cron yang secara manual mengosongkan tabel ringkasan di database saya. Ini bekerja dengan baik dan tampaknya menjadi pendekatan yang disarankan, tetapi rasanya terlalu seperti hack bagi saya.
CadentOrange
6

Meningkatkan jumlah proses autovacuum dan mengurangi naptime mungkin akan membantu. Berikut ini adalah konfigurasi untuk PostgreSQL 9.1 yang saya gunakan pada server yang menyimpan informasi cadangan dan sebagai hasilnya mendapat banyak aktivitas memasukkan.

http://www.postgresql.org/docs/current/static/runtime-config-autovacuum.html

autovacuum_max_workers = 6              # max number of autovacuum subprocesses
autovacuum_naptime = 10         # time between autovacuum runs
autovacuum_vacuum_cost_delay = 20ms     # default vacuum cost delay for

Saya juga akan mencoba menurunkan cost_delayagar penyedotan debu menjadi lebih agresif.

Saya juga dapat menguji autovacuuming dengan menggunakan pgbench.

http://wiki.postgresql.org/wiki/Pgbenchtesting

Contoh pertentangan tinggi:

Buat database bench_replication

pgbench -i -p 5433 bench_replication

Jalankan pgbench

pgbench -U postgres -p 5432 -c 64 -j 4 -T 600 bench_replication

Periksa status autovacuuming

psql
>\connect bench_replicaiton
bench_replication=# select schemaname, relname, last_autovacuum from pg_stat_user_tables;
 schemaname |     relname      |        last_autovacuum        
------------+------------------+-------------------------------
 public     | pgbench_branches | 2012-07-18 18:15:34.494932+02
 public     | pgbench_history  | 
 public     | pgbench_tellers  | 2012-07-18 18:14:06.526437+02
 public     | pgbench_accounts | 
Craig Efrein
sumber
6

Skrip "kualifikasi untuk autovacuum" yang ada sangat berguna, tetapi (sebagaimana dinyatakan dengan benar) tidak ada opsi khusus tabel. Ini adalah versi modifikasi yang mempertimbangkan opsi-opsi itu:

WITH rel_set AS
(
    SELECT
        oid,
        CASE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_threshold=', 2), ',', 1)
            WHEN '' THEN NULL
        ELSE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_threshold=', 2), ',', 1)::BIGINT
        END AS rel_av_vac_threshold,
        CASE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_scale_factor=', 2), ',', 1)
            WHEN '' THEN NULL
        ELSE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_scale_factor=', 2), ',', 1)::NUMERIC
        END AS rel_av_vac_scale_factor
    FROM pg_class
) 
SELECT
    PSUT.relname,
    to_char(PSUT.last_vacuum, 'YYYY-MM-DD HH24:MI')     AS last_vacuum,
    to_char(PSUT.last_autovacuum, 'YYYY-MM-DD HH24:MI') AS last_autovacuum,
    to_char(C.reltuples, '9G999G999G999')               AS n_tup,
    to_char(PSUT.n_dead_tup, '9G999G999G999')           AS dead_tup,
    to_char(coalesce(RS.rel_av_vac_threshold, current_setting('autovacuum_vacuum_threshold')::BIGINT) + coalesce(RS.rel_av_vac_scale_factor, current_setting('autovacuum_vacuum_scale_factor')::NUMERIC) * C.reltuples, '9G999G999G999') AS av_threshold,
    CASE
        WHEN (coalesce(RS.rel_av_vac_threshold, current_setting('autovacuum_vacuum_threshold')::BIGINT) + coalesce(RS.rel_av_vac_scale_factor, current_setting('autovacuum_vacuum_scale_factor')::NUMERIC) * C.reltuples) < PSUT.n_dead_tup
        THEN '*'
    ELSE ''
    END AS expect_av
FROM
    pg_stat_user_tables PSUT
    JOIN pg_class C
        ON PSUT.relid = C.oid
    JOIN rel_set RS
        ON PSUT.relid = RS.oid
ORDER BY C.reltuples DESC;
Vadim Zingertal
sumber