Postingan kali ini merupakan lanjutan dari postingan kami sebelumnya tentang Online Schema Upgrade di Galera menggunakan metode TOI. Sekarang kami akan menunjukkan cara melakukan upgrade skema menggunakan metode Rolling Schema Upgrade (RSU).
RSU dan TOI
Seperti yang telah kita diskusikan, saat menggunakan TOI, perubahan terjadi pada saat yang sama di semua node. Ini bisa menjadi batasan serius karena cara mengeksekusi perubahan skema menyiratkan bahwa tidak ada kueri lain yang dapat dieksekusi. Untuk pernyataan ALTER yang panjang, cluster mungkin tidak tersedia selama berjam-jam. Jelas, ini bukan sesuatu yang dapat Anda terima dalam produksi. Metode RSU mengatasi kelemahan ini - perubahan terjadi pada satu node pada satu waktu sementara node lain tidak terpengaruh dan dapat melayani lalu lintas. Setelah ALTER selesai pada satu node, itu akan bergabung kembali dengan cluster dan Anda dapat melanjutkan dengan mengeksekusi perubahan skema pada node berikutnya.
Perilaku seperti itu datang dengan batasannya sendiri. Yang utama adalah bahwa perubahan skema terjadwal harus kompatibel. Apa artinya? Mari kita pikirkan sejenak. Pertama-tama kita perlu ingat bahwa cluster aktif dan berjalan sepanjang waktu - node yang diubah harus dapat menerima semua lalu lintas yang mengenai node yang tersisa. Singkatnya, DML yang dieksekusi pada skema lama harus bekerja juga pada skema baru (dan sebaliknya jika Anda menggunakan semacam distribusi koneksi seperti round-robin di Galera Cluster Anda). Kami akan fokus pada kompatibilitas MySQL, tetapi Anda juga harus ingat bahwa aplikasi Anda harus bekerja dengan node yang diubah dan yang tidak diubah - pastikan perubahan Anda tidak merusak logika aplikasi. Salah satu praktik yang baik adalah dengan secara eksplisit meneruskan nama kolom ke kueri - jangan mengandalkan “PILIH *” karena Anda tidak pernah tahu berapa banyak kolom yang akan Anda dapatkan sebagai balasannya.
Format log biner berbasis Galera dan Baris
Oke, jadi DML harus bekerja pada skema lama dan baru. Bagaimana DML ditransfer antar node Galera? Apakah itu mempengaruhi perubahan apa yang kompatibel dan apa yang tidak? Ya, memang - memang begitu. Galera tidak menggunakan replikasi MySQL biasa tetapi masih mengandalkannya untuk mentransfer acara antar node. Tepatnya, Galera menggunakan format ROW untuk acara. Sebuah acara dalam format baris (setelah decoding) mungkin terlihat seperti ini:
### INSERT INTO `schema`.`table`
### SET
### @1=1
### @2=1
### @3='88764053989'
### @4='14700597838'
Atau:
### UPDATE `schema`.`table`
### WHERE
### @1=1
### @2=1
### @3='88764053989'
### @4='14700597838'
### SET
### @1=2
### @2=2
### @3='88764053989'
### @4='81084251066'
Seperti yang Anda lihat, ada pola yang terlihat:sebuah baris dikenali dari isinya. Tidak ada nama kolom, hanya urutannya. Ini saja harus menyalakan beberapa lampu peringatan:"apa yang akan terjadi jika saya menghapus salah satu kolom?" Nah, jika kolom terakhir, ini bisa diterima. Jika Anda akan menghapus kolom di tengah, ini akan mengacaukan urutan kolom dan, akibatnya, replikasi akan terputus. Hal serupa akan terjadi jika Anda menambahkan beberapa kolom di tengah, bukan di akhir. Ada lebih banyak kendala, meskipun. Mengubah definisi kolom akan berfungsi selama itu adalah tipe data yang sama - Anda dapat mengubah kolom INT menjadi BIGINT tetapi Anda tidak dapat mengubah kolom INT menjadi VARCHAR - ini akan menghentikan replikasi. Anda dapat menemukan deskripsi rinci tentang perubahan apa yang kompatibel dan apa yang tidak dalam dokumentasi MySQL. Apa pun yang Anda lihat di dokumentasi, untuk tetap aman, lebih baik menjalankan beberapa pengujian pada cluster pengembangan/pementasan terpisah. Pastikan itu akan berfungsi tidak hanya sesuai dengan dokumentasi, tetapi juga berfungsi dengan baik di pengaturan khusus Anda.
Secara keseluruhan, seperti yang dapat Anda lihat dengan jelas, menjalankan RSU dengan cara yang aman jauh lebih kompleks daripada hanya menjalankan beberapa perintah. Namun, karena perintah itu penting, mari kita lihat contoh bagaimana Anda dapat menjalankan RSU dan apa yang bisa salah dalam prosesnya.
Contoh RSU
Penyiapan awal
Mari kita bayangkan contoh aplikasi yang agak sederhana. Kami akan menggunakan alat bechmark, Sysbench, untuk menghasilkan konten dan lalu lintas, tetapi alurnya akan sama untuk hampir setiap aplikasi - Wordpress, Joomla, Drupal, apa saja. Kami akan menggunakan HAProxy collocated dengan aplikasi kami untuk membagi membaca dan menulis di antara node Galera secara round-robin. Anda dapat memeriksa di bawah ini bagaimana HAProxy melihat cluster Galera.
Secara keseluruhan topologi terlihat seperti di bawah ini:
Lalu lintas dihasilkan menggunakan perintah berikut:
while true ; do sysbench /root/sysbench/src/lua/oltp_read_write.lua --threads=4 --max-requests=0 --time=3600 --mysql-host=10.0.0.100 --mysql-user=sbtest --mysql-password=sbtest --mysql-port=3307 --tables=32 --report-interval=1 --skip-trx=on --table-size=100000 --db-ps-mode=disable run ; done
Skema terlihat seperti di bawah ini:
mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
Pertama, mari kita lihat bagaimana kita bisa menambahkan indeks ke tabel ini. Menambahkan indeks adalah perubahan yang kompatibel yang dapat dilakukan dengan mudah menggunakan RSU.
mysql> SET SESSION wsrep_OSU_method=RSU;
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE sbtest1.sbtest1 ADD INDEX idx_new (k, c);
Query OK, 0 rows affected (5 min 19.59 sec)
Seperti yang Anda lihat di tab Node, host tempat kami menjalankan perubahan telah secara otomatis beralih ke status Donor/Desynced yang memastikan bahwa host ini tidak akan memengaruhi cluster lainnya jika diperlambat oleh ALTER.
Mari kita periksa bagaimana skema kita sekarang:
mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`),
KEY `idx_new` (`k`,`c`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
Seperti yang Anda lihat, indeks telah ditambahkan. Harap diingat, ini hanya terjadi pada node tertentu. Untuk menyelesaikan perubahan skema penuh, Anda harus mengikuti proses ini pada node yang tersisa dari Galera Cluster. Untuk menyelesaikan node pertama, kita dapat mengganti wsrep_OSU_method kembali ke TOI:
SET SESSION wsrep_OSU_method=TOI;
Query OK, 0 rows affected (0.00 sec)
Kami tidak akan menunjukkan sisa prosesnya, karena sama saja - aktifkan RSU di level sesi, jalankan ALTER, aktifkan TOI. Yang lebih menarik adalah apa yang akan terjadi jika perubahan itu tidak sesuai. Mari kita lihat kembali skemanya:
mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`),
KEY `idx_new` (`k`,`c`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
Katakanlah kita ingin mengubah jenis kolom 'k' dari INT menjadi VARCHAR(30) pada satu node.
mysql> SET SESSION wsrep_OSU_method=RSU;
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE sbtest1.sbtest1 MODIFY COLUMN k VARCHAR(30) NOT NULL DEFAULT '';
Query OK, 10004785 rows affected (1 hour 14 min 51.89 sec)
Records: 10004785 Duplicates: 0 Warnings: 0
Sekarang, mari kita lihat skemanya:
mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` varchar(30) NOT NULL DEFAULT '',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`),
KEY `idx_new` (`k`,`c`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.02 sec)
Semuanya seperti yang kami harapkan - kolom 'k' telah diubah menjadi VARCHAR. Sekarang kita dapat memeriksa apakah perubahan ini dapat diterima atau tidak untuk Cluster Galera. Untuk mengujinya, kita akan menggunakan salah satu node yang tersisa dan tidak diubah untuk mengeksekusi kueri berikut:
mysql> INSERT INTO sbtest1.sbtest1 (k, c, pad) VALUES (123, 'test', 'test');
Query OK, 1 row affected (0.19 sec)
Mari kita lihat apa yang terjadi. Jelas tidak terlihat bagus - simpul kami sedang down. Log akan memberi Anda lebih banyak detail:
2017-04-07T10:51:14.873524Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.873560Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.879120Z 5 [Warning] WSREP: Failed to apply app buffer: seqno: 982675, status: 1
at galera/src/trx_handle.cpp:apply():351
Retrying 2th time
2017-04-07T10:51:14.879272Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.879287Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.879399Z 5 [Warning] WSREP: Failed to apply app buffer: seqno: 982675, status: 1
at galera/src/trx_handle.cpp:apply():351
Retrying 3th time
2017-04-07T10:51:14.879618Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.879633Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.879730Z 5 [Warning] WSREP: Failed to apply app buffer: seqno: 982675, status: 1
at galera/src/trx_handle.cpp:apply():351
Retrying 4th time
2017-04-07T10:51:14.879911Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.879924Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.885255Z 5 [ERROR] WSREP: Failed to apply trx: source: 938415a6-1aab-11e7-ac29-0a69a4a1dafe version: 3 local: 0 state: APPLYING flags: 1 conn_id: 125559 trx_id: 2856843 seqnos (l: 392283, g: 9
82675, s: 982674, d: 982563, ts: 146831275805149)
2017-04-07T10:51:14.885271Z 5 [ERROR] WSREP: Failed to apply trx 982675 4 times
2017-04-07T10:51:14.885281Z 5 [ERROR] WSREP: Node consistency compromized, aborting…
Seperti yang dapat dilihat, Galera mengeluhkan fakta bahwa kolom tidak dapat dikonversi dari INT ke VARCHAR(30). Itu mencoba untuk mengeksekusi ulang writeset empat kali tetapi gagal, tidak mengejutkan. Dengan demikian, Galera menentukan bahwa konsistensi node dikompromikan dan node dikeluarkan dari cluster. Konten log yang tersisa menunjukkan proses ini:
2017-04-07T10:51:14.885560Z 5 [Note] WSREP: Closing send monitor...
2017-04-07T10:51:14.885630Z 5 [Note] WSREP: Closed send monitor.
2017-04-07T10:51:14.885644Z 5 [Note] WSREP: gcomm: terminating thread
2017-04-07T10:51:14.885828Z 5 [Note] WSREP: gcomm: joining thread
2017-04-07T10:51:14.885842Z 5 [Note] WSREP: gcomm: closing backend
2017-04-07T10:51:14.896654Z 5 [Note] WSREP: view(view_id(NON_PRIM,6fcd492a,37) memb {
b13499a8,0
} joined {
} left {
} partitioned {
6fcd492a,0
938415a6,0
})
2017-04-07T10:51:14.896746Z 5 [Note] WSREP: view((empty))
2017-04-07T10:51:14.901477Z 5 [Note] WSREP: gcomm: closed
2017-04-07T10:51:14.901512Z 0 [Note] WSREP: New COMPONENT: primary = no, bootstrap = no, my_idx = 0, memb_num = 1
2017-04-07T10:51:14.901531Z 0 [Note] WSREP: Flow-control interval: [16, 16]
2017-04-07T10:51:14.901541Z 0 [Note] WSREP: Received NON-PRIMARY.
2017-04-07T10:51:14.901550Z 0 [Note] WSREP: Shifting SYNCED -> OPEN (TO: 982675)
2017-04-07T10:51:14.901563Z 0 [Note] WSREP: Received self-leave message.
2017-04-07T10:51:14.901573Z 0 [Note] WSREP: Flow-control interval: [0, 0]
2017-04-07T10:51:14.901581Z 0 [Note] WSREP: Received SELF-LEAVE. Closing connection.
2017-04-07T10:51:14.901589Z 0 [Note] WSREP: Shifting OPEN -> CLOSED (TO: 982675)
2017-04-07T10:51:14.901602Z 0 [Note] WSREP: RECV thread exiting 0: Success
2017-04-07T10:51:14.902701Z 5 [Note] WSREP: recv_thread() joined.
2017-04-07T10:51:14.902720Z 5 [Note] WSREP: Closing replication queue.
2017-04-07T10:51:14.902730Z 5 [Note] WSREP: Closing slave action queue.
2017-04-07T10:51:14.902742Z 5 [Note] WSREP: /usr/sbin/mysqld: Terminated.
Tentu saja, ClusterControl akan mencoba memulihkan node tersebut - pemulihan melibatkan menjalankan SST sehingga perubahan skema yang tidak kompatibel akan dihapus, tetapi kami akan kembali ke awal - perubahan skema kami akan dibalik.
Seperti yang Anda lihat, saat menjalankan RSU adalah proses yang sangat sederhana, di bawahnya bisa agak rumit. Diperlukan beberapa pengujian dan persiapan untuk memastikan bahwa Anda tidak akan kehilangan node hanya karena perubahan skema tidak kompatibel.