Terakhir kali, kami bertemu dengan perangkat dari salah satu objek penting dari memori bersama, cache buffer. Kemungkinan kehilangan informasi dari RAM adalah alasan utama perlunya pemulihan dari kegagalan. Hari ini kita akan berbicara tentang alat-alat ini.
Majalah
Sayangnya, mukjizat tidak terjadi: untuk bertahan dari hilangnya informasi dalam RAM, semua yang diperlukan harus ditulis ke disk (atau perangkat non-volatil lainnya) secara tepat waktu.
Karena itu, inilah yang telah dilakukan. Seiring dengan perubahan data,
jurnal perubahan ini juga disimpan. Saat kami mengubah sesuatu pada halaman di cache buffer, kami membuat catatan di log tentang perubahan ini. Catatan berisi informasi minimum yang memadai sehingga, jika perlu, perubahan dapat diulang.
Agar ini berfungsi, entri jurnal harus masuk ke disk
sebelum halaman yang diubah sampai di sana. Oleh karena itu namanya: log write-ahead.
Jika kegagalan terjadi, data pada disk dalam keadaan tidak konsisten: beberapa halaman ditulis sebelumnya, beberapa nanti. Tetapi masih ada jurnal yang dapat dibaca dan dilakukan kembali oleh operasi-operasi yang sudah selesai sebelum kegagalan, tetapi yang hasilnya tidak mencapai disk.
Mengapa tidak memaksa halaman data sendiri untuk ditulis ke disk, mengapa melakukan pekerjaan ganda? Ternyata sangat efektif.
Pertama-tama, log adalah aliran data berurutan untuk ditulis. Bahkan HDD melakukan cukup baik dengan perekaman berurutan. Tetapi catatan data itu sendiri adalah acak, karena halaman-halaman tersebut tersebar di disk kurang lebih secara acak.
Kedua, entri jurnal bisa jauh lebih kecil dari satu halaman.
Ketiga, saat merekam, Anda tidak perlu khawatir untuk memastikan bahwa data pada disk tetap konsisten pada setiap titik waktu yang sewenang-wenang (persyaratan ini sangat menyulitkan kehidupan).
Dan keempat, seperti yang akan kita lihat nanti, jurnal (karena ada) dapat digunakan tidak hanya untuk pemulihan, tetapi juga untuk cadangan dan replikasi.
Anda harus mencatat semua operasi, di mana ada risiko ketidakkonsistenan pada disk jika terjadi kegagalan. Secara khusus, tindakan berikut dicatat:
- mengubah halaman dalam cache buffer (sebagai aturan, ini adalah tabel dan halaman indeks) - karena halaman yang diubah tidak langsung masuk ke disk;
- melakukan dan membatalkan transaksi - perubahan status terjadi di buffer XACT dan juga tidak segera mencapai disk;
- operasi file (membuat dan menghapus file dan direktori, misalnya, membuat file saat membuat tabel) - karena operasi ini harus terjadi secara bersamaan dengan perubahan data.
Tidak dicatat:
- operasi dengan tabel non-jurnal (tidak dicatat) - namanya berbicara sendiri;
- operasi dengan tabel sementara - tidak masuk akal, karena masa hidup tabel tersebut tidak melebihi masa sesi yang membuatnya.
Sebelum PostgreSQL 10,
indeks hash tidak dicatat (mereka hanya melayani memetakan fungsi hash untuk tipe data yang berbeda), tetapi sekarang ini telah diperbaiki.
Perangkat logis

Logikanya, jurnal dapat dianggap sebagai urutan catatan dari berbagai panjang. Setiap catatan berisi
data tentang operasi tertentu, didahului oleh
header standar. Judul, antara lain, menunjukkan:
- Nomor transaksi tempat catatan itu berada.
- manajer sumber daya - komponen sistem yang bertanggung jawab untuk merekam;
- checksum (CRC) - memungkinkan Anda menentukan korupsi data;
- rekam panjang dan tautkan ke catatan sebelumnya.
Data itu sendiri memiliki format dan makna yang berbeda. Misalnya, mereka dapat mewakili beberapa fragmen halaman yang perlu ditulis di atas kontennya dengan offset tertentu. Manajer sumber daya yang ditentukan βmemahamiβ bagaimana menafsirkan data dalam catatannya. Ada manajer terpisah untuk tabel, untuk setiap jenis indeks, untuk status transaksi, dll. Daftar lengkapnya dapat diperoleh jika diinginkan oleh perintah
pg_waldump -r list
Perangkat fisik
Pada disk, log disimpan sebagai file di direktori $ PGDATA / pg_wal. Setiap file default ke 16 MB. Ukuran dapat ditingkatkan untuk menghindari sejumlah besar file dalam satu direktori. Sebelum PostgreSQL 11, ini hanya bisa dilakukan ketika kompilasi kode sumber, tetapi sekarang Anda dapat menentukan ukuran saat menginisialisasi cluster (
--wal-segsize
).
Entri log termasuk dalam file yang sedang digunakan; ketika itu berakhir, yang berikutnya mulai digunakan.
Buffer khusus dialokasikan untuk log di memori bersama server. Ukuran cache jurnal ditetapkan oleh parameter
wal_buffers (nilai default menyiratkan konfigurasi otomatis: 1/32 dari cache buffer dialokasikan).
Cache jurnal disusun seperti cache buffer, tetapi ia bekerja terutama dalam mode ring buffer: entri ditambahkan ke "head" dan ditulis ke disk dari "tail".
Posisi perekaman ("ekor") dan penyisipan ("kepala") menunjukkan fungsi masing-masing pg_current_wal_lsn dan pg_current_wal_insert lsn:
=> SELECT pg_current_wal_lsn(), pg_current_wal_insert_lsn();
pg_current_wal_lsn | pg_current_wal_insert_lsn --------------------+--------------------------- 0/331E4E64 | 0/331E4EA0 (1 row)
Untuk merujuk ke catatan tertentu, tipe data pg_lsn (LSN = nomor urut log) digunakan - ini adalah nomor 64-bit yang mewakili offset byte sebelum menulis ke awal log. LSN adalah output sebagai dua angka 32-bit dalam notasi heksadesimal.
Anda dapat menemukan di file mana kita akan menemukan posisi yang diinginkan, dan dengan apa offset dari awal file:
=> SELECT file_name, upper(to_hex(file_offset)) file_offset FROM pg_walfile_name_offset('0/331E4E64');
file_name | file_offset --------------------------+------------- 000000010000000000000033 | 1E4E64 \ /\ / 0/331E4E64
Nama file terdiri dari dua bagian. 8 digit heksadesimal atas menunjukkan jumlah cabang waktu (digunakan ketika memulihkan dari cadangan), sisanya sesuai dengan digit LSN tertinggi (dan digit LSN yang lebih rendah lainnya menunjukkan offset).
File log dapat dilihat pada sistem file dalam direktori $ PGDATA / pg_wal /, tetapi dimulai dengan PostgreSQL 10 mereka juga dapat dilihat dengan fungsi khusus:
=> SELECT * FROM pg_ls_waldir() WHERE name = '000000010000000000000033';
name | size | modification --------------------------+----------+------------------------ 000000010000000000000033 | 16777216 | 2019-07-08 20:24:13+03 (1 row)
Maju menulis
Mari kita lihat bagaimana jurnal terjadi dan bagaimana rekaman proaktif disediakan. Buat tabel:
=> CREATE TABLE wal(id integer); => INSERT INTO wal VALUES (1);
Kami akan melihat header dari halaman tabel. Untuk melakukan ini, kita memerlukan ekstensi yang sudah akrab:
=> CREATE EXTENSION pageinspect;
Mari kita mulai transaksi dan ingat posisi penyisipan dalam log:
=> BEGIN; => SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/331F377C (1 row)
Sekarang mari kita lakukan beberapa operasi, misalnya, perbarui baris:
=> UPDATE wal set id = id + 1;
Perubahan ini dicatat dalam log, posisi penyisipan telah berubah:
=> SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/331F37C4 (1 row)
Untuk memastikan bahwa halaman data yang dimodifikasi tidak didorong ke disk sebelum entri jurnal, LSN dari entri jurnal terakhir yang terkait dengan halaman ini disimpan di header halaman:
=> SELECT lsn FROM page_header(get_raw_page('wal',0));
lsn ------------ 0/331F37C4 (1 row)
Ingatlah bahwa jurnal itu umum untuk seluruh kluster, dan entri baru masuk ke dalamnya setiap saat. Oleh karena itu, LSN pada halaman mungkin kurang dari nilai yang baru saja dikembalikan fungsi pg_current_wal_insert_lsn. Tapi tidak ada yang terjadi di sistem kami, jadi angkanya sama.
Sekarang selesaikan transaksi.
=> COMMIT;
Catatan komit juga masuk ke log, dan posisinya berubah lagi:
=> SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/331F37E8 (1 row)
Komit mengubah status transaksi dalam struktur yang disebut XACT (kami
sudah membicarakannya ). Status disimpan dalam file, tetapi mereka juga menggunakan cache mereka sendiri, yang menempati 128 halaman dalam memori bersama. Oleh karena itu, untuk halaman XACT, LSN dari entri jurnal terakhir harus dilacak. Tetapi informasi ini tidak disimpan di halaman itu sendiri, tetapi di RAM.
Pada titik tertentu, entri jurnal yang dibuat akan ditulis ke disk. Di mana - kita akan berbicara lain waktu, tetapi dalam kasus kami ini sudah terjadi:
=> SELECT pg_current_wal_lsn(), pg_current_wal_insert_lsn();
pg_current_wal_lsn | pg_current_wal_insert_lsn --------------------+--------------------------- 0/331F37E8 | 0/331F37E8 (1 row)
Setelah titik ini, data dan halaman XACT dapat didorong keluar dari cache. Tetapi jika diminta untuk memaksa mereka keluar lebih awal, itu akan terdeteksi dan entri jurnal akan direkam secara paksa terlebih dahulu.
Mengetahui dua posisi LSN, Anda bisa mendapatkan ukuran entri jurnal di antara mereka (dalam byte) dengan hanya mengurangi satu posisi dari yang lain. Anda hanya perlu memberikan posisi ke tipe pg_lsn:
=> SELECT '0/331F37E8'::pg_lsn - '0/331F377C'::pg_lsn;
?column? ---------- 108 (1 row)
Dalam hal ini, pembaruan baris dan komit diperlukan 108 byte dalam log.
Dengan cara yang sama, Anda dapat memperkirakan berapa banyak entri jurnal yang dihasilkan oleh server per unit waktu pada beban tertentu. Ini adalah informasi penting yang akan diperlukan selama pengaturan (yang akan kita bicarakan lain kali).
Sekarang kita akan menggunakan utilitas pg_waldump untuk melihat entri log yang dibuat.
Utilitas dapat bekerja dengan rentang LSN (seperti dalam contoh ini) dan memilih catatan untuk transaksi yang ditentukan. Itu harus dijalankan atas nama pengguna OS postgres, karena dia membutuhkan akses ke file log pada disk.
postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/331F377C -e 0/331F37E8 000000010000000000000033
rmgr: Heap len (rec/tot): 69/ 69, tx: 101085, lsn: 0/331F377C, prev 0/331F3014, desc: HOT_UPDATE off 1 xmax 101085 ; new off 2 xmax 0, blkref #0: rel 1663/16386/33081 blk 0
rmgr: Transaction len (rec/tot): 34/ 34, tx: 101085, lsn: 0/331F37C4, prev 0/331F377C, desc: COMMIT 2019-07-08 20:24:13.945435 MSK
Di sini kita melihat tajuk dari dua entri.
Yang pertama adalah operasi
HOT_UPDATE , terkait dengan manajer sumber daya
tumpukan . Nama file dan nomor halaman ditunjukkan dalam bidang blkref dan cocok dengan halaman tabel yang diperbarui:
=> SELECT pg_relation_filepath('wal');
pg_relation_filepath ---------------------- base/16386/33081 (1 row)
Entri kedua adalah COMMIT, terkait dengan Manajer Sumber Daya Transaksi.
Bukan format yang paling mudah dibaca, tetapi Anda bisa mengetahuinya jika perlu.
Pemulihan
Ketika kita memulai server, proses postmaster dimulai terlebih dahulu, dan pada gilirannya, memulai proses startup, tugasnya adalah memastikan pemulihan jika terjadi kegagalan.
Untuk menentukan apakah pemulihan diperlukan, startup melihat ke file kontrol khusus $ PGDATA / global / pg_control dan melihat status cluster. Kami dapat memeriksa status kami sendiri menggunakan utilitas pg_controldata:
postgres$ /usr/lib/postgresql/11/bin/pg_controldata -D /var/lib/postgresql/11/main | grep state
Database cluster state: in production
Server yang berhenti dengan rapi akan memiliki status "shut down". Jika server tidak berfungsi, dan statusnya tetap "dalam produksi", ini berarti bahwa DBMS telah turun dan kemudian pemulihan akan dilakukan secara otomatis.
Untuk pemulihan, proses startup akan secara berurutan membaca log dan menerapkan entri ke halaman, jika perlu. Anda dapat memverifikasi kebutuhan dengan membandingkan LSN halaman pada disk dengan LSN dari entri jurnal. Jika LSN halaman lebih besar, maka catatan tidak perlu. Tetapi pada kenyataannya - itu bahkan tidak mungkin, karena catatan dirancang untuk aplikasi yang sangat konsisten.
Ada beberapa pengecualian. Beberapa catatan dibentuk sebagai gambar halaman penuh (FPI, gambar halaman penuh), dan jelas bahwa gambar seperti itu dapat diterapkan ke halaman dalam keadaan apa pun - itu masih akan menghapus semua yang ada di sana. Perubahan lain dalam status transaksi dapat diterapkan ke versi halaman XACT mana pun - oleh karena itu, di dalam halaman tersebut tidak perlu menyimpan LSN.
Mengubah halaman selama pemulihan terjadi dalam cache buffer, seperti pada saat kerja normal - untuk postmaster ini memulai proses latar belakang yang diperlukan.
Demikian pula, entri jurnal berlaku untuk file: misalnya, jika catatan mengatakan bahwa file tersebut harus ada, tetapi tidak ada, file dibuat.
Nah, pada akhir proses pemulihan, semua tabel non-jurnal ditimpa dengan "boneka" dari
lapisan init mereka.
Ini adalah presentasi algoritma yang sangat sederhana. Secara khusus, kami belum mengatakan apa pun tentang di mana harus mulai membaca entri jurnal (percakapan ini harus ditunda sampai pos pemeriksaan dipertimbangkan).
Dan klarifikasi terakhir. Proses pemulihan "klasik" terdiri dari dua fase. Pada fase pertama (roll forward), entri jurnal digulung, dan server mengulangi semua pekerjaan yang hilang selama kegagalan. Pada yang kedua (roll back), transaksi yang tidak dilakukan pada saat kegagalan dibatalkan. Tetapi PostgreSQL tidak membutuhkan fase kedua. Seperti yang kita
bahas sebelumnya , karena kekhasan pelaksanaan transaksi multi-versi tidak perlu digulirkan kembali secara fisik; sudah cukup bahwa bit perbaikan tidak akan diatur dalam XACT.
Untuk dilanjutkan .