Postgres: batasan mengasapi, pg_repack dan ditangguhkan

Postgres: batasan mengasapi, pg_repack dan ditangguhkan

Efek mengasapi pada tabel dan indeks sudah diketahui secara luas dan tidak hanya ada di Postgres. Ada cara untuk mengatasinya, seperti VACUUM FULL atau CLUSTER, tetapi mereka mengunci tabel selama pengoperasian dan oleh karena itu tidak selalu dapat digunakan.

Artikel ini akan berisi sedikit teori tentang bagaimana bloat terjadi, bagaimana Anda dapat melawannya, tentang batasan yang ditangguhkan dan masalah yang ditimbulkannya pada penggunaan ekstensi pg_repack.

Artikel ini ditulis berdasarkan pidato ku di PgConf.Russia 2020.

Mengapa kembung terjadi?

Postgres didasarkan pada model multi-versi (MVCC). Esensinya adalah bahwa setiap baris dalam tabel dapat memiliki beberapa versi, sedangkan transaksi tidak melihat lebih dari satu versi ini, tetapi tidak harus sama. Hal ini memungkinkan beberapa transaksi bekerja secara bersamaan dan hampir tidak berdampak satu sama lain.

Jelas sekali, semua versi ini perlu disimpan. Postgres bekerja dengan memori halaman demi halaman dan satu halaman adalah jumlah minimum data yang dapat dibaca dari disk atau ditulis. Mari kita lihat contoh kecil untuk memahami bagaimana hal ini terjadi.

Katakanlah kita memiliki tabel yang telah kita tambahkan beberapa catatan. Data baru telah muncul di halaman pertama file tempat tabel disimpan. Ini adalah versi aktif dari baris yang tersedia untuk transaksi lain setelah penerapan (untuk kesederhanaan, kita akan berasumsi bahwa tingkat isolasi adalah Read Commited).

Postgres: batasan mengasapi, pg_repack dan ditangguhkan

Kami kemudian memperbarui salah satu entri, sehingga menandai versi lama sebagai tidak relevan lagi.

Postgres: batasan mengasapi, pg_repack dan ditangguhkan

Selangkah demi selangkah, memperbarui dan menghapus versi baris, kami mendapatkan halaman di mana sekitar setengah dari datanya adalah "sampah". Data ini tidak terlihat pada transaksi apa pun.

Postgres: batasan mengasapi, pg_repack dan ditangguhkan

Postgres memiliki mekanisme KEKOSONGAN, yang membersihkan versi lama dan memberikan ruang bagi data baru. Namun jika tidak dikonfigurasi dengan cukup agresif atau sibuk bekerja di tabel lain, maka “data sampah” tetap ada, dan kita harus menggunakan halaman tambahan untuk data baru.

Jadi dalam contoh kita, suatu saat tabel akan terdiri dari empat halaman, namun hanya setengahnya yang berisi data langsung. Akibatnya, saat mengakses tabel, kita akan membaca lebih banyak data dari yang diperlukan.

Postgres: batasan mengasapi, pg_repack dan ditangguhkan

Meskipun VACUUM sekarang menghapus semua versi baris yang tidak relevan, situasinya tidak akan membaik secara drastis. Kami akan memiliki ruang kosong di halaman atau bahkan seluruh halaman untuk baris baru, namun kami masih akan membaca lebih banyak data daripada yang diperlukan.
Omong-omong, jika halaman yang benar-benar kosong (yang kedua dalam contoh kita) ada di akhir file, maka VACUUM akan dapat memangkasnya. Tapi sekarang dia berada di tengah, jadi tidak ada yang bisa dilakukan padanya.

Postgres: batasan mengasapi, pg_repack dan ditangguhkan

Ketika jumlah halaman yang kosong atau sangat jarang menjadi banyak, yang disebut penggembungan, hal ini mulai memengaruhi kinerja.

Semua yang dijelaskan di atas adalah mekanisme terjadinya bloat pada tabel. Dalam indeks, hal ini terjadi dengan cara yang hampir sama.

Apakah saya kembung?

Ada beberapa cara untuk mengetahui apakah Anda mengalami kembung. Ide pertama adalah menggunakan statistik internal Postgres, yang berisi informasi perkiraan tentang jumlah baris dalam tabel, jumlah baris "langsung", dll. Anda dapat menemukan banyak variasi skrip yang sudah jadi di Internet. Kami mengambil sebagai dasar naskah dari Pakar PostgreSQL, yang dapat mengevaluasi tabel bloat bersama dengan indeks toast dan bloat btree. Berdasarkan pengalaman kami, kesalahannya adalah 10-20%.

Cara lainnya adalah dengan menggunakan ekstensi pgstattuple, yang memungkinkan Anda melihat ke dalam halaman dan mendapatkan perkiraan dan nilai pengasapan yang tepat. Namun dalam kasus kedua, Anda harus memindai seluruh tabel.

Kami menganggap nilai bloat yang kecil, hingga 20%, dapat diterima. Ini dapat dianggap sebagai analog dari fillfactor untuk meja и indeks. Pada 50% ke atas, masalah kinerja mungkin dimulai.

Cara untuk mengatasi kembung

Postgres memiliki beberapa cara untuk mengatasi kembung, tetapi tidak selalu cocok untuk semua orang.

Konfigurasikan AUTOVACUUM agar tidak terjadi bloat. Atau lebih tepatnya, menjaganya pada tingkat yang dapat Anda terima. Hal ini tampak seperti nasihat “kapten”, namun kenyataannya hal ini tidak selalu mudah untuk dicapai. Misalnya, Anda memiliki pengembangan aktif dengan perubahan rutin pada skema data, atau semacam migrasi data sedang berlangsung. Akibatnya, profil pemuatan Anda mungkin sering berubah dan biasanya bervariasi dari satu tabel ke tabel lainnya. Ini berarti Anda harus terus bekerja sedikit ke depan dan menyesuaikan AUTOVACUUM dengan perubahan profil setiap tabel. Namun jelas hal ini tidak mudah untuk dilakukan.

Alasan umum lainnya mengapa AUTOVACUUM tidak dapat mengikuti tabel adalah karena ada transaksi yang berjalan lama sehingga mencegahnya membersihkan data yang tersedia untuk transaksi tersebut. Rekomendasi di sini juga jelas - singkirkan transaksi yang “menggantung” dan minimalkan waktu transaksi aktif. Namun jika beban pada aplikasi Anda adalah gabungan OLAP dan OLTP, maka Anda dapat melakukan banyak pembaruan rutin dan kueri singkat secara bersamaan, serta operasi jangka panjang - misalnya, membuat laporan. Dalam situasi seperti ini, ada baiknya memikirkan untuk menyebarkan beban ke berbagai basis, yang akan memungkinkan penyesuaian yang lebih baik pada masing-masing basis.

Contoh lain - meskipun profilnya homogen, tetapi database berada di bawah beban yang sangat tinggi, maka AUTOVACUUM yang paling agresif pun mungkin tidak dapat mengatasinya, dan kembung akan terjadi. Penskalaan (vertikal atau horizontal) adalah satu-satunya solusi.

Apa yang harus dilakukan jika Anda telah menyiapkan AUTOVACUUM, namun pembengkakan terus bertambah.

Tim VAKUM PENUH membangun kembali isi tabel dan indeks dan hanya menyisakan data yang relevan di dalamnya. Untuk menghilangkan kembung, ini berfungsi dengan sempurna, tetapi selama eksekusinya, kunci eksklusif pada tabel (AccessExclusiveLock) ditangkap, yang tidak memungkinkan mengeksekusi kueri pada tabel ini, bahkan memilih. Jika Anda mampu menghentikan layanan Anda atau sebagian darinya untuk beberapa waktu (dari puluhan menit hingga beberapa jam tergantung pada ukuran database dan perangkat keras Anda), maka opsi ini adalah yang terbaik. Sayangnya, kami tidak punya waktu untuk menjalankan VACUUM FULL selama pemeliharaan terjadwal, jadi cara ini tidak cocok untuk kami.

Tim KLASTER Membangun kembali konten tabel dengan cara yang sama seperti VACUUM FULL, tetapi memungkinkan Anda menentukan indeks yang menurutnya data akan diurutkan secara fisik pada disk (tetapi di masa mendatang urutan tidak dijamin untuk baris baru). Dalam situasi tertentu, ini adalah pengoptimalan yang baik untuk sejumlah kueri - dengan membaca banyak catatan berdasarkan indeks. Kerugian dari perintah ini sama dengan VACUUM FULL - ia mengunci tabel selama pengoperasian.

Tim INDEKS ULANG mirip dengan dua sebelumnya, tetapi membangun kembali indeks tertentu atau semua indeks tabel. Kunci sedikit lebih lemah: ShareLock pada tabel (mencegah modifikasi, tetapi memungkinkan pemilihan) dan AccessExclusiveLock pada indeks yang sedang dibangun kembali (memblokir kueri menggunakan indeks ini). Namun, di Postgres versi ke-12, sebuah parameter muncul SECARA BERSAMAAN, yang memungkinkan Anda membangun kembali indeks tanpa memblokir penambahan, modifikasi, atau penghapusan catatan secara bersamaan.

Di Postgres versi sebelumnya, Anda dapat mencapai hasil yang mirip dengan REINDEX CONCURRENTLY menggunakan BUAT INDEKS SECARA BERSAMAAN. Ini memungkinkan Anda membuat indeks tanpa penguncian ketat (ShareUpdateExclusiveLock, yang tidak mengganggu kueri paralel), lalu mengganti indeks lama dengan yang baru dan menghapus indeks lama. Hal ini memungkinkan Anda menghilangkan penggembungan indeks tanpa mengganggu aplikasi Anda. Penting untuk diingat bahwa ketika membangun kembali indeks, akan ada beban tambahan pada subsistem disk.

Jadi, jika untuk indeks ada cara untuk menghilangkan bloat “on the fly”, maka untuk tabel tidak ada cara lain. Di sinilah berbagai ekstensi eksternal berperan: pg_repack (sebelumnya pg_reorg), pgcompact, pgcompacttable dan lain-lain. Pada artikel ini, saya tidak akan membandingkannya dan hanya akan berbicara tentang pg_repack, yang, setelah beberapa modifikasi, kami gunakan sendiri.

Cara kerja pg_repack

Postgres: batasan mengasapi, pg_repack dan ditangguhkan
Katakanlah kita memiliki tabel yang benar-benar biasa - dengan indeks, batasan, dan, sayangnya, dengan pengasapan. Langkah pertama pg_repack adalah membuat tabel log untuk menyimpan data tentang semua perubahan saat sedang berjalan. Pemicunya akan mereplikasi perubahan ini untuk setiap penyisipan, pembaruan, dan penghapusan. Kemudian dibuat tabel yang strukturnya mirip dengan aslinya, tetapi tanpa indeks dan batasan, agar tidak memperlambat proses penyisipan data.

Selanjutnya, pg_repack mentransfer data dari tabel lama ke tabel baru, secara otomatis memfilter semua baris yang tidak relevan, lalu membuat indeks untuk tabel baru. Selama pelaksanaan semua operasi ini, perubahan terakumulasi dalam tabel log.

Langkah selanjutnya adalah mentransfer perubahan ke tabel baru. Migrasi dilakukan melalui beberapa iterasi, dan ketika terdapat kurang dari 20 entri yang tersisa di tabel log, pg_repack memperoleh kunci yang kuat, memigrasikan data terbaru, dan mengganti tabel lama dengan yang baru di tabel sistem Postgres. Ini adalah satu-satunya waktu yang sangat singkat ketika Anda tidak dapat bekerja dengan meja. Setelah ini, tabel lama dan tabel dengan log akan dihapus dan ruang di sistem file dikosongkan. Prosesnya selesai.

Semuanya terlihat bagus secara teori, tapi apa yang terjadi dalam praktiknya? Kami menguji pg_repack tanpa beban dan sedang memuat, dan memeriksa pengoperasiannya jika terjadi penghentian dini (dengan kata lain, menggunakan Ctrl+C). Semua tes positif.

Kami pergi ke toko kelontong - dan kemudian semuanya tidak berjalan sesuai harapan.

Pancake pertama dijual

Pada cluster pertama kami menerima kesalahan tentang pelanggaran batasan unik:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Batasan ini memiliki nama yang dibuat secara otomatis index_16508 - dibuat oleh pg_repack. Berdasarkan atribut-atribut yang termasuk dalam komposisinya, kami menentukan batasan “kami” yang sesuai dengannya. Masalahnya ternyata ini bukanlah batasan biasa, melainkan batasan yang ditangguhkan (kendala yang ditangguhkan), yaitu verifikasinya dilakukan lebih lambat dari perintah sql, yang menyebabkan konsekuensi yang tidak terduga.

Kendala yang ditangguhkan: mengapa dibutuhkan dan bagaimana cara kerjanya

Sedikit teori tentang pembatasan yang ditangguhkan.
Mari kita perhatikan contoh sederhana: kita memiliki buku referensi tabel mobil dengan dua atribut - nama dan urutan mobil di direktori.
Postgres: batasan mengasapi, pg_repack dan ditangguhkan

create table cars
(
  name text constraint pk_cars primary key,
  ord integer not null constraint uk_cars unique
);



Katakanlah kita perlu menukar mobil pertama dan kedua. Solusi langsungnya adalah memperbarui nilai pertama ke nilai kedua, dan nilai kedua ke nilai pertama:

begin;
  update cars set ord = 2 where name = 'audi';
  update cars set ord = 1 where name = 'bmw';
commit;

Namun saat kami menjalankan kode ini, kami memperkirakan akan terjadi pelanggaran batasan karena urutan nilai dalam tabel bersifat unik:

[23305] ERROR: duplicate key value violates unique constraint “uk_cars”
Detail: Key (ord)=(2) already exists.

Bagaimana saya bisa melakukannya secara berbeda? Opsi satu: menambahkan tambahan nilai pengganti pada order yang dijamin tidak ada di tabel, misalnya “-1”. Dalam pemrograman, ini disebut “menukar nilai dua variabel melalui variabel ketiga”. Satu-satunya kelemahan metode ini adalah pembaruan tambahan.

Opsi dua: Mendesain ulang tabel untuk menggunakan tipe data floating point untuk nilai pesanan, bukan bilangan bulat. Kemudian, ketika nilainya diperbarui dari 1, misalnya, menjadi 2.5, entri pertama akan otomatis “berdiri” di antara entri kedua dan ketiga. Solusi ini berhasil, tetapi ada dua batasan. Pertama, ini tidak akan berfungsi untuk Anda jika nilainya digunakan di suatu tempat di antarmuka. Kedua, bergantung pada keakuratan tipe data, Anda akan memiliki sejumlah kemungkinan penyisipan sebelum menghitung ulang nilai semua catatan.

Opsi ketiga: buat batasan ditangguhkan sehingga hanya dicentang pada saat penerapan:

create table cars
(
  name text constraint pk_cars primary key,
  ord integer not null constraint uk_cars unique deferrable initially deferred
);

Karena logika permintaan awal kami memastikan bahwa semua nilai unik pada saat komit, maka itu akan berhasil.

Contoh yang dibahas di atas, tentu saja, sangat sintetik, namun mengungkapkan gagasannya. Dalam aplikasi kami, kami menggunakan batasan yang ditangguhkan untuk mengimplementasikan logika yang bertanggung jawab untuk menyelesaikan konflik ketika pengguna secara bersamaan bekerja dengan objek widget bersama di papan. Menggunakan batasan seperti itu memungkinkan kita membuat kode aplikasi sedikit lebih sederhana.

Secara umum, bergantung pada jenis batasannya, Postgres memiliki tiga tingkat granularitas untuk memeriksanya: tingkat baris, transaksi, dan ekspresi.
Postgres: batasan mengasapi, pg_repack dan ditangguhkan
Sumber: mohon maaf

CHECK dan NOT NULL selalu dicentang pada level baris, untuk batasan lainnya seperti terlihat pada tabel, terdapat pilihan yang berbeda. Anda dapat membaca lebih lanjut di sini.

Untuk meringkas secara singkat, batasan yang ditangguhkan dalam sejumlah situasi memberikan kode yang lebih mudah dibaca dan perintah yang lebih sedikit. Namun, Anda harus membayarnya dengan mempersulit proses debugging, karena saat kesalahan terjadi dan saat Anda mengetahuinya dipisahkan dalam waktu. Masalah lain yang mungkin terjadi adalah penjadwal tidak selalu dapat menyusun rencana optimal jika permintaan melibatkan batasan yang ditangguhkan.

Peningkatan pg_repack

Kita telah membahas apa itu kendala yang ditangguhkan, namun bagaimana kaitannya dengan masalah kita? Mari kita ingat kesalahan yang kita terima sebelumnya:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Hal ini terjadi ketika data disalin dari tabel log ke tabel baru. Ini terlihat aneh karena... data dalam tabel log dikomit bersama dengan data dalam tabel sumber. Jika mereka memenuhi batasan tabel asli, bagaimana mereka bisa melanggar batasan yang sama di tabel baru?

Ternyata, akar masalahnya terletak pada langkah pg_repack sebelumnya, yang hanya membuat indeks, bukan batasan: tabel lama memiliki batasan unik, dan tabel baru malah membuat indeks unik.

Postgres: batasan mengasapi, pg_repack dan ditangguhkan

Penting untuk dicatat di sini bahwa jika batasannya normal dan tidak ditangguhkan, maka indeks unik yang dibuat akan setara dengan batasan ini, karena Batasan unik di Postgres diterapkan dengan membuat indeks unik. Namun dalam kasus batasan yang ditangguhkan, perilakunya tidak sama, karena indeks tidak dapat ditangguhkan dan selalu diperiksa pada saat perintah sql dijalankan.

Jadi, inti masalahnya terletak pada “penundaan” pemeriksaan: di tabel asli terjadi pada saat komit, dan di tabel baru pada saat perintah sql dijalankan. Ini berarti kita perlu memastikan bahwa pemeriksaan dilakukan dengan cara yang sama dalam kedua kasus: selalu tertunda, atau selalu segera.

Jadi ide apa yang kita punya?

Buat indeks yang mirip dengan ditangguhkan

Ide pertama adalah melakukan kedua pemeriksaan dalam mode langsung. Hal ini mungkin menimbulkan beberapa pembatasan positif palsu, tetapi jika jumlahnya sedikit, hal ini tidak akan memengaruhi pekerjaan pengguna, karena konflik seperti itu adalah situasi normal bagi mereka. Hal ini terjadi, misalnya, ketika dua pengguna mulai mengedit widget yang sama pada saat yang sama, dan klien dari pengguna kedua tidak punya waktu untuk menerima informasi bahwa widget tersebut telah diblokir untuk diedit oleh pengguna pertama. Dalam situasi seperti ini, server menolak pengguna kedua, dan kliennya mengembalikan perubahan dan memblokir widget. Beberapa saat kemudian, ketika pengguna pertama selesai mengedit, pengguna kedua akan menerima informasi bahwa widget tidak lagi diblokir dan akan dapat mengulangi tindakannya.

Postgres: batasan mengasapi, pg_repack dan ditangguhkan

Untuk memastikan bahwa pemeriksaan selalu dalam mode non-ditangguhkan, kami membuat indeks baru yang mirip dengan batasan asli yang ditangguhkan:

CREATE UNIQUE INDEX CONCURRENTLY uk_tablename__immediate ON tablename (id, index);
-- run pg_repack
DROP INDEX CONCURRENTLY uk_tablename__immediate;

Di lingkungan pengujian, kami hanya menerima sedikit kesalahan yang diharapkan. Kesuksesan! Kami menjalankan pg_repack lagi pada produksi dan mendapatkan 5 kesalahan pada cluster pertama dalam satu jam kerja. Ini adalah hasil yang dapat diterima. Namun, pada cluster kedua, jumlah kesalahan meningkat secara signifikan dan kami harus menghentikan pg_repack.

Kenapa ini terjadi? Kemungkinan terjadinya kesalahan bergantung pada berapa banyak pengguna yang bekerja dengan widget yang sama pada waktu yang bersamaan. Rupanya, pada saat itu, terdapat jauh lebih sedikit perubahan kompetitif dengan data yang disimpan di cluster pertama dibandingkan cluster lainnya, yaitu. kami hanya “beruntung”.

Idenya tidak berhasil. Pada saat itu, kami melihat dua solusi lain: menulis ulang kode aplikasi kami untuk menghilangkan batasan yang ditangguhkan, atau “mengajarkan” pg_repack untuk bekerja dengannya. Kami memilih yang kedua.

Ganti indeks di tabel baru dengan batasan yang ditangguhkan dari tabel asli

Tujuan revisi sudah jelas - jika tabel asli memiliki batasan yang ditangguhkan, maka untuk tabel baru Anda perlu membuat batasan seperti itu, dan bukan indeks.

Untuk menguji perubahan kami, kami menulis tes sederhana:

  • tabel dengan batasan yang ditangguhkan dan satu catatan;
  • memasukkan data ke dalam loop yang bertentangan dengan record yang ada;
  • lakukan pembaruan – data tidak lagi konflik;
  • melakukan perubahan.

create table test_table
(
  id serial,
  val int,
  constraint uk_test_table__val unique (val) deferrable initially deferred 
);

INSERT INTO test_table (val) VALUES (0);
FOR i IN 1..10000 LOOP
  BEGIN
    INSERT INTO test_table VALUES (0) RETURNING id INTO v_id;
    UPDATE test_table set val = i where id = v_id;
    COMMIT;
  END;
END LOOP;

Versi asli pg_repack selalu crash pada penyisipan pertama, versi modifikasi berfungsi tanpa kesalahan. Besar.

Kami pergi ke produksi dan sekali lagi mendapatkan kesalahan pada fase yang sama saat menyalin data dari tabel log ke yang baru:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Situasi klasik: semuanya berfungsi di lingkungan pengujian, tetapi tidak dalam produksi?!

APPLY_COUNT dan persimpangan dua batch

Kami mulai menganalisis kode secara harfiah baris demi baris dan menemukan poin penting: data ditransfer dari tabel log ke tabel baru dalam batch, konstanta APPLY_COUNT menunjukkan ukuran batch:

for (;;)
{
num = apply_log(connection, table, APPLY_COUNT);

if (num > MIN_TUPLES_BEFORE_SWITCH)
     continue;  /* there might be still some tuples, repeat. */
...
}

Masalahnya adalah data dari transaksi awal, di mana beberapa operasi berpotensi melanggar batasan, ketika ditransfer, dapat berakhir di persimpangan dua batch - setengah dari perintah akan dilakukan di batch pertama, dan separuh lainnya. di detik. Dan di sini, tergantung pada keberuntungan Anda: jika tim tidak melanggar apa pun di gelombang pertama, maka semuanya baik-baik saja, tetapi jika mereka melanggar, terjadi kesalahan.

APPLY_COUNT sama dengan 1000 catatan, yang menjelaskan mengapa pengujian kami berhasil - pengujian tersebut tidak mencakup kasus "persimpangan batch". Kami menggunakan dua perintah - masukkan dan perbarui, jadi tepat 500 transaksi dari dua perintah selalu ditempatkan dalam satu batch dan kami tidak mengalami masalah apa pun. Setelah menambahkan pembaruan kedua, hasil edit kami berhenti berfungsi:

FOR i IN 1..10000 LOOP
  BEGIN
    INSERT INTO test_table VALUES (1) RETURNING id INTO v_id;
    UPDATE test_table set val = i where id = v_id;
    UPDATE test_table set val = i where id = v_id; -- one more update
    COMMIT;
  END;
END LOOP;

Jadi, tugas selanjutnya adalah memastikan bahwa data dari tabel asli yang diubah dalam satu transaksi, juga masuk ke tabel baru dalam satu transaksi.

Penolakan dari pengelompokan

Dan sekali lagi kami memiliki dua solusi. Pertama: mari kita tinggalkan partisi menjadi beberapa batch dan transfer data dalam satu transaksi. Keuntungan dari solusi ini adalah kesederhanaannya - perubahan kode yang diperlukan sangat minim (omong-omong, di versi lama pg_reorg bekerja persis seperti itu). Namun ada masalah - kita membuat transaksi yang sudah berjalan lama, dan ini, seperti disebutkan sebelumnya, merupakan ancaman munculnya kembung baru.

Solusi kedua lebih kompleks, tetapi mungkin lebih tepat: buat kolom di tabel log dengan pengidentifikasi transaksi yang menambahkan data ke tabel. Kemudian, saat kita menyalin data, kita dapat mengelompokkannya berdasarkan atribut ini dan memastikan bahwa perubahan terkait ditransfer secara bersamaan. Batch akan dibentuk dari beberapa transaksi (atau satu transaksi besar) dan ukurannya akan bervariasi tergantung pada seberapa banyak data yang diubah dalam transaksi tersebut. Penting untuk dicatat bahwa karena data dari transaksi yang berbeda memasuki tabel log dalam urutan acak, maka tidak mungkin lagi membacanya secara berurutan, seperti sebelumnya. seqscan untuk setiap permintaan dengan pemfilteran berdasarkan tx_id terlalu mahal, diperlukan indeks, tetapi ini juga akan memperlambat metode karena biaya tambahan untuk memperbaruinya. Secara umum, seperti biasa, Anda perlu mengorbankan sesuatu.

Jadi, kami memutuskan untuk memulai dengan opsi pertama, karena lebih sederhana. Pertama, penting untuk memahami apakah transaksi jangka panjang akan menjadi masalah nyata. Karena transfer data utama dari tabel lama ke tabel baru juga terjadi dalam satu transaksi panjang, pertanyaannya berubah menjadi “berapa kita akan meningkatkan transaksi ini?” Durasi transaksi pertama terutama bergantung pada ukuran meja. Durasi yang baru bergantung pada berapa banyak perubahan yang terakumulasi dalam tabel selama transfer data, mis. pada intensitas beban. Proses pg_repack terjadi pada saat beban layanan minimal, dan volume perubahan sangat kecil dibandingkan dengan ukuran tabel asli. Kami memutuskan bahwa kami dapat mengabaikan waktu transaksi baru (sebagai perbandingan, rata-rata 1 jam 2-3 menit).

Eksperimennya positif. Peluncuran pada produksi juga. Agar lebih jelas, berikut gambar dengan ukuran salah satu database setelah dijalankan:

Postgres: batasan mengasapi, pg_repack dan ditangguhkan

Karena kami benar-benar puas dengan solusi ini, kami tidak mencoba menerapkan solusi kedua, namun kami mempertimbangkan kemungkinan untuk mendiskusikannya dengan pengembang ekstensi. Sayangnya, revisi kami saat ini belum siap untuk dipublikasikan, karena kami hanya menyelesaikan masalah dengan pembatasan unik yang ditangguhkan, dan untuk tambalan yang lengkap, perlu memberikan dukungan untuk jenis lain. Kami berharap dapat melakukan hal ini di masa depan.

Mungkin Anda bertanya-tanya, mengapa kami terlibat dalam cerita ini dengan modifikasi pg_repack, dan tidak, misalnya, menggunakan analognya? Pada titik tertentu kami juga memikirkan hal ini, namun pengalaman positif menggunakannya sebelumnya, pada tabel tanpa batasan yang ditangguhkan, memotivasi kami untuk mencoba memahami esensi masalah dan memperbaikinya. Selain itu, menggunakan solusi lain juga memerlukan waktu untuk melakukan pengujian, jadi kami memutuskan bahwa kami akan mencoba memperbaiki masalah di dalamnya terlebih dahulu, dan jika kami menyadari bahwa kami tidak dapat melakukan ini dalam waktu yang wajar, maka kami akan mulai mencari analog. .

Temuan

Apa yang dapat kami rekomendasikan berdasarkan pengalaman kami sendiri:

  1. Pantau kembung Anda. Berdasarkan data pemantauan, Anda dapat memahami seberapa baik konfigurasi autovacuum.
  2. Sesuaikan AUTOVACUUM untuk menjaga kembung pada tingkat yang dapat diterima.
  3. Jika kembung masih terus bertambah dan Anda tidak dapat mengatasinya dengan alat yang sudah ada, jangan takut untuk menggunakan ekstensi eksternal. Hal utama adalah menguji semuanya dengan baik.
  4. Jangan takut untuk memodifikasi solusi eksternal agar sesuai dengan kebutuhan Anda - terkadang ini bisa lebih efektif dan bahkan lebih mudah daripada mengubah kode Anda sendiri.

Sumber: www.habr.com

Tambah komentar