
Replikasi bukan cadangan. Atau tidak? Inilah cara kami menggunakan replikasi yang ditangguhkan untuk pemulihan dengan secara tidak sengaja menghapus pintasan.
Spesialis infrastruktur di GitLab bertanggung jawab untuk menjalankan GitLab.com , contoh terbesar dari GitLab di alam. Ada 3 juta pengguna dan hampir 7 juta proyek, dan ini adalah salah satu situs SaaS open source terbesar dengan arsitektur khusus. Tanpa sistem basis data PostgreSQL, infrastruktur GitLab.com tidak akan berjalan jauh, dan kami hanya tidak melakukannya untuk toleransi kesalahan jika terjadi kegagalan ketika data dapat hilang. Kecil kemungkinan bahwa bencana seperti itu akan terjadi, tetapi kami telah dipersiapkan dengan baik dan diisi dengan berbagai mekanisme pencadangan dan replikasi.
Replikasi bukan alat cadangan database untuk Anda ( lihat di bawah ). Tetapi sekarang kita akan melihat cara memulihkan data yang terhapus secara tidak sengaja dengan menggunakan replikasi yang tertunda: di GitLab.com, pengguna menghapus pintasan untuk proyek gitlab-ce
dan kehilangan kontak dengan permintaan dan tugas gabungan.
Dengan replika yang tertunda, kami memulihkan data hanya dalam 1,5 jam. Lihat bagaimana itu.
Pemulihan point-in-time dengan PostgreSQL
PostgreSQL memiliki fungsi bawaan yang mengembalikan keadaan basis data pada titik waktu tertentu. Itu disebut Point-in-Time Recovery (PITR) dan menggunakan mekanisme yang sama yang menjaga relevansi replika: dimulai dengan snapshot yang dapat diandalkan dari seluruh kluster basis data (cadangan dasar), kami menerapkan sejumlah perubahan keadaan hingga titik waktu tertentu.
Untuk menggunakan fungsi ini untuk cadangan dingin, kami secara teratur membuat cadangan dasar dari basis data dan menyimpannya di arsip (arsip GitLab tinggal di penyimpanan cloud Google ). Kami juga memantau perubahan status database dengan mengarsipkan log write-ahead (WAL). Dan dengan semua ini, kita dapat melakukan PITR untuk pemulihan bencana: kita mulai dengan gambar yang diambil sebelum kesalahan dan menerapkan perubahan dari arsip WAL hingga kegagalan.
Apa itu replikasi yang ditangguhkan?
Replikasi ditangguhkan adalah penerapan perubahan WAL tertunda. Yaitu, transaksi terjadi pada jam X
, tetapi akan muncul di replika dengan penundaan d
pada jam X + d
.
PostgreSQL memiliki 2 cara untuk mengkonfigurasi replika fisik dari database: mengembalikan dari arsip dan replikasi streaming. Mengembalikan dari arsip , pada kenyataannya, berfungsi seperti PITR, tetapi terus menerus: kami terus-menerus mengekstrak perubahan dari arsip WAL dan menerapkannya pada replika. Dan replikasi streaming langsung mengambil aliran WAL dari host basis data hulu. Kami lebih suka pemulihan dari arsip - lebih mudah dikelola dan memiliki kinerja normal, yang tidak tertinggal dari kluster yang berfungsi.
Cara mengatur pemulihan yang ditangguhkan dari arsip
Opsi pemulihan dijelaskan dalam file recovery.conf
. Contoh:
standby_mode = 'on' restore_command = '/usr/bin/envdir /etc/wal-ed/env /opt/wal-e/bin/wal-e wal-fetch -p 4 "%f" "%p"' recovery_min_apply_delay = '8h' recovery_target_timeline = 'latest'
Dengan parameter ini, kami membuat replika malas dengan pemulihan dari arsip. Di sini wal-e digunakan untuk mengekstrak segmen WAL ( restore_command
) dari arsip, dan perubahan akan diterapkan setelah delapan jam ( recovery_min_apply_delay
). Replika akan memantau perubahan dalam timeline dalam arsip, misalnya, karena failover di cluster ( recovery_target_timeline
).
Dengan recovery_min_apply_delay
Anda dapat mengonfigurasi replikasi streaming yang tertunda, tetapi ada beberapa trik yang terkait dengan slot replikasi, umpan balik cadangan yang panas, dan sebagainya. Arsip WAL memungkinkan Anda untuk menghindarinya.
Parameter recovery_min_apply_delay
hanya muncul di PostgreSQL 9.3. Dalam versi sebelumnya, untuk replikasi yang ditangguhkan, Anda perlu mengonfigurasi kombinasi fungsi manajemen pemulihan ( pg_xlog_replay_pause(), pg_xlog_replay_resume()
) atau menyimpan segmen WAL dalam arsip untuk penundaan waktu.
Bagaimana PostgreSQL melakukan ini?
Penasaran ingin melihat bagaimana PostgreSQL mengimplementasikan pemulihan yang ditangguhkan. Mari kita lihat recoveryApplyDelay(XlogReaderState)
. Ini dipanggil dari loop utama untuk setiap entri di WAL.
static bool recoveryApplyDelay(XLogReaderState *record) { uint8 xact_info; TimestampTz xtime; long secs; int microsecs; /* nothing to do if no delay configured */ if (recovery_min_apply_delay <= 0) return false; /* no delay is applied on a database not yet consistent */ if (!reachedConsistency) return false; /* * Is it a COMMIT record? * * We deliberately choose not to delay aborts since they have no effect on * MVCC. We already allow replay of records that don't have a timestamp, * so there is already opportunity for issues caused by early conflicts on * standbys. */ if (XLogRecGetRmid(record) != RM_XACT_ID) return false; xact_info = XLogRecGetInfo(record) & XLOG_XACT_OPMASK; if (xact_info != XLOG_XACT_COMMIT && xact_info != XLOG_XACT_COMMIT_PREPARED) return false; if (!getRecordTimestamp(record, &xtime)) return false; recoveryDelayUntilTime = TimestampTzPlusMilliseconds(xtime, recovery_min_apply_delay); /* * Exit without arming the latch if it's already past time to apply this * record */ TimestampDifference(GetCurrentTimestamp(), recoveryDelayUntilTime, &secs, µsecs); if (secs <= 0 && microsecs <= 0) return false; while (true) { // Shortened: // Use WaitLatch until we reached recoveryDelayUntilTime // and then break; } return true; }
Intinya adalah bahwa penundaan didasarkan pada waktu fisik yang dicatat dalam stempel waktu komit transaksi ( xtime
). Seperti yang Anda lihat, penundaan hanya berlaku untuk komit dan tidak menyentuh catatan lain - semua perubahan diterapkan secara langsung, dan komit ditunda, sehingga kami akan melihat perubahan hanya setelah penundaan tersebut dikonfigurasi.
Cara menggunakan replika malas untuk memulihkan data
Katakanlah kita memiliki klaster basis data dalam produksi dan replika dengan penundaan delapan jam. Mari kita lihat cara memulihkan data menggunakan contoh cara pintas yang tidak sengaja terhapus .
Ketika kami mengetahui masalah ini, kami menghentikan sementara pemulihan dari arsip untuk replika malas:
SELECT pg_xlog_replay_pause();
Dengan jeda, kami tidak memiliki risiko replika akan mengulangi permintaan DELETE
. Hal yang berguna jika Anda perlu waktu untuk mencari tahu.
Intinya adalah bahwa replika yang ditangguhkan harus mencapai titik sebelum permintaan DELETE
. Kami secara kasar mengetahui waktu penghapusan secara fisik. Kami menghapus recovery_min_apply_delay
dan menambahkan recovery_target_time
ke recovery.conf
. Jadi replika mencapai momen yang tepat tanpa penundaan:
recovery_target_time = '2018-10-12 09:25:00+00'
Dengan stempel waktu, lebih baik mengurangi kelebihan agar tidak ketinggalan. Benar, semakin besar penurunannya, semakin banyak data yang hilang. Sekali lagi, jika kami menyelinap melalui permintaan DELETE
, semuanya akan dihapus lagi dan Anda harus memulai dari awal lagi (atau bahkan mengambil cadangan dingin untuk PITR).
Kami memulai kembali instance Postgres yang ditangguhkan, dan segmen WAL diulangi hingga waktu yang ditentukan. Anda dapat melacak kemajuan pada tahap ini dengan permintaan:
SELECT -- current location in WAL pg_last_xlog_replay_location(), -- current transaction timestamp (state of the replica) pg_last_xact_replay_timestamp(), -- current physical time now(), -- the amount of time still to be applied until recovery_target_time has been reached '2018-10-12 09:25:00+00'::timestamptz - pg_last_xact_replay_timestamp() as delay;
Jika cap waktu tidak berubah lagi, pemulihan selesai. Anda dapat mengonfigurasi tindakan recovery_target_action
untuk menutup, memajukan, atau menjeda instance setelah replay (secara default itu berhenti).
Database datang ke keadaan sebelum permintaan naas itu. Sekarang Anda dapat, misalnya, mengekspor data. Kami mengekspor data yang dihapus tentang pintasan dan semua koneksi dengan tugas dan menggabungkan permintaan dan mentransfernya ke database yang berfungsi. Jika kerugiannya berskala besar, Anda bisa mempromosikan replika dan menggunakannya sebagai yang utama. Tetapi kemudian semua perubahan akan hilang setelah saat kita pulih.
Alih-alih cap waktu, lebih baik menggunakan ID transaksi. Sangat berguna untuk menulis ID ini, misalnya, untuk pernyataan DDL (seperti DROP TABLE
), menggunakan log_statements = 'ddl'
. Jika kami memiliki ID transaksi, kami akan mengambil recovery_target_xid
dan menjalankan semuanya hingga ke transaksi sebelum permintaan DELETE
.
Kembali bekerja sangat sederhana: hapus semua perubahan dari recovery.conf
dan mulai kembali Postgres. Segera, penundaan delapan jam akan muncul di replika lagi, dan kami siap untuk masalah di masa depan.
Manfaat Pemulihan
Dengan replika yang tertunda, alih-alih cadangan dingin, Anda tidak harus mengembalikan seluruh gambar dari arsip selama berjam-jam. Sebagai contoh, kita perlu lima jam untuk mendapatkan seluruh cadangan dasar 2 TB. Dan kemudian Anda masih harus menerapkan seluruh WAL harian untuk memulihkan ke kondisi yang diinginkan (dalam kasus terburuk).
Replika tertunda lebih baik daripada cadangan dingin dengan dua cara:
- Tidak perlu mendapatkan seluruh cadangan basis dari arsip.
- Ada jendela delapan jam tetap dari segmen WAL yang perlu diulang.
Kami juga terus-menerus memeriksa apakah mungkin membuat PITR dari WAL, dan kami akan segera melihat kerusakan atau masalah lain dengan arsip WAL, memantau kelambatan replika yang tertunda.
Dalam contoh ini, kami membutuhkan 50 menit untuk pulih, yaitu, kecepatannya adalah 110 GB data WAL per jam (arsip masih pada AWS S3 saat itu ). Secara total, kami memecahkan masalah dan mengembalikan data dalam 1,5 jam.
Intinya: di mana replika tertunda sangat berguna (dan di mana tidak)
Gunakan replikasi malas sebagai pertolongan pertama jika Anda secara tidak sengaja kehilangan data dan perhatikan bencana ini dalam penundaan yang dikonfigurasi.
Namun perlu diingat: replikasi bukan cadangan.
Cadangan dan replikasi memiliki tujuan yang berbeda. Cold backup berguna jika Anda secara tidak sengaja membuat DELETE
atau DROP TABLE
. Kami membuat cadangan dari penyimpanan dingin dan mengembalikan keadaan tabel sebelumnya atau seluruh database. Tetapi pada saat yang sama, permintaan DROP TABLE
hampir segera dimainkan di semua replika di cluster kerja, sehingga replikasi reguler tidak akan menyelamatkan Anda di sini. Replikasi itu sendiri membuat database dapat diakses ketika server terpisah disewa dan mendistribusikan beban.
Bahkan dengan replika yang tertunda, terkadang kita benar-benar membutuhkan cadangan dingin di tempat yang aman jika pusat data macet, kerusakan tersembunyi atau peristiwa lain yang tidak segera Anda sadari. Tidak ada arti dari satu replikasi.
Catatan Di GitLab.com, kami sekarang melindungi terhadap kehilangan data hanya di tingkat sistem dan tidak mengembalikan data di tingkat pengguna.