MySQL: penyimpanan lokal dan terdistribusi

Database


Di tempat saya bekerja, kami menggunakan MySQL dalam konfigurasi yang dapat diskalakan untuk menangani kebutuhan basis data kami.

Ini berarti bahwa Anda menulis ke server utama, tetapi membaca umumnya pergi ke database replika lebih rendah di pohon replikasi.

Sejumlah persyaratan tambahan yang seharusnya tidak membuat Anda khawatir sebagai pengembang membuatnya sedikit lebih rumit daripada konfigurasi “utama dan beberapa salinan” yang sederhana. Tapi inti dari semuanya adalah ini:

  • Selalu ada salinan database yang dapat dibaca yang sangat dekat dengan aplikasi Anda, dalam hal latensi
  • Selalu ada cukup salinan data di sekitar untuk menjalankan database kami di penyimpanan lokal tanpa membanjiri.

Sifat basis data kami sedemikian rupa sehingga kami membanjiri semua data yang dibaca dengan memori yang cukup sedapat mungkin. Basis data kami adalah mesin memori dalam hal membaca.

Saya bercanda tentang itu:

Anda juga bisa menjadi konsultan kinerja database yang sukses: katakan “beli lebih banyak memori!” dan “tidak ada indeks” jika perlu. Jika Anda bekerja untuk SAP atau Oracle, tambahkan “ini akan mahal”.

Tapi itu benar: membaca sangat buruk untuk database. Ketika database tidak muat di memori, apa beban kerja database:

Bagan berdasarkan data yang direkam dengan blktracedianalisis oleh Workload Intelligence dari Oakgate.

Beban kerja yang ditampilkan di sini terlalu besar dan tidak dapat dilayani dalam InnoDB Buffer Pool. Kami melihat aliran pembacaan yang stabil dari waktu ke waktu, pada beberapa megabita per detik dan hingga 1000 operasi IO per detik (IOPS).

Jika database ini memiliki lebih banyak memori, pembacaan akan mengenai blok cache dalam memori dan akhirnya dilayani dari RAM. Kami melihat permintaan baca di lapisan basis data, tetapi mereka puas dari kumpulan buffer basis data dan tidak mengenai disk. Hanya beban menulis yang tersisa.

Ini adalah pertanyaan yang menarik.

Kita dapat membanjiri pembacaan dengan cache, tetapi kita tidak dapat membanjiri penulisan.

Mereka akhirnya harus pergi ke disk, dan untuk kompatibilitas ACID kita harus menunda COMMIT sampai penulisan mencapai lapisan penyimpanan persisten.

Bergantung pada kebutuhan keberlanjutan kami, kami menuntut tulisan

  • Ini mengenai memori persisten lokal mesin (disk, NVME, atau unit cache yang didukung baterai (BBU)).
  • Setidaknya menabrak mobil kedua atau
  • Itu menyentuh perangkat kedua di zona lain yang dapat dijangkau.

Peningkatan persyaratan menyebabkan lebih banyak latensi dan menurunkan tingkat penulisan maksimum kami karena peningkatan waktu tunggu.

Tapi bacaan yang belum disimpan lebih buruk.

Penulisan dapat membuat antrean yang dalam, tetapi ini jarang terjadi untuk pembacaan. Ini adalah pengamatan kunci.

Ketika kita melakukan penulisan ke penyimpanan, kita dapat melakukannya secara asinkron: kita menjalankan penulisan, dan sementara kita menunda komit dan menunggu penulisan selesai, kita dapat melakukan hal-hal lain. Hal ini dimungkinkan, karena entri lain ini – sebagian besar – independen dari hasil entri sebelumnya, selama jaminan pesanan dihentikan. Menulis adalah aliran yang terus mengalir dan dapat diantrekan.

Membaca tidak pada beberapa tingkatan.

Basis data membaca data dari indeks, dan indeks sebagian besar merupakan struktur data seperti pohon yang seimbang, dengan penyebaran yang besar. Jika Anda memiliki lebih banyak data daripada memori dan indeks yang masuk akal, indeksnya sekitar empat lapisan. Anda memerlukan sekitar empat akses media untuk mencapai data yang diinginkan dari root, dengan asumsi tidak ada cache.

Mengapa hampir empat lapis?

Dalam sebuah pohon, kedalaman pohon adalah logaritma dari jumlah catatan ke basis fanout.

Untuk satu miliar catatan dan penggemar 200 per level, Anda mendapatkan kedalaman pohon 3,91.

Lebih sedikit catatan membuatnya sedikit lebih dangkal: kedalaman 2,60 untuk satu juta catatan dalam 200.

Fanout yang lebih kecil membuatnya sedikit lebih dalam: satu juta record dengan fanout 20 bukannya 200 memberi Anda kedalaman 4,61 (ingat, kami bekerja dengan halaman 16KB, jadi fanout hanya 20 cukup kecil ).

Dalam praktiknya, semua pohon seimbang dalam database kami akan memiliki kedalaman sekitar 4 lapisan, karena log(n)/log(fan-out) secara efektif konstan sekitar 4.

Sering kali, bahkan untuk data yang besar, kami dapat sepenuhnya meng-cache pohon internal, sehingga dikurangi menjadi 4 permintaan pembacaan disk dan satu pembacaan disk yang sebenarnya. Tetapi: dalam pencarian seperti itu, setiap pembacaan disk sepenuhnya bergantung pada hasil pembacaan disk sebelumnya. Bacaan semua serial dengan akses disk.

Yang sekali lagi menekankan pentingnya memori dalam operasi database. “Beli lebih banyak memori, lalu investasikan lebih banyak memori” adalah saran yang sangat masuk akal.

Sekarang, kita sebenarnya tidak hanya membaca data dari sebuah tabel. Seringkali kami menggabungkan dua tabel bersama. Dalam Loop Join, kami menghubungkan dua tabel dengan bentuk SQL

SELECT a.somecolumn, b.someothercolumn
  FROM a JOIN b
    ON a.aid = b.aid
 WHERE <some conditions>

Baris ini dari asemoga menggunakan indeks (lihat di atas untuk membaca indeks), dan kemudian dapatkan baris darinya a Seperti yang diproduksi. akan menggunakannya a.aid nilai yang dipancarkan dan menggunakannya untuk mencari b.aid Baris tabel b.

Sekali lagi, kami tidak tahu baris mana itu b Kami tertarik sebelum kami selesai membaca pintu a. Baca izin b Itu tergantung pada hasil dan karena itu penyelesaian bacaan kita (diindeks). a.

Ini bukan cara yang baik untuk membuat antrean dalam: berhenti sampai pembacaan sebelumnya selesai – kecuali jika kita mengalokasikan cukup memori agar pembacaan tidak terjadi sejak awal.

Kenali database kami yang tersedia. Mereka memiliki informasi ketersediaan kamar hotel. Basis data berjalan pada bare metal, 16C/32T, memori 128GB, dan dua SSD pada satu pengontrol dengan unit cache yang didukung baterai (BBU).

Kotak ini sebagian besar menyediakan pembacaan, 4746 di detik terakhir, dan sebagai replika melihatnya hanya menulis dari hulu.

Kotak saat ini menampung beberapa terabyte data, melayani antara 6.000 dan 12.000 kueri per detik.

Komposisi pernyataan mirip dengan Java: sekitar 50% adalah ekspresi SELECTpersentase yang besar dari SET Perintah mewakili driver JDBC dan sisanya dari DML dan manajemen transaksi.

Bucks saat ini berjalan pada jam 9, tetapi ini masih pagi dan kami memiliki puncaknya di malam hari. Pada 12 beban, ia memiliki kapasitas yang cukup untuk dibawa jika kita kehilangan AZ dan harus memuat pusat data yang gagal ke mesin yang tersisa.

Perangkat keras semacam ini menjadi gelisah pada 24 dengan beban kerja semacam ini.

Kami juga melihat beban baca variabel sekitar 1.000 IOPS, memuncak pada 4.000.

Ini bekerja dengan baik. Seberapa baik?

Kami melihat latensi baca dan tulis pada skala mikrodetik, yaitu 10^-6, sepersejuta detik.

Penundaan baca adalah tirai berlapis dengan puncak 0,2 ms, 0,26 ms, 0,33 ms, 0,45 ms, dan 0,58 ms. Kita bisa menggambar kurva Gaussian besar di atas semuanya, memuncak pada 0.33ms.

Penundaan penulisan… tidak ada?

Saat kami memperbesar, kami melihat bahwa penulisan disk tampaknya terjadi dengan penundaan 40 mikrodetik, 0,04 milidetik. Ini sesuai dengan transfer bus PCIe – transfer bus 256-byte membutuhkan waktu sekitar 100 ns, 40 s bagus untuk waktu bus sekitar 12 KB, dan sebagai perbandingan, 16 KB ditulis dengan ukuran data MySQL halaman. Lihat angka-angkanya.

Ini adalah waktu yang diperlukan untuk mentransfer 16 KB data dari MySQL Buffer Pool dalam RAM ke cache BBU senilai 4 GB pada kartu pengontrol HP SmartArray, yang dianggap persisten secara lokal. CPU ARM pada pengontrol kemudian mengambil semua penulisan ini, mengurutkannya, mengurutkannya, dan menulisnya ke SSD di latar belakang.

Semuanya bekerja dengan sangat baik bersama-sama: blade dicadangkan untuk sekitar €150 per bulan, per perangkat, lebih dari 5 tahun. Menyimpan 1 TB data logis dalam 1 TB ruang disk fisik. Dan itu beroperasi dalam jumlah penundaan yang sangat rendah dan stabil.

Ini tidak berlebihan di tingkat mesin – kami menggunakan teknologi tingkat basis data lainnya untuk mencapai itu. Menggunakan Replikasi, kami menyalin data dari perangkat ini ke mesin lain dan di pusat data lainnya. Hal ini memungkinkan kita untuk menggunakan redundansi untuk kapasitas, tetapi juga untuk ketersediaan.

Hal ini dilakukan dengan biaya yang juga memungkinkan kami untuk sepenuhnya menghapus lapisan cache yang berlebihan seperti memcached – juga menghapus seluruh kelas bug ketidakcocokan cache yang terkait dengan penggunaan memcached hingga tahun 2012 saat kami menghapus cache.

Memperkenalkan penyimpanan terdistribusi ke dalam persamaan secara mendasar mengubah karakteristik penyimpanan yang mendasarinya.

Penyimpanan Anda tidak lagi bersifat lokal, dan untuk alasan di luar cakupan posting ini, penyimpanan Anda selalu direplikasi – biasanya dengan pabrik replikasi n=3. Artinya, 1 TB data dalam database menjadi 3 TB data pada disk, 1 TB pada ketiga mesin yang berbeda.

Lintasan pusat data di pusat data lokal kami membutuhkan waktu sekitar 60 mikrodetik, ditambah waktu untuk menulis data ke penyimpanan persisten lokal menjadikannya 100 mikrodetik, 0,1 milidetik, per lompatan. Beberapa versi dan koordinasi ditambahkan dan hasil akhirnya adalah distribusi waktu penulisan dengan maksimum 0,9 ms untuk penyimpanan yang diuji di sini.

Latensi bacanya sama, tetapi agak lebih rendah – kami memerlukan satu salinan data untuk membacanya, tidak semuanya, jadi kami keluar sekitar 0,6 md.

Grafik latensi untuk latensi baca dan tulis penyimpanan Ceph kami adalah r=0,6 md, w=0,9 md. Ini sangat rendah untuk Ceph – cluster Ceph pada tahun 2015 adalah sekitar 5,0 milidetik.

Menjalankan database dengan indeks yang sebanding di atas memori ini banyak mengubah perilaku database:

Perbandingan waktu latensi: Di ​​atas, grafik perangkat lokal, di bawah Beban kerja yang sebanding pada volume Ceph.

Kami telah membaca dan latensi baca hampir dua kali lipat dari SSD lokal. Kami telah membahas bagaimana pembacaan tidak mengantri dengan baik karena hasil pembacaan berikutnya sering kali bergantung pada hasil pembacaan sebelumnya.

Ini berarti bahwa kami telah mengurangi kapasitas baca instance untuk jumlah koneksi yang sama sebanyak 2.

Kami masing-masing dapat meningkatkan biaya teknik dan moneter

  • Menambahkan memori untuk mematikan pembacaan di kumpulan buffer meningkat
  • Tambahkan kotak ke kolam dengan biaya sampel tambahan
  • Tambahkan koneksi dan kueri database dengan beberapa pekerjaan paralel, coba rekayasa beban kerja Anda menjadi lebih paralel.

Untuk hierarki yang sedang diuji ini, hal kedua terjadi: kami meningkatkan jumlah instance untuk mengatasi beban kueri di bawah latensi baca yang memburuk. Dan kita perlu mencoba hal pertama: gunakan instance dengan lebih banyak memori untuk menghilangkan akses disk.

Untuk posting, gambarnya berubah total:

Tulis latensi yang dibandingkan: Di atas, sebidang perangkat penyimpanan lokal, di bawah beban kerja yang sebanding pada volume Ceph.

Penulisan sekarang membutuhkan waktu 0,9 md, bukan 0,04 md.

Selain itu, sementara histogram latensi tulis adalah Gaussian dengan baik, ia memiliki lebar dasar yang signifikan: 0,9 ms sebenarnya adalah dasar kurva yang berubah dari 0,6 md hingga 1,6 md di sisi lain.

Itu menulis antrian dengan baik dan penyimpanan di sini berkinerja sangat baik di bawah beban paralel di tolok ukur. Begitu banyak penulisan paralel yang seharusnya berfungsi dengan baik: setiap penulisan membutuhkan waktu 0,9 md, tetapi Anda dapat melakukannya secara bersamaan dengan cepat.

Sayangnya, beban kerja transaksional tidak memiliki bentuk itu. Replikasi paralel tunduk pada sejumlah pembatasan pemesanan ulang dan juga tunduk pada pembatasan yang timbul dari rincian implementasi.

Latensi replikasi dari waktu ke waktu: Instance tidak dapat mengikuti beban tulis meskipun replikasi paralel diaktifkan. Hanya modulasi tulis yang tepat yang mencegah penundaan replika lebih lanjut.

Dan itu bahkan bukan satu penulisan tinggi: kombinasi perintah yang ditampilkan di sini bahkan belum menyentuh penghalang 1000 sisipan/detik.

Namun, statistik penyimpanan benar-benar menghancurkan:

Kami melihat sedikit lebih dari 1000 pembacaan dan 1000 penulisan per detik pada volume, yang bagus mengingat latensi yang kami lihat di atas, tetapi tidak memiliki paralelisme. I/O yang kami lihat bagus untuk I/O total 22 MB/s, yang mengarah ke saturasi I/O disk penuh.

Tidak ada cara mudah untuk mengatasi masalah ini, selain membuat perubahan mendalam pada program yang menyebabkan Anda menulisnya secara berbeda. Tapi kami benar-benar tidak ingin menghabiskan waktu pengembang unit bisnis aplikasi “mengkodekan seluk-beluk solusi penyimpanan yang kami pilih” alih-alih “menyelesaikan masalah bisnis”.

Rekomendasi untuk menggunakan penyimpanan lokal di lingkungan virtual masih berlaku, karena memberikan kinerja yang lebih baik dan tidak memberikan redundansi tingkat penyimpanan yang tidak kami gunakan, mengingat replikasi menyediakan kapasitas. Dan Redundansi di tingkat basis data



Source link

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *