- Knock menyiapkan prosedur untuk memigrasikan Postgres, penyimpanan inti bagi mesin alur kerja notifikasinya, dari AWS RDS Aurora 11.9 ke 15.3 tanpa dampak ke pelanggan
- Jika tidak bergerak sebelum tanggal pensiun Postgres 11.9 di Amazon RDS, 29 Februari 2024, mereka harus menerima upgrade paksa dan downtime
- Upgrade in-place serta
pg_dump/pg_restoredikesampingkan karena membutuhkan waktu henti yang panjang; mereka memilih pendekatan dengan menyiapkan logical replication berbasisPUBLICATION/SUBSCRIPTIONke DB baru - Strategi replikasi dibagi berdasarkan ukuran tabel dan pola penulisan; tabel kecil direplikasi langsung, sedangkan tabel besar yang append-only menggabungkan
copy_data = falsedengan backfill snapshot - Cutover akhir selesai dalam beberapa detik dengan tetap mempertahankan koneksi ke kedua DB, mengubah flag, memberi 500ms untuk query yang sedang berjalan, lalu menghentikan permintaan ke DB baru selama 1 detik untuk mengurangi risiko stale read
Tujuan dan batasan upgrade
- Knock bergantung pada Postgres untuk mesin alur kerja notifikasi, dan menggunakan Postgres untuk konfigurasi workflow, template pesan, pengumpulan jutaan log, serta antrean pekerjaan latar belakang
- Karena sifat database relasional, upgrade Postgres minimal memerlukan reboot, dan upgrade versi mayor bisa membutuhkan penghentian total selama beberapa menit atau lebih karena perubahan cara penyimpanan data dan indeks di disk
- Postgres 11.9, yang digunakan sejak awal perusahaan, dijadwalkan pensiun di Amazon RDS; tanpa tindakan terpisah, ada kemungkinan upgrade paksa dan downtime paksa
- Syarat upgrade disusun untuk mengurangi risiko operasional
- Melompat ke versi terbaru yang memungkinkan, yaitu Postgres 15.3 untuk Aurora
- Tidak mengizinkan downtime lebih dari 60 detik, dan idealnya downtime sistem 0
- Selesai sebelum tenggat Amazon pada Februari 2024
- Meminimalkan dampak pelanggan, misalnya 0 respons error API
- Menjadikan prosedur sebagai runbook agar dapat digunakan kembali untuk upgrade berikutnya
- Dari 11.9 ke 15.3 berarti upgrade 4 versi mayor, sehingga pendekatan mengulang upgrade in-place 4 kali dikeluarkan dari opsi
Persiapan awal: mengurangi risiko dan observabilitas
- Upgrade Postgres didekati dengan terlebih dahulu membuat daftar risiko, lalu mengurangi risiko yang berdampak besar tetapi mudah dihilangkan lebih awal
- Downtime panjang
- Kehilangan data
- Perubahan performa DB pada workload aplikasi
- Perubahan frekuensi atau perilaku
VACUUM - Perlu tidaknya memindahkan replication slot
- Dengan catatan rilis Postgres, mereka memeriksa perubahan antarversi dan mengidentifikasi risiko seperti perubahan perilaku
VACUUMatau kebutuhan reindexing pada upgrade tertentu - Selama upgrade, metrik sistem dan database harus terus dipantau
- Max TXN ID untuk mencegah transaction wraparound
- Penggunaan CPU DB
- Sesi tunggu pada instance writer
- Latensi query
- Latensi respons API aplikasi
- Knock juga memantau metrik khusus aplikasi, seperti waktu yang dibutuhkan agar permintaan API berubah menjadi notifikasi
- Tanpa metrik yang bisa diperiksa tepat waktu, proses upgrade menjadi seperti berjalan dengan mata tertutup
Pendekatan yang dikecualikan: upgrade in-place dan dump/restore
- Upgrade in-place AWS RDS dijalankan dari konsol AWS; AWS menghentikan DB, menjalankan skrip upgrade, lalu mengembalikannya online
- Proses ini dapat memakan waktu dari beberapa menit hingga beberapa jam atau lebih, tergantung jumlah data dan besarnya perubahan antarversi
- Bahkan setelah DB kembali online, pekerjaan pemeliharaan seperti
VACUUMatauREINDEXmungkin masih diperlukan, sehingga DB belum tentu langsung sepenuhnya siap digunakan - Pendekatan
pg_dumpdanpg_restoremengharuskan semua aplikasi dipisahkan dari DB lama untuk mendapatkan backup yang andal, dan pada DB besar proses dump serta restore itu sendiri memakan waktu lama - Karena sangat mungkin melampaui batas downtime Knock, kedua pendekatan tersebut dikesampingkan
Pendekatan yang dipilih: upgrade berbasis logical replication
- Pilihan akhir adalah pendekatan logical replication menggunakan
PUBLICATIONdanSUBSCRIPTIONPostgres - Alur dasarnya sebagai berikut
- Menjalankan DB baru dengan versi Postgres target
- Memindahkan konfigurasi, extension, struktur tabel, pengguna, dan lain-lain
- Membuat publication di DB lama dan mengonfigurasi subscription di DB baru
- Menambahkan tabel ke publication
- Setelah replikasi selesai, menjalankan pengujian untuk memeriksa risiko yang tersisa
- Setelah konfigurasi DB baru cukup terverifikasi, mengalihkan aplikasi ke DB baru
- Menghapus DB lama
- Mereka dapat bergerak dalam tahap bertahap tanpa menjalankan upgrade besar sekaligus, serta menguji DB baru dengan data nyata dan workload nyata
- Setelah DB baru siap, cutover itu sendiri selesai dalam beberapa detik, sehingga waktu dan cara cutover dapat dikendalikan dengan lebih baik
Inti konfigurasi replikasi
- Logical replication Postgres menggunakan parameter yang diperlukan untuk konfigurasi replication slot, dan pada aplikasi sederhana perubahan utamanya bisa berupa pengaturan
wal_levelkelogical - Jika sudah menggunakan replication slot untuk read replica, failover DB, sinkronisasi data warehouse, dan sebagainya, parameter terkait seperti
max_replication_slotsharus disesuaikan sesuai dokumentasi - Struktur tabel pada DB baru harus sama, tetapi kosong dibanding DB lama
- Snapshot skema dapat dibuat dengan
pg_dumpallmenggunakan opsi--schema-onlydan--no-role-passwords, lalu dibandingkan dengan SQL untuk DB baru guna memperbaiki perbedaannya - Saat membuat publication di DB lama dan subscription di DB baru, opsi utama dikonfigurasi
enabled = false: agar sinkronisasi tidak dimulai sejak awalcreate_slot = true: agar Postgres mengelola replication slotcopy_data = true: secara default menyalin isi tabeldisable_on_error = true: agar subscription dihentikan pada error tak terduga sehingga masalah bisa diperbaiki lalu dilanjutkan
- Jika semua tabel langsung dimasukkan ke publication dengan
FOR ALL TABLES, DB besar dapat mengalami masalah performa; Knock menambahkan tabel satu per satu denganALTER PUBLICATION ... ADD TABLE
Klasifikasi tabel dan strategi replikasi
- Knock membagi tabel berdasarkan ukuran disk dan jumlah tuple
- Tabel kecil yang dapat disinkronkan dalam beberapa menit
- Tabel besar tetapi hampir append-only
- Tabel besar yang sebagian besar row-nya sering diperbarui
- Menurut standar Knock, tabel “kecil” adalah tabel berukuran di bawah 50GB dan di bawah 10 juta tuple
- Di Postgres, tuple adalah unit penyimpanan insert atau update; meski jumlah row sedikit, jika ada banyak tuple yang belum dibersihkan, waktu replikasi bisa menjadi lama
- Menjalankan
VACUUMsebelum replikasi dapat membantu mengurangi jumlah tuple yang perlu disalin DB sumber ke DB tujuan - Waktu sinkronisasi tabel berkaitan langsung dengan ukuran disk dan jumlah tuple; sinkronisasi yang lama dapat mengganggu
VACUUMpada primary DB, yang berujung pada penurunan performa dan risiko transaction wraparound
Replikasi tabel kecil
- Tabel kecil ditangani dengan menambahkan tabel ke publication di DB lama dan melakukan refresh subscription di DB baru
- Penyalinan tabel, sinkronisasi, dan penerapan perubahan berikutnya ditangani oleh Postgres
- Tabel yang sangat kecil dapat disinkronkan dalam waktu kurang dari 1 detik
Replikasi tabel besar append-only
- Tabel besar yang tidak memiliki update atau hanya memiliki update pada row terbaru dapat dibuatkan publication/subscription terpisah dengan
copy_data = false - Knock membedakannya dari replikasi biasa dengan menambahkan sufiks
_nocopypada nama - Perubahan baru direplikasi terlebih dahulu, sementara data historis di-backfill secara terpisah dari backup atau snapshot
- Prosedur yang digunakan di AWS RDS Aurora adalah sebagai berikut
- Membuat snapshot DB produksi
- Memulihkan snapshot ke instance DB baru
- Menambahkan sufiks seperti
_snapshotpada nama tabel DB snapshot yang akan direplikasi - Membuat tabel snapshot dengan skema yang sama di DB tujuan
- Mengonfigurasi publication/subscription dari DB snapshot ke DB tujuan
- Memantau progres replikasi
- Setelah replikasi mengejar ketertinggalan, menggabungkannya ke tabel tujuan sebenarnya dengan
INSERT ... ON CONFLICT DO NOTHING
- Untuk tabel yang sangat besar, proses ini dapat memakan waktu beberapa hari, tetapi karena berjalan di latar belakang, seharusnya tidak memengaruhi lingkungan produksi
- Setelah penggabungan, mereka memeriksa konsistensi dengan membandingkan jumlah row, lalu menghapus tabel snapshot di DB tujuan, subscription snapshot, dan instance DB snapshot
Tabel besar yang sering diperbarui
- Tabel besar yang sebagian besar row-nya sering diperbarui adalah yang paling sulit, dan replikasi yang memakan waktu lama dapat menghambat eksekusi
AUTOVACUUM - Langkah yang dapat dipertimbangkan adalah sebagai berikut
- Memeriksa apakah ukuran tabel dapat dikurangi melalui housekeeping
- Memeriksa apakah
VACUUMbaru-baru ini sudah dijalankan - Meninjau apakah tabel dapat dipartisi menjadi bagian yang lebih kecil
- Memeriksa apakah update row berhenti setelah waktu tertentu sehingga dapat diperlakukan seperti append-only
- Jika DB sumber berada di bawah PG 15, pilihannya terbatas; tabel harus direplikasi seperti tabel kecil dan degradasi layanan dipantau melalui monitoring
- Jika perlu, rollback dapat dilakukan dengan menghapus tabel dari publication dan me-refresh subscription
- Untuk tabel yang terlalu besar, replikasi dapat dimulai pada jam traffic rendah guna mengurangi dampak beban dan aktivitas tulis
Replikasi tabel besar secara terpecah yang tersedia di PG 15 atau lebih baru
- Jika DB sumber adalah PG 15 atau lebih baru, replikasi dapat dibagi ke beberapa publication untuk memindahkan tabel besar dalam potongan yang lebih kecil
- Pendekatan ini bekerja mirip partitioning atau sharding, dengan biaya penggunaan replication slot yang lebih banyak
- Karena Knock berpindah dari 11.9 ke 15.3, mereka tidak dapat menggunakan pendekatan ini dan tidak mengujinya langsung
- Contohnya adalah membagi row ke beberapa publication menggunakan hash primary key dan klausa
WHERE - Ukuran potongan yang menurut Knock dapat dikelola adalah sekitar 100GB berdasarkan data tanpa indeks
Memeriksa dan menghentikan status replikasi
- Setelah tabel ditambahkan ke subscription, statusnya dapat diperiksa pada
pg_subscription_rel.srsubstatedi DB tujuani: inisialisasid: menyalin isi tabelf: penyalinan selesai, menunggu sinkronisasi akhirs: menyelesaikan sinkronisasi awalr: replikasi normal berjalan
- Tahap
dharus mempertahankan transaction ID Postgres lama, sehingga dapat secara efektif menghambatVACUUMdan menyebabkan masalah performa atau transaction ID wraparound - Jika mendekati wraparound, lebih baik menghentikan migrasi dan membaginya menjadi potongan yang lebih kecil
- Untuk menghentikan replikasi tabel tertentu, hapus tabel dari publication DB lama dan refresh subscription DB baru
- Jika hanya men-disable subscription, DB sumber dapat terus menahan transaction ID lama sehingga masalah performa mungkin tidak terselesaikan
- Dalam keadaan darurat, seluruh publication dan subscription dapat dihapus lalu dimulai dari awal, dan Postgres akan membersihkan replication slot terkait
Batasan pemindahan replication slot
- Replication slot Postgres menyimpan log aktivitas DB yang dapat dikonsumsi oleh DB atau aplikasi lain
- Progres slot dilacak dengan Log Sequence Number, yaitu LSN, dan LSN bersifat unik untuk primary Postgres DB
- LSN replication slot dari DB lama tidak dapat disalin begitu saja ke DB baru
- Aplikasi yang mengonsumsi replication slot, seperti alat data warehouse, perlu menentukan strategi migrasi sesuai dokumentasi masing-masing alat
- Jika aplikasi sendiri menggunakan replication slot, mekanisme idempotensi yang dapat menghapus transaksi duplikat antara DB lama dan DB baru akan membantu
Verifikasi akhir
- Setelah semua tabel ditambahkan ke publication dan subscription sudah mengejar ketertinggalan, perlu diverifikasi bahwa tabel saling sesuai
- Karena lag logical replication, DB lama dan DB baru sulit cocok sempurna pada momen yang sama, tetapi perbandingan jumlah row dapat memastikan keduanya cukup dekat
- Knock menulis skrip untuk menghitung jumlah row di DB lama dan DB baru untuk setiap tabel
- Untuk tabel yang memiliki kolom
inserted_at, mereka hanya membandingkan row yang lebih lama dari 10 detik, dengan asumsi data 10 detik terakhir akan segera direplikasi - Pada sebagian tabel, mereka juga membandingkan sampel row acak untuk memastikan isi tabel cocok
Cara cutover aplikasi
- Untuk cutover akhir, aplikasi dapat diubah agar terhubung ke kedua DB
- DB dengan traffic rendah dimigrasikan dengan cara sederhana: mengubah konfigurasi ke DB baru dan me-restart aplikasi
- Pada aplikasi dengan banyak aktivitas bersamaan, mereka harus menghindari conflicting writes antara DB lama dan DB baru
- Skrip cutover Knock bekerja dengan urutan berikut
- Menginstruksikan semua instance aplikasi untuk mengirim query baru ke DB baru
- Memberi waktu 500ms bagi query DB yang sedang berjalan untuk selesai, lalu membatalkannya paksa setelah itu
- Selama 1 detik pertama setelah flag dialihkan, secara sengaja menjeda permintaan ke DB baru agar pending transaction punya waktu untuk direplikasi ke DB baru
- Setelah itu menormalkan aktivitas DB, tetapi mengarahkannya ke DB baru
- Beberapa workload DB khusus dihentikan lalu di-restart agar terhubung kembali ke DB baru
- Knock memastikan bahwa 500ms jauh lebih lama daripada sebagian besar query DB, dan tidak ada error akibat pemutusan koneksi paksa
Penanganan sequence
- Logical replication Postgres tidak menyinkronkan sequence
- Meski nilai sequence digunakan di DB lama, nilai sequence di DB baru tidak bertambah
- Tepat sebelum peralihan feature flag, Knock menjalankan skrip yang terhubung ke kedua DB
- Untuk semua sequence di DB lama, mengambil nilai berikutnya dengan
SELECT nextval('sequence_name') - Di DB baru, memajukan sequence dengan
SELECT setval('sequence_name', value::int4 + 100000)
- Untuk semua sequence di DB lama, mengambil nilai berikutnya dengan
- Cara ini membuat gap pada sequence, tetapi sequence Knock adalah bigint, sehingga melewati 100 ribu nilai pada dasarnya mendekati 0% dari ruang sequence yang tersedia
- Ukuran gap harus disesuaikan dengan skala nilai sequence yang akan digunakan selama cutover aktual
Hal-hal yang perlu dicek sebelum cutover
- Item pemeriksaan sebelum cutover akhir mencakup kesiapan operasional secara luas
- Apakah jumlah row semua tabel sesuai harapan
- Apakah semua subscription dalam status enable dan berjalan tanpa error
- Apakah skema cocok, dan apakah rilis migrasi bisa dibekukan
- Apakah DB baru sudah di-sizing sesuai workload
- Apakah read replica diperlukan untuk menyamakan topologi cluster DB lama dan DB baru
- Apakah
REINDEXdan pemeliharaanVACUUMdasar sudah dilakukan di DB baru - Apakah catatan rilis Postgres sudah diperiksa ulang untuk kemungkinan regresi aplikasi
- Apakah pengujian otomatis dan manual sudah dilakukan pada DB staging versi baru
- Apakah query paling berat sudah diuji beban dengan
pg_bench - Apakah masih ada risiko yang dapat dikurangi
- Apakah prosedur cutover sudah dilatih beberapa kali di lingkungan staging atau test
- Apakah backup DB sudah dibuat tepat sebelum cutover
Hasil cutover sebenarnya
- Knock mereplikasi tabel satu per satu selama beberapa minggu, terutama setelah jam kerja dan pada waktu traffic paling rendah
- Mereka berlatih cutover beberapa kali di lingkungan staging dan menyempurnakan prosedur agar dapat berjalan tanpa banyak intervensi operator
- Setelah replica PG 15 dan kode cutover aplikasi siap, mereka melakukan pemeriksaan akhir dan mengalihkan flag
- Cutover sebenarnya selesai dalam beberapa detik, dan aplikasi tetap berjalan selain latency blip singkat yang disengaja untuk menunggu replikasi
- Setelah itu mereka mengembalikan perubahan aplikasi sementara, mengalihkan semua koneksi secara permanen ke DB baru, serta menghapus subscription di DB baru dan DB lama
- Knock menyelesaikan migrasi tanpa downtime dari Postgres 11.9 ke 15.3
Kesimpulan
- Melompati 4 versi mayor Postgres sekaligus adalah pekerjaan berat, tetapi memungkinkan
- Pendekatan logical replication bisa lebih aman daripada downtime terjadwal karena memungkinkan latihan, pengujian, dan pengerjaan ulang berkali-kali sebelum cutover aktual
- Jika ada masalah selama proses, mereka dapat menghapus publication di DB lama dan memulai ulang, sehingga prosedur dapat dibatalkan tanpa degradasi layanan
- Ketersediaan 100% yang sempurna secara teknis tidak mungkin, tetapi migrasi tanpa downtime membantu menjaga sistem tetap berjalan tanpa gangguan layanan besar
1 komentar
Opini di Hacker News
Cara menyalin seluruh isi tabel satu per satu menimbulkan beban I/O yang terlalu besar, dan tidak akan berhasil untuk tabel yang sangat besar
Cara yang lebih baik adalah membuat replication slot, mengambil snapshot, memulihkannya ke instance baru, memajukan LSN, lalu mereplikasi dari titik itu. Dengan begitu Anda mendapatkan logical replica yang berisi semua data, lalu cukup upgrade replica tersebut
Artikel Instacart menjelaskan caranya: https://archive.ph/K5ZuJ
Kalau ingatan saya benar, ada beberapa kesalahan kecil di artikelnya, tetapi prosedur umumnya berjalan dan saya sudah beberapa kali meng-upgrade instance berskala TB dengan cara ini
Jika logical replication dimulai lebih dulu lalu pg_upgrade dijalankan, ada risiko korupsi. Diskusi terkait ada di pgsql-hackers: https://www.postgresql.org/message-id/flat/20230217075433.u5...
Untuk mengatasinya, pertama buat logical slot, majukan cluster baru sampai ke posisi LSN slot tersebut tetapi jangan mulai logical replication dulu, lalu jalankan pg_upgrade, dan setelah cluster naik di versi PostgreSQL baru, barulah mulai logical replication
Postgres.ai baru-baru ini memakai persis cara ini saat melakukan upgrade tanpa downtime pada beberapa cluster multi-TiB milik GitLab dalam kondisi beban tinggi, dan juga menggunakan PAUSE/RESUME dari PgBouncer. Presentasi Alexander Sosna dijadwalkan akhir pekan ini: https://www.postgresql.eu/events/pgconfeu2023/schedule/sessi...
Progres per tabel memang jauh lebih merepotkan, tetapi terlihat lebih dapat dipercaya
Dengan cara ini, kami berhasil meng-upgrade banyak database yang sangat besar dan aktif
https://www.instacart.com/company/how-its-made/zero-downtime...
Pendekatannya menarik dan terdokumentasi dengan baik, tetapi kalimat “pelanggan modern mengharapkan ketersediaan 100%” terasa mengganjal
Itu bukan preferensi saya sebagai pelanggan, dan bukan juga pengalaman saya sebagai penyedia. Untuk banyak workload, konsistensi jauh lebih penting daripada ketersediaan
Jika penyedia mengumumkan jendela downtime, saya justru sering merasa lebih tenang karena itu terlihat seperti sinyal bahwa mereka menangani data saya dengan hati-hati
Saya ingin membangun kepercayaan baik pada keandalan produk maupun konsistensi workload. Tentu saja, jauh lebih baik mengelola ekspektasi pelanggan dan sengaja mengambil downtime demi uptime yang lebih baik dalam jangka panjang, daripada berpura-pura konsisten tetapi sebenarnya tidak stabil
Membuat pelanggan mengantisipasi jendela maintenance berkala sebelumnya mungkin secara keseluruhan bisa mengarah ke arsitektur yang lebih kokoh. Jika pelanggan membangun pengaman untuk menahan downtime, resiliensi meningkat, dan tim juga mendapat waktu untuk berinvestasi pada produk yang lebih baik ketika mereka bisa mempercayai pelanggan seperti itu
Setelah upgrade versi mayor berikutnya, mungkin saya akan menulis artikel berjudul “menetapkan ekspektasi soal downtime adalah jalan menuju uptime yang sangat tinggi”
Sebagai pelanggan AWS, saya mengharapkan ketersediaan 100%. Karena pelanggan saya tersebar di seluruh dunia dan tidak ada waktu yang bisa saya sisihkan untuk downtime
AWS sekarang mendukung deployment blue/green: https://aws.amazon.com/about-aws/whats-new/2023/10/amazon-rd...
Setelah beberapa kali bolak-balik dengan AWS, eksperimennya macet selama berjam-jam, dan baru belakangan AWS UI mengakui bahwa switchover tidak diterapkan. Untungnya gagal dengan aman, tetapi saya tidak punya keyakinan bahwa waktu switchover sebenarnya bisa dipastikan untuk dataset berukuran GB ke atas
Lain kali mungkin bisa
Ini luar biasa
Saya membuat tool yang mengotomatiskan sebagian besar hal yang Anda alami, dan jika berguna atau Anda ingin mengembangkannya dengan masukan/ide, silakan kapan saja: https://github.com/shayonj/pg_easy_replicate
Temuan dari tabel besar bisa menarik untuk tool seperti ini. Jika bisa membuat penerapan strategi yang tepat per tabel menjadi lebih mudah, ini bisa menjadi tool wajib bagi tim yang melakukan migrasi seperti ini ke depannya
Pernyataan bahwa “untuk layanan seperti Knock, downtime apa pun tidak bisa diterima, terlepas dari apakah terjadwal atau tidak” terasa meragukan
Kalau sistemnya kompleks, pasti ada kegagalan dan downtime. Downtime 15 menit yang diumumkan sebelumnya tidak masalah untuk hampir semua bisnis SaaS. Ini bukan rumah sakit, bukan juga pembangkit listrik
Banyak pekerjaan semu muncul karena layanan dianggap lebih penting daripada kenyataannya. Jika waktu engineering yang dicurahkan di sini dipakai untuk memperbaiki produk atau produktivitas tim pengembang, kemungkinan pengguna akan lebih bahagia. Apalagi jika notifikasi bisa dimasukkan ke antrean lalu dikejar setelah downtime
Kalau ada SLA enterprise dengan ketentuan kompensasi untuk downtime 15 menit, itu bisa dibenarkan, tetapi kebanyakan tidak begitu. Pada kenyataannya, sangat mungkin sudah pernah ada beberapa insiden serupa atau yang lebih lama
Dalam migrasi database, perbedaan beban kerja antara “downtime singkat” dan “tanpa downtime” biasanya cukup besar, jadi ini makin penting. Dalam kasus seperti ini, yang sifatnya sekali jalan, dan ketika versi PostgreSQL terbaru di RDS didukung secara bawaan, menurut saya ini khususnya sulit dibenarkan
Kami juga membahas penjadwalan jendela gangguan, tetapi yang terus kami pikirkan adalah bagaimana melakukan gladi resik upgrade dengan data produksi. Replika PG 15 yang tersinkron dengan data produksi sangat penting untuk memverifikasi apakah workload berjalan sesuai perkiraan
Dengan replika real-time, gladi resik bisa dilakukan dengan dampak minimal pada lingkungan produksi
Pelajaran besar dari migrasi ini adalah betapa bergunanya melacak dan memitigasi setiap risiko yang terpikirkan dalam proyek seperti ini. Pada akhirnya, risiko upgrade in-place tampak lebih besar daripada risiko jalur yang kami pilih, dan itu adalah keputusan yang terpisah dari ada tidaknya jendela gangguan
Sebagai bonus, jika pendekatan ini diperlukan lagi ke depannya, tulisan blog ini akan menjadi titik awal dan menghemat beberapa minggu. Semoga juga membantu tim lain dalam situasi serupa
Epic, salah satu penyedia rekam medis elektronik terbesar di AS, juga memiliki downtime terjadwal setidaknya sebulan sekali untuk upgrade, masing-masing sekitar 30–60 menit
Anda tidak bisa mengendalikan kapan reboot terjadi. Setelah proses dimulai, cutover bisa mulai satu jam, dua jam, atau tiga jam kemudian, dan Anda tidak bisa tahu ataupun mengendalikan kapan reboot terjadi
Jika ada replika, replika akan di-upgrade secara paralel dan reboot pada waktu acak, sehingga makin merepotkan
Jadi jika Anda tidak bisa menerima ketidaktersediaan acak dalam rentang waktu yang bisa berlangsung hingga beberapa jam, tergantung ukuran database, maka untuk upgrade RDS, replikasi logis pada praktiknya adalah satu-satunya cara
Semakin besar instance-nya, semakin sulit masalahnya
Kalau Jira turun 15 menit sehari, biasanya dampaknya tidak besar. Ada pekerjaan lain di antrean, dan dalam skenario terburuk ketika beberapa gangguan bertumpuk pun masih ada pekerjaan dokumentasi yang dijanjikan kepada seseorang
Namun jika seluruh rangkaian produk Atlassian mati bersamaan, jauh lebih sulit mempertahankan pekerjaan penyangga agar orang tetap bisa bekerja. Jika semua aplikasi perusahaan dibuat memakai storage array yang sama, kehilangan produktivitas bisa melonjak dari 5% menjadi 95%
Pesaing seperti itu berarti menempatkan kebutuhan saya di atas kenyamanan mereka sendiri
Gangguan Anda juga berarti gangguan saya
Di hava.io kami sedang menjalani proses ini sekarang
Kami sedang menaikkan AWS RDS PostgreSQL 11.13 ke 15.5
Pada akhirnya kami memilih pendekatan yang relatif sederhana, yaitu replikasi satu arah menggunakan pglogical. Karena pernah melakukan migrasi tanpa downtime dari Google Cloud SQL ke AWS RDS dengan cara yang sama, kami yakin ini akan berjalan tanpa dampak yang terlihat oleh pelanggan
pglogical membuat migrasi semacam ini cukup sederhana. Tidak selalu cepat, tetapi kalau Anda bisa menunggu beberapa hari sementara seluruh database direplikasi bertahap ke instance baru, itu cukup baik
Cara ini juga memberi kebebasan lebih untuk mengubah jenis dan ukuran storage. Karena storage kami diprovisikan berlebihan demi mendapatkan IOPS, kami ingin mengganti jenis storage dan juga mengecilkan ukurannya. Jadi restore snapshot sederhana tidak bisa dipakai
Saya jadi bertanya-tanya apakah ini fitur yang dijanjikan AWS pada tahap “sales engineering”
Kenyataannya, ketika kami terpaksa melakukan upgrade versi mayor, mereka tidak bisa menyediakannya
Mengejutkan bahwa replika tidak bisa diinisialisasi dari backup
Kalau bisa, itu akan mengurangi kerepotan streaming isi database lama yang stabil ke server baru
Dan ini bukan “tanpa downtime”, karena ada downtime beberapa detik saat layanan dialihkan ke server baru
Tulisan itu melewatkan bagaimana konsistensi dipertahankan. Misalnya, aplikasi tidak bisa begitu saja ditempelkan ke kedua server untuk jangka waktu tertentu. Pembacaan mungkin bisa dilayani dari keduanya, tetapi itu pun tidak sempurna, dan penulisan harus selalu hanya menuju satu server
Terakhir, tidak ada opsi rollback. Pekerjaan memindahkan data skala besar seperti ini kadang bermasalah larut malam. Karena itu selalu perlu ada rencana untuk kembali ke tahap sebelumnya dan bisa tidur dengan yakin bahwa layanan tetap hidup pada pagi hari
Terutama setelah transaksi tulis sudah dikirim ke server baru, lalu karena alasan apa pun harus kembali ke server lama, itu sulit, dan data sudah tidak konsisten
Tanpa suatu mekanisme replikasi, atau tanpa mengangkatnya ke lapisan aplikasi, sistem yang dipulihkan akan memiliki penulisan yang hilang
Misalnya, aplikasi bisa diubah untuk menerapkan dual write. Sepengetahuan saya, tim-tim yang mereplatform seluruh aplikasi dari RDBMS ke database yang sepenuhnya berbeda seperti Apache Cassandra juga melakukan hal seperti itu
Dalam situasi kami, dual write terlihat lebih berisiko daripada menyiapkan streaming replication sebagai fitur bawaan PostgreSQL. Namun bagi beberapa tim, itu bisa menjadi pilihan yang lebih baik
Terkait bagian “bukan tanpa downtime” dan “detail mempertahankan konsistensi tidak ada”, tulisan tersebut membahas cukup rinci cara kami menjaga konsistensi dan menghindari downtime API. Intinya, aplikasi terhubung ke kedua database, tetapi belum menjadikan database baru sebagai default
Lalu kami mengirim sinyal peralihan ke semua instance aplikasi dengan LaunchDarkly, dan LaunchDarkly mempertahankan koneksi latensi rendah dengan semua instance
Selama 1 detik pertama setelah sinyal, server memasukkan permintaan database ke antrean agar replikasi bisa menyusul. Karena itu ada lonjakan latensi singkat, tetapi masih dalam batas toleransi yang sengaja kami hitung. Setelah jeda sementara itu, permintaan mengalir seperti biasa, tetapi menuju database baru, dan peralihan selesai
Untuk traffic yang masih tersisa ke database lama, kami juga menambahkan pemutusan paksa koneksi dengan timeout 500 ms. Nilai ini jauh lebih besar daripada waktu kueri p99, sehingga kueri yang sedang berjalan tidak dipaksa berhenti. Dengan ini traffic ke database lama berhenti, dan replikasi punya cukup waktu untuk menyusul
Opsi rollback memang tidak masuk dalam tulisan blog, tetapi kami juga mempertimbangkan membuat database alternatif PG 11.9 dan mereplikasi database 15.3 ke database ketiga itu. Jika harus menghentikan proses, kami bisa roll-forward ke database dengan versi yang sama ini
Setelah beberapa kali melatih prosedur upgrade di staging dan memastikan peluang keberhasilannya, kami memutuskan untuk tidak memakai opsi ini. Karena sudah beberapa kali rehearsal, kami cukup percaya diri saat peralihan sebenarnya. Di production pun kami memvalidasi sebagian workload read-only terhadap instance 15.3 lewat canary deployment, dan memperlakukannya seperti read replica
Untuk menghindari masalah larut malam, kami sengaja melakukannya pada awal malam di akhir pekan. Peralihan dibuat sangat ter-skrip dan dilatih dengan teliti untuk mengurangi risiko kesalahan manusia
Jika terjadi kegagalan fatal, sistem juga sudah siap dikembalikan ke database lama. Dalam kasus ini akan ada sebagian kehilangan data yang sudah masuk ke database baru, dan kami sudah menyiapkan rekonsiliasi untuk bagian-bagian penting. Untuk mengurangi risiko kehilangan data, kami menghentikan sementara sebagian pekerjaan background selama peralihan agar jumlah penulisan berkurang
Detail seperti ini tidak dimasukkan ke blog karena kami ingin lebih berfokus pada detail terkait PostgreSQL daripada pertimbangan yang spesifik untuk Knock. Tim yang ingin menerapkan playbook ini harus selalu membuat daftar risiko dalam konteks mereka sendiri dan memitigasinya
Bagian terkait sequence jelas menarik
Sudah cukup lama saya hampir tidak memakai sequence, dan terutama menggunakan sequential UUID atau UUID v7, atau pendekatan seperti HiLo
https://en.wikipedia.org/wiki/Hi/Lo_algorithm
Caranya membuat sequence 12-bit berdasarkan draf spesifikasi IETF, lalu menyusun UUID dengan menggabungkan milidetik UNIX epoch saat ini dan 62 bit angka acak
Intinya adalah menempatkan
uuidv7_seqdan membuat fungsigenerate_uuidv7()mengembalikan nilai berbentuk UUID v7 dengan memakaiclock_timestamp(),NEXTVAL, danRANDOM()Di banyak tempat kami memakai KSUID dan UUID v4. “Jebakan” ini berlaku untuk semua sequence, jadi saat melakukan migrasi semacam ini, hal tersebut layak disebut sebagai saran umum
[1]: https://segment.com/blog/a-brief-history-of-the-uuid/
Bukan bermaksud meremehkan pekerjaan besar yang berhasil dilakukan, tetapi saya penasaran kenapa tidak melakukan upgrade kecil-kecilan setiap kali versi baru keluar
Sebagai bacaan ini bagus, tetapi rasanya seperti cerita para pelaut yang, meski tahu bisa berakhir tragis, memilih menerobos badai besar secara langsung alih-alih menghindarinya
Dalam kasus ini, apakah upgrade kecil memang bukan opsi? Saya penasaran apakah alasannya semacam “satu upgrade kecil pun punya biaya downtime sebesar upgrade besar, jadi kami menundanya selama mungkin”. Di bagian pembuka memang ada petunjuk seperti itu, tetapi mungkin saya membacanya terlalu jauh
Ini lebih dekat ke “kalau tidak rusak, jangan diperbaiki” daripada “menunda sampai terpojok”, meskipun kami tahu suatu saat harus melompat
Sekalipun jawaban sebenarnya kurang dari 60 detik, dalam perjalanan menuju 15 mereka akan mengalami downtime itu berkali-kali