2 poin oleh GN⁺ 2023-12-14 | 1 komentar | Bagikan ke WhatsApp
  • 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_restore dikesampingkan karena membutuhkan waktu henti yang panjang; mereka memilih pendekatan dengan menyiapkan logical replication berbasis PUBLICATION/SUBSCRIPTION ke DB baru
  • Strategi replikasi dibagi berdasarkan ukuran tabel dan pola penulisan; tabel kecil direplikasi langsung, sedangkan tabel besar yang append-only menggabungkan copy_data = false dengan 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 VACUUM atau 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 VACUUM atau REINDEX mungkin masih diperlukan, sehingga DB belum tentu langsung sepenuhnya siap digunakan
  • Pendekatan pg_dump dan pg_restore mengharuskan 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 PUBLICATION dan SUBSCRIPTION Postgres
  • 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_level ke logical
  • Jika sudah menggunakan replication slot untuk read replica, failover DB, sinkronisasi data warehouse, dan sebagainya, parameter terkait seperti max_replication_slots harus disesuaikan sesuai dokumentasi
  • Struktur tabel pada DB baru harus sama, tetapi kosong dibanding DB lama
  • Snapshot skema dapat dibuat dengan pg_dumpall menggunakan opsi --schema-only dan --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 awal
    • create_slot = true: agar Postgres mengelola replication slot
    • copy_data = true: secara default menyalin isi tabel
    • disable_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 dengan ALTER 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 VACUUM sebelum 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 VACUUM pada 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 _nocopy pada 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 _snapshot pada 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 VACUUM baru-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.srsubstate di DB tujuan
    • i: inisialisasi
    • d: menyalin isi tabel
    • f: penyalinan selesai, menunggu sinkronisasi akhir
    • s: menyelesaikan sinkronisasi awal
    • r: replikasi normal berjalan
  • Tahap d harus mempertahankan transaction ID Postgres lama, sehingga dapat secara efektif menghambat VACUUM dan 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)
  • 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 REINDEX dan pemeliharaan VACUUM dasar 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

 
GN⁺ 2023-12-14
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

    • Metode ini adalah resep yang bagus, tetapi perlu koreksi kecil namun penting pada urutan menyisipkan pg_upgrade
      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...
    • Sebagai OP, saya juga mempertimbangkan metode ini, tetapi saya tidak yakin dengan memajukan LSN secara manual seperti yang diusulkan, dan juga tidak yakin bisa mendeteksi inkonsistensi jika replikasi terlewat
      Progres per tabel memang jauh lebih merepotkan, tetapi terlihat lebih dapat dipercaya
    • Artikelnya sudah diperbarui: https://tech.instacart.com/zero-downtime-postgresql-cutovers...
    • Artikel itu membahas dasar dari cara upgrade Instacart, tetapi sudah cukup lama, dan artikel di bawah ini lebih baik menggambarkan prosedur saat ini
      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

    • Sebagai OP, ini masukan yang bagus
      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”
    • Tergantung siapa pelanggannya
      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...

    • Saya mencobanya sendiri beberapa minggu lalu, dan untuk PostgreSQL sebaiknya belum dipercaya dulu
      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
    • Benar. Sebagai OP, saat itu kami memakai Aurora 11.9 dan belum termasuk yang didukung deployment blue/green
      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

    • Tool yang keren
      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

    • Sebagai OP, memang benar semua layanan punya downtime karena alasan apa pun
      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
    • Dari sudut pandang dokter, menarik bahwa “ini kan bukan rumah sakit” muncul sebagai contoh sistem yang tidak bisa menoleransi downtime
      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
    • Masalahnya adalah di RDS tidak ada cara untuk meng-upgrade instance PostgreSQL dengan downtime terjadwal 15 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
    • Masalah sebenarnya dari downtime adalah ketika semua sistem turun pada saat yang sama
      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%
    • Berbeda dengan pernyataan “downtime 15 menit yang diumumkan sebelumnya tidak masalah untuk hampir semua bisnis SaaS”, bisa saja ada pesaing yang tidak punya downtime setiap bulan
      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

    • Sebagai OP, replika memang bisa diinisialisasi dari backup, tetapi penulisan yang terus terjadi selama backup tidak akan ikut didapat
      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

    • Bagi orang yang ingin tetap menempatkan tanggung jawab pembuatan UUID v7 di dalam database sampai PostgreSQL mendukungnya secara native, fungsi PL/pgSQL bisa membantu
      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_seq dan membuat fungsi generate_uuidv7() mengembalikan nilai berbentuk UUID v7 dengan memakai clock_timestamp(), NEXTVAL, dan RANDOM()
    • Sebagai OP, kami menghindari sequence kecuali di satu bagian aplikasi karena dependensi
      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

    • Sebagai OP, kami kemungkinan akan memakai pendekatan yang sama untuk upgrade minor sekalipun
      Ini lebih dekat ke “kalau tidak rusak, jangan diperbaiki” daripada “menunda sampai terpojok”, meskipun kami tahu suatu saat harus melompat
    • Menaikkan N versi, entah N itu 1 atau 3, hampir sama dari sisi ancaman terhadap ketersediaan
    • Setiap upgrade membawa downtime
      Sekalipun jawaban sebenarnya kurang dari 60 detik, dalam perjalanan menuju 15 mereka akan mengalami downtime itu berkali-kali