Jika pada saat yang sama banyak operasi dilakukan untuk mengubah skema database, layanan tidak dapat bekerja dengan benar saat merekam. Pengembang Vladimir Kolyasinsky menjelaskan operasi apa di PostgreSQL yang membutuhkan kunci jangka panjang dan bagaimana tim Yandex.Connect memastikan bahwa layanan ini hampir sepenuhnya dapat ditulis selama operasi tersebut. Selain itu, Anda akan belajar tentang perpustakaan untuk Django, yang dirancang untuk mengotomatisasi bagian dari proses yang dijelaskan.
Kami memiliki banyak muatan, ribuan RPS, dan waktu henti dalam beberapa menit, belum lagi waktu yang lebih lama, tidak dapat diterima. Diperlukan agar migrasi terjadi tanpa diketahui oleh pengguna. Dan dengan beban seperti itu tidak mungkin untuk bangun jam empat pagi, menggulung sesuatu ketika tidak ada beban, dan pergi tidur lagi - karena bebannya berputar sepanjang waktu.
- Selamat malam semuanya! Nama saya Vladimir, saya telah bekerja di Yandex selama lima tahun. Dua tahun terakhir saya telah mengembangkan layanan dan layanan internal untuk organisasi.
Sedikit tentang apa layanan ini untuk organisasi. Kami telah menggunakan sejumlah besar layanan internal untuk waktu yang lama: wiki untuk menyimpan dan bertukar data, messenger untuk komunikasi cepat dengan kolega, pelacak untuk mengatur proses kerja, formulir untuk melakukan survei ke dalam dan ke luar, serta banyak layanan lainnya.
Beberapa waktu lalu, kami memutuskan bahwa layanan kami keren dan bermanfaat tidak hanya di dalam Yandex, tetapi juga untuk orang di luar. Kami mulai membawa mereka ke platform Yandex.Connect terpadu, menambahkan layanan eksternal yang ada di sana, seperti Mail untuk domain.

Saat ini saya sedang mengembangkan Form Designer dan Wiki. Tumpukan yang digunakan terutama layanan yang ditulis dalam Python dari versi kedua dan ketiga; Django 1.9-1.11. Sebagai basis data, sebagian besar adalah PostgreSQL. Itu juga Seledri dengan MongoDB dan SQS sebagai broker. Semua ini berfungsi di Docker.
Mari kita beralih ke masalah yang kita hadapi. Layanan populer, mereka digunakan oleh ratusan ribu orang setiap hari, data terakumulasi, tabel menjadi lebih dan lebih, dan seiring waktu, banyak operasi mengubah skema basis data, yang dilakukan tanpa disadari oleh pengguna kemarin, mulai mengganggu operasi normal layanan.
Hari ini kita akan berbicara tentang bagaimana kita mengatasi situasi seperti itu dan bagaimana kita mencapai ketersediaan tinggi layanan baca dan tulis.
Pertama, mari kita pertimbangkan operasi apa dengan PostgreSQL yang membutuhkan kunci panjang di atas meja. Dengan mengunci, maksud saya segala jenis kunci yang mengganggu operasi normal tabel - baik itu akses eksklusif, yang mengganggu penulisan dan membaca, atau tingkat penguncian yang lebih lemah yang hanya mencegah penulisan.
Selanjutnya, kita akan melihat bagaimana cara menghindari kunci selama operasi tersebut. Kemudian kita akan berbicara tentang operasi apa dengan PostgreSQL yang awalnya cepat dan tidak memerlukan kunci panjang. Dan pada akhirnya, mari kita bicara tentang perpustakaan kami zero_downtime_migrations, yang kami gunakan untuk mengotomatisasi beberapa teknik yang dijelaskan sebelumnya untuk menghindari kunci panjang.
Operasi yang membutuhkan kunci panjang:

Membuat indeks. Secara default, ini tidak memblokir operasi baca dalam tabel, tetapi semua operasi penulisan akan diblokir selama indeks dibuat, sehingga, layanan hanya akan dibaca.
Juga, operasi seperti itu termasuk menambahkan kolom baru dengan nilai default, karena di bawah tenda PostgreSQL akan menimpa seluruh tabel, dan untuk kali ini akan diblokir baik untuk membaca dan menulis. Selain itu, semua indeksnya akan ditimpa.
Tentang mengubah jenis kolom - hal serupa akan terjadi, pelat juga akan ditimpa lagi. Perlu dicatat bahwa ini tidak hanya membutuhkan waktu lama di meja besar, tetapi juga untuk waktu yang singkat membutuhkan hingga dua kali lipat jumlah memori bebas yang ditempati oleh tabel.
Juga, operasi VACUUM FULL memerlukan tingkat penguncian yang sama dengan operasi sebelumnya - ini adalah akses eksklusif. VACUUM FULL juga akan memblokir semua operasi baca dan tulis ke tabel.
Dua operasi terakhir menambahkan properti unik ke kolom dan, secara umum, menambahkan CONSTRAINT. Mereka juga memerlukan penguncian selama durasi verifikasi data, meskipun mereka membutuhkan waktu jauh lebih sedikit daripada yang dipertimbangkan sebelumnya, karena mereka tidak menimpa tabel di bawah tenda.


Membuat indeks. Ini cukup sederhana di sini, dapat dibuat menggunakan kata kunci CONCURRENTLY. Apa bedanya? Operasi ini akan memakan waktu lebih banyak, karena bukan hanya satu, tetapi beberapa melewati tabel akan dilakukan, dan itu juga akan menunggu selesainya semua operasi saat ini yang berpotensi mengubah indeks. Dan itu juga bisa gagal - misalnya, jika indeks unik dilanggar saat membuat indeks unik. Maka indeks akan ditandai sebagai tidak valid, dan itu perlu dihapus dan diciptakan kembali. Perintah REINDEX tidak direkomendasikan, karena ia bekerja sama dengan CREATE INDEX biasa, yaitu, ia akan mengunci tabel untuk penulisan.
Mengenai penghapusan indeks - mulai dari versi 9.3, Anda juga dapat menghapus indeks CONCURRENTLY untuk menghindari pemblokiran selama penghapusan, meskipun secara umum itu adalah operasi yang sangat cepat.

Mari kita lihat menambahkan kolom baru dengan nilai default. Ini adalah operasi standar yang dilakukan ketika kita ingin menjalankan perintah seperti itu, termasuk Django melakukan operasi seperti itu.
Bagaimana saya bisa menulis ulang untuk menghindari menimpa tabel? Pertama, dalam satu transaksi, tambahkan kolom baru tanpa nilai default, dan tambahkan nilai default dalam permintaan terpisah. Apa bedanya di sini? Saat kami menambahkan nilai default ke kolom yang ada, ini tidak mengubah data yang ada di tabel. Hanya metadata yang berubah. Artinya, untuk semua baris baru nilai default ini sudah akan dijamin. Tetap bagi kami untuk memperbarui semua baris yang ada di tabel pada saat perintah ini dijalankan. Apa yang akan kita lakukan dalam kumpulan beberapa ribu salinan agar tidak memblokir untuk waktu yang lama sejumlah besar data.
Setelah kami memperbarui semua data, tetap hanya menjalankan SET NOT NULL jika kami membuat kolom NOT NULL. Jika kita tidak membuat, maka jangan. Dengan cara ini Anda bisa menghindari menimpa tabel saat melakukan perubahan semacam ini.
Urutan perintah semacam itu membutuhkan waktu lebih lama daripada pelaksanaan perintah biasa, karena itu tergantung pada ukuran tabel dan jumlah indeks di dalamnya, dan perintah yang biasa hanya memblokir semua operasi dan menimpa tabel terlepas dari beban, karena tidak ada beban saat ini. Tetapi ini tidak terlalu menjadi masalah, karena selama operasi meja tersedia untuk membaca dan menulis. Butuh waktu lama, Anda hanya perlu mengikuti ini dan hanya itu.

Tentang mengubah jenis kolom. Pendekatannya mirip dengan menambahkan kolom dengan nilai default. Pertama-tama kita menambahkan kolom terpisah dari jenis yang kita butuhkan, kemudian menambahkan pemicu untuk mengubah data di kolom asli untuk menulis ke kedua kolom sekaligus, ke yang baru dengan jenis data yang kita butuhkan. Untuk semua entri baru, mereka akan langsung menuju ke kedua kolom ini. Kami perlu memperbarui semua yang sudah ada. Apa yang kami lakukan dalam porsi, seperti pada slide sebelumnya, serupa.
Setelah itu, tetap dalam satu transaksi untuk menghapus pelatuk, menghapus kolom lama dan mengganti nama kolom lama menjadi yang baru. Dengan demikian, kami mencapai hasil yang sama: kami mengubah jenis kolom, sementara mengunci tabel tidak lama.

Tentang menambahkan kolom unik. Kunci diambil pada saat penciptaan. Ini dapat dihindari jika Anda tahu bahwa keunikan di PostgreSQL dijamin dengan membuat indeks yang unik. Kami sendiri dapat membangun indeks unik yang diperlukan menggunakan CONCURRENTLY. Dan setelah membangun indeks ini, buat CONSTRAINT menggunakan indeks ini. Setelah ini, definisi indeks awal dari tabel akan hilang, dan hasil bahwa definisi tabel akan menunjukkan kepada kita tidak akan ada perbedaan setelah melakukan dua operasi ini.

Dan secara umum, saat menambahkan CONSTRAINT. Anda dapat menggunakan teknik ini untuk menghindari pemblokiran saat memeriksa data. Kami pertama-tama menambahkan CONSTRAINT dengan kata kunci TIDAK VALID. Ini berarti bahwa CONSTRAINT ini tidak dijamin akan dieksekusi untuk semua baris dalam tabel. Tetapi pada saat yang sama, untuk semua baris baru, CONSTRAINT ini sudah akan diterapkan, dan pengecualian yang sesuai akan dilemparkan jika tidak dieksekusi.
Kami hanya dapat memvalidasi semua nilai yang ada, yang dapat dilakukan dengan perintah VALIDATE CONSTRAINT yang terpisah, dan pada saat yang sama perintah ini tidak lagi mengganggu membaca atau menulis ke tabel. Tabel untuk saat ini akan tersedia.
Operasi yang awalnya bekerja cepat di PostgreSQL dan tidak memerlukan kunci panjang:

Salah satu operasi ini adalah menambahkan kolom tanpa nilai default dan batasan apa pun. Karena tidak ada perubahan yang dilakukan pada tabel itu sendiri, hanya meta-datanya yang berubah. Dan semua nilai NULL yang kita lihat sebagai hasil SELECT dicampur hanya dalam output.
Selain itu, menambahkan nilai default ke label yang ada adalah operasi cepat karena hanya meta data yang berubah. Tabel dan kunci diambil secara harfiah selama beberapa milidetik yang diperlukan untuk memasukkan informasi ini.
Juga, operasi cepat pengaturan SET TIDAK NULL, di sini dibutuhkan sedikit lebih lama dari yang dijelaskan sebelumnya, sekitar beberapa detik per tabel dari 30 juta catatan. Waktu ini juga dapat dihindari jika itu penting.
Mengganti nama kolom, mengubah panjang kolom juga tidak menyebabkan menimpa kolom. Menghapus kolom dan, secara umum, banyak entitas di PostgreSQL juga merupakan operasi cepat.

Mengenai penambahan kolom NOT NULL. Untuk menghindari pemblokiran selama validasi, Anda dapat melakukan metode yang disebutkan sebelumnya - tambahkan CONSTRAINT yang sesuai dengan PERIKSA (kolom BUKAN NULL) BUKAN VALID, dan validasi dengan perintah terpisah.
Perbedaannya secara umum adalah bahwa pembatasan ini akan ada di level tabel, dan bukan di level kolom dalam definisi tabel. Perbedaan lainnya adalah dapat mempengaruhi kinerja, sekitar satu persen. Dalam hal ini, tidak akan ada pemblokiran, jika layanan sangat dimuat, bahkan beberapa detik pemblokiran dapat menyebabkan antrian transaksi yang menumpuk dan akan ada masalah pada layanan.

Menghapus data dalam PostgreSQL umumnya merupakan operasi cepat, karena data tidak segera dihapus, hanya kolom yang ditandai usang dalam atribut tabel, dan data sebenarnya akan dihapus hanya setelah dimulainya vakum berikutnya.

Mari kita bicara tentang
perpustakaan . Saya berbicara tentang Django, migrasi. Secara umum, Django adalah pustaka untuk Python, sebuah kerangka kerja web, awalnya dibuat untuk dengan cepat membuat situs web seperti berita, sejak itu telah ditingkatkan secara signifikan. Ada sistem ORM yang memungkinkan Anda untuk berkomunikasi dengan catatan dalam database, dengan tabel, seolah-olah mereka adalah objek atau kelas Python. Artinya, setiap tabel memiliki kelasnya sendiri dalam Python. Dan ketika kita membuat perubahan pada kode Python kita, yaitu, kita menambahkan atribut baru seperti kolom ke tabel, Django selama proses membuat pemberitahuan migrasi perubahan ini, dan membuat file migrasi untuk membuat perubahan mirror ke database itu sendiri sehingga mereka tidak berbeda.
Perpustakaan ditulis untuk mengotomatisasi beberapa teknik yang dibahas sebelumnya untuk menghindari kunci panjang di atas meja selama migrasi tersebut. Ini telah bekerja dengan Django sejak versi 1.8 hingga 2.1 inklusif, dan Python dari 2.7 hingga 3.7 inklusif.
Mengenai fitur perpustakaan saat ini, ini menambahkan kolom dengan nilai default tanpa kunci, nullable atau tidak, ini membuat indeks CONCURRENTLY, serta kemampuan untuk me-restart ketika crash. Dalam implementasi Django standar, jika kita menambahkan kolom dengan nilai default, tabel terkunci, dan jika besar, mungkin 40 menit mengunci pengalaman saya. Tabel terkunci, dan hanya itu, tunggu sampai perubahan disalin dan dibuat. 30 menit berlalu - mereka menangkap kesalahan koneksi ke database, migrasi turun, perubahan tidak dilakukan, dan Anda harus memulai lagi, tunggu 40 menit lagi, lagi-lagi memblokir tabel untuk saat ini.

Perpustakaan memungkinkan Anda untuk melanjutkan migrasi dari tempat di mana ia terputus. Ketika Anda crash dan restart, kotak dialog ditampilkan di mana ada berbagai opsi untuk tindakan, yaitu, Anda dapat mengatakan untuk terus memperbarui data. Ini biasanya pembaruan data karena ini adalah proses terpanjang. Migrasi hanya akan melanjutkan dari tempat sebelumnya. Operasi semacam itu juga membutuhkan waktu lebih lama daripada operasi standar dengan penguncian meja, tetapi pada saat yang sama, layanan tetap beroperasi saat ini.

Tentang koneksi secara keseluruhan. Ada dokumentasi; singkatnya, Anda perlu mengganti mesin di pengaturan basis data Django dengan mesin dari perpustakaan. Juga ada berbagai mixin jika Anda menggunakan mesin Anda untuk terhubung.

Contoh pekerjaan adalah tentang menambahkan kolom dengan nilai default. Di sini kita menambahkan kolom dengan nilai boolean, Benar secara default. Operasi apa yang dilakukan oleh SchemaEditor standar? Operasi yang dapat Anda lihat jika Anda menjalankan migrasi SQL. Ini cukup berguna, dengan tipe migrasi yang sama, tidak selalu jelas apa yang sebenarnya bisa diubah oleh Django di sana. Dan bermanfaat untuk memulai dan melihat apakah operasi yang diharapkan oleh kami selesai dan jika sesuatu yang berlebihan dan tidak perlu telah ada di sana.
Perintah apa yang dijalankan oleh SchemaEditor? Pertama, kolom baru ditambahkan ke satu transaksi, nilai default ditambahkan. Kemudian, hingga pembaruan tersebut kembali sehingga tidak diperbarui, data akan diperbarui.
Kemudian SET NOT NULL diatur pada kolom, dan nilai default akan dihapus, mengulangi perilaku Django, yang menyimpan nilai default tidak dalam database, tetapi pada level logika dalam kode.
Di sini, secara umum, ada juga ruang untuk tumbuh. Misalnya, Anda bisa membuat indeks bantu untuk menemukan baris dengan cepat dengan nilai NULL saat Anda mendekati memperbarui seluruh tabel.

Anda juga dapat memperbaiki id maksimum untuk waktu pembaruan ketika kami memulai migrasi, sehingga dengan id Anda dapat dengan cepat menemukan nilai yang belum kami perbarui.
Secara umum, perpustakaan berkembang, kami menerima permintaan kumpulan. Siapa yang peduli - bergabung.
Perlu diperhatikan bahwa dengan pertumbuhan DB, migrasi memiliki properti yang tak terhindarkan untuk melambat. Anda perlu melacak penguncian yang diambil tabel, menjalankan migrasi SQL untuk melihat operasi apa yang diterapkan. Untuk bagian kami, di Yandex.Connect kami menggunakan perpustakaan ini di mana kemampuannya memungkinkan. Dan di mana mereka tidak mengizinkannya, kita sendiri, dengan tangan kita sendiri, migrasi Django palsu, menjalankan kueri SQL kita.
Dengan demikian, kami mencapai ketersediaan tinggi layanan baca dan tulis. Kami memiliki banyak muatan, ribuan RPS, dan waktu henti dalam beberapa menit, belum lagi waktu yang lebih lama, tidak dapat diterima. Diperlukan agar migrasi terjadi tanpa diketahui oleh pengguna. Dan dengan beban seperti itu, tidak mungkin untuk bangun jam empat pagi, menggulung sesuatu ketika tidak ada beban, dan pergi tidur lagi - karena beban berputar sepanjang waktu.
Perlu dicatat bahwa bahkan operasi cepat di PostgreSQL masih dapat menyebabkan perlambatan layanan dan kesalahan karena cara antrian kunci bekerja di PostgreSQL.
Bayangkan sebuah operasi diluncurkan, bahkan untuk beberapa milidetik, membutuhkan akses eksklusif. Contoh operasi semacam itu adalah menambahkan kolom tanpa nilai default. Bayangkan bahwa pada saat diluncurkan dalam transaksi lain, ada beberapa operasi panjang lainnya - katakanlah, PILIH dengan agregasi. Dalam hal ini, operasi kami akan mengantri untuknya. Ini akan terjadi karena akses konflik eksklusif dengan semua jenis kunci lainnya.
Sementara operasi kami menambahkan kolom sedang menunggu kunci, semua yang lain akan mengantri untuk itu dan tidak akan dieksekusi sampai selesai. Pada saat yang sama, operasi yang dilakukan - PILIH dengan agregasi - mungkin tidak bertentangan dengan yang lain, dan jika bukan karena pembuatan kolom kami, mereka tidak akan berdiri dalam antrian, tetapi akan dieksekusi secara paralel.
Situasi ini dapat menciptakan masalah besar pada layanan. Oleh karena itu, sebelum memulai ALTER TABLE atau operasi lain yang memerlukan akses penguncian eksklusif, Anda perlu mencari sehingga permintaan panjang tidak pergi ke database saat ini. Atau Anda cukup memasukkan batas waktu log yang sangat kecil. Kemudian, jika tidak mungkin mengambil kunci dengan cepat, operasi akan jatuh. Kami hanya bisa memulai kembali, dan tidak mengunci meja untuk waktu yang lama, sementara operasi akan menunggu pemberian hibah untuk kunci. Itu saja, terima kasih.