Mysql
 sql >> Teknologi Basis Data >  >> RDS >> Mysql

Mempartisi tabel miliar baris data sepak bola menggunakan konteks data

Pada artikel ini, Anda akan mempelajari cara menggunakan semantik di balik data Anda saat Anda mempartisi database Anda. Ini secara drastis dapat meningkatkan kinerja aplikasi Anda. Dan, yang paling penting, Anda akan menemukan bahwa Anda harus menyesuaikan kriteria partisi Anda dengan domain aplikasi unik Anda.

Saya telah berkolaborasi dengan startup untuk mengembangkan aplikasi web bagi pakar olahraga untuk membuat keputusan dan menjelajahi data. Aplikasi ini mendukung olahraga apa pun, tetapi kami berbasis di Eropa - dan orang Eropa menyukai sepak bola. Masing-masing dari ratusan game yang dimainkan setiap hari di seluruh dunia hadir dengan ribuan baris. Hanya dalam beberapa bulan, tabel Acara di aplikasi kami mencapai setengah miliar baris!

Dengan memahami bagaimana pakar sepak bola menanyakan data kami, kami dapat mempartisi basis data dengan cerdas. Peningkatan waktu rata-rata pada tabel baru ini adalah antara 20x dan 40x lebih cepat. Peningkatan waktu rata-rata pada semua kueri adalah 5X hingga 10X.

Sekarang mari kita selidiki skenario ini dan pelajari mengapa Anda tidak dapat mengabaikan konteks data saat mempartisi database.

Menyajikan konteksnya

Aplikasi olahraga kami menawarkan data mentah dan agregat, meskipun para profesional yang mengadopsinya lebih memilih yang terakhir. Basis data yang mendasarinya berisi terabyte data yang kompleks, tidak terstruktur, dan heterogen dari beberapa penyedia. Jadi, tantangan terbesarnya adalah merancang database yang andal, cepat, dan mudah dijelajahi.

Domain aplikasi

Dalam industri ini, banyak penyedia menawarkan klien mereka akses ke acara pertandingan sepak bola paling penting. Secara khusus, mereka memberi Anda data yang terkait dengan apa yang terjadi selama pertandingan, seperti gol, assist, kartu kuning, operan, dan banyak lagi. Tabel yang berisi data ini sejauh ini merupakan tabel terbesar yang pernah kami tangani.

Spesifikasi, teknologi, dan arsitektur VPS

Tim saya telah mengembangkan aplikasi backend yang menyediakan fitur eksplorasi data paling penting. Kami mengadopsi Kotlin v1.6 yang berjalan di atas JVM (Java Virtual Machine) sebagai bahasa pemrograman, Spring Boot 2.5.3 sebagai framework, dan Hibernate 5.4.32.Final sebagai ORM (Object Relational Mapping). Alasan utama mengapa kami memilih tumpukan teknologi ini adalah karena kecepatan adalah salah satu persyaratan bisnis yang paling penting. Jadi, kami membutuhkan teknologi yang dapat memanfaatkan pemrosesan multi-utas yang berat, dan Spring Boot ternyata menjadi solusi yang andal.

Kami menerapkan backend kami pada VPS 8CPU 16GB melalui wadah Docker yang dikelola oleh Dokku. Itu dapat menggunakan RAM paling banyak 15GB. Ini karena satu GB RAM didedikasikan untuk sistem caching berbasis Redis. Kami menambahkannya untuk meningkatkan kinerja dan menghindari beban berlebih pada backend dengan operasi berulang.

Database dan struktur tabel

Untuk database, kami memutuskan untuk memilih MySQL 8. Sebuah 8GB dan 2 CPU VPS saat ini menghosting server database, yang mendukung hingga 200 koneksi bersamaan. Aplikasi backend dan database berada di server farm yang sama untuk menghindari overhead komunikasi. Kami merancang struktur database untuk menghindari duplikasi dan dengan mempertimbangkan kinerja. Kami memutuskan untuk mengadopsi database relasional karena kami ingin memiliki struktur yang konsisten untuk mengonversi data yang diterima dari penyedia. Dengan cara ini, kami menstandardisasi data olahraga, sehingga lebih mudah untuk dijelajahi dan disajikan kepada pengguna akhir.

Basis data berisi ratusan tabel pada saat penulisan, dan saya tidak dapat menyajikan semuanya karena NDA yang saya tanda tangani. Untungnya, satu tabel sudah cukup untuk menganalisis secara menyeluruh mengapa kami akhirnya mengadopsi partisi berbasis konteks data yang akan Anda lihat. Tantangan sebenarnya datang ketika kami mulai melakukan kueri berat di tabel Acara. Namun sebelum membahasnya, mari kita lihat seperti apa tabel Acara:

Seperti yang Anda lihat, ini tidak melibatkan banyak kolom, tetapi perlu diingat bahwa saya harus menghilangkan beberapa kolom untuk alasan kerahasiaan. Tapi apa sebenarnya yang penting di sini adalah parameterId dan gameId kolom. Kami menggunakan dua kunci asing ini untuk memilih jenis parameter (misalnya, gol, kartu kuning, operan, penalti) dan permainan di mana itu terjadi.

Masalah kinerja

Tabel Acara mencapai setengah miliar baris hanya dalam beberapa bulan. Seperti yang telah kita bahas secara mendalam di posting blog ini, masalah utamanya adalah kita perlu melakukan operasi agregat menggunakan kueri IN yang lambat. Ini karena apa yang terjadi selama pertandingan tidak begitu penting. Sebaliknya, pakar olahraga ingin menganalisis data gabungan untuk menemukan tren dan membuat keputusan berdasarkan tren tersebut.

Selain itu, meskipun mereka umumnya menganalisis seluruh musim atau 5 atau 10 pertandingan terakhir, pengguna sering kali ingin mengecualikan beberapa permainan tertentu dari analisis mereka. Ini karena mereka tidak ingin permainan yang dimainkan sangat buruk atau baik untuk mempolarisasi hasil mereka. Kami tidak dapat membuat data agregat sebelumnya karena kami harus melakukan ini pada semua kemungkinan kombinasi, yang tidak mungkin dilakukan. Jadi, kita harus menyimpan semua data dan menggabungkannya dengan cepat.

Memahami Masalah Performa

Sekarang, mari selami aspek utama yang menyebabkan masalah kinerja yang harus kita hadapi.

Tabel sejuta baris lambat

Jika Anda pernah berurusan dengan tabel yang berisi ratusan juta baris, Anda tahu bahwa mereka pada dasarnya lambat. Anda bahkan tidak dapat berpikir untuk menjalankan GABUNG pada tabel sebesar itu. Namun, Anda dapat melakukan kueri SELECT dalam waktu yang wajar. Ini terutama benar ketika kueri ini melibatkan kondisi WHERE sederhana. Di sisi lain, mereka menjadi sangat lambat saat menggunakan fungsi agregat atau klausa IN. Dalam kasus ini, mereka dapat dengan mudah memakan waktu hingga 80 detik, yang terlalu banyak.

Indeks tidak cukup

Untuk meningkatkan kinerja, kami memutuskan untuk menentukan beberapa indeks. Ini adalah pendekatan pertama kami untuk menemukan solusi untuk masalah kinerja. Tapi, sayangnya, ini menyebabkan masalah lain. Indeks membutuhkan waktu dan ruang. Ini umumnya tidak signifikan, tetapi tidak ketika berhadapan dengan tabel besar seperti itu. Ternyata mendefinisikan indeks kompleks berdasarkan kueri paling umum membutuhkan waktu beberapa jam dan GB. Juga, indeks sangat membantu tetapi tidak ajaib.

Partisi basis data berbasis konteks data sebagai solusi

Karena kami tidak dapat memecahkan masalah kinerja dengan indeks yang ditentukan khusus, kami memutuskan untuk mencoba pendekatan baru. Kami berbicara dengan pakar lain, mencari solusi online, membaca artikel berdasarkan skenario serupa, dan akhirnya memutuskan bahwa mempartisi database adalah pendekatan yang tepat untuk diikuti.

Mengapa partisi tradisional mungkin bukan pendekatan yang tepat

Sebelum mempartisi semua tabel terbesar kami, kami mempelajari topik baik di dokumentasi resmi MySQL dan di artikel menarik. Meskipun kami semua sepakat bahwa ini adalah cara yang harus dilakukan, kami juga menyadari bahwa menerapkan partisi tanpa mempertimbangkan domain aplikasi khusus kami akan menjadi kesalahan. Secara khusus, kami memahami betapa pentingnya menemukan kriteria yang tepat saat mempartisi database. Beberapa ahli dalam partisi mengajarkan kita bahwa pendekatan tradisional adalah partisi pada jumlah baris. Tapi kami ingin menemukan sesuatu yang lebih cerdas dan lebih efisien dari itu.

Menyelidiki domain aplikasi untuk menemukan kriteria partisi

Kami mempelajari pelajaran penting dengan menganalisis domain aplikasi dan mewawancarai pengguna kami. Pakar olahraga cenderung menganalisis data gabungan dari pertandingan di kompetisi yang sama. Misalnya, kompetisi dalam sepak bola dapat berupa liga, turnamen, atau pertandingan tunggal di mana Anda dapat memenangkan trofi. Ada ribuan kompetisi yang berbeda. Yang paling penting di Eropa adalah Liga Champions, Liga Premier, LaLiga, Serie A, Bundesliga, Eredivisie, Liga 1, dan Liga Primeira.

Ini berarti bahwa pengguna kami sangat jarang memperhitungkan data yang berasal dari kompetisi yang berbeda. Selain itu, mereka lebih suka menjelajahi data musim demi musim. Dengan kata lain, mereka jarang meninggalkan konteks yang diwakili oleh kompetisi olahraga yang dimainkan pada musim tertentu. Struktur database kami menyatakan konsep ini dengan tabel yang disebut SeasonCompetition , yang tujuannya adalah untuk mengaitkan kompetisi dengan musim tertentu. Jadi, kami menyadari bahwa pendekatan yang baik adalah dengan mempartisi tabel yang lebih besar menjadi sub-tabel yang terkait dengan SeasonCompetition tertentu. contoh.

Secara khusus, kami mendefinisikan format nama berikut untuk tabel baru ini:<tableName>_<seasonCompetitionId> .

Akibatnya, jika kita memiliki 100 baris di SeasonCompetition tabel, kita harus membagi Events yang besar tabel ke dalam Events_1 yang lebih kecil , Events_2 , …, Events_100 tabel. Berdasarkan analisis kami, pendekatan ini akan menghasilkan peningkatan kinerja yang cukup besar dalam kasus rata-rata, meskipun memperkenalkan beberapa overhead dalam kasus yang paling jarang.

Mencocokkan kriteria dengan kueri paling umum

Sebelum mengkodekan dan meluncurkan skrip untuk menjalankan operasi yang kompleks dan berpotensi tidak dapat dikembalikan ini, kami memvalidasi studi kami dengan melihat kueri paling umum yang dilakukan oleh aplikasi backend kami. Namun saat melakukannya, kami menemukan bahwa sebagian besar kueri hanya melibatkan game yang dimainkan dalam SeasonCompetition. Ini meyakinkan kami bahwa kami benar. Jadi kami mempartisi semua tabel besar dalam database dengan pendekatan yang baru saja ditentukan.


SELECT AVG('value') as 'value', SUM('minutes') as 'minutes'
FROM 'Events'
WHERE 'parameterId' = 15 AND 'gameId' IN(223,241,245,212,201,299,187,304,187,205)
GROUP BY 'teamId'

Sekarang, mari kita pelajari pro dan kontra dari keputusan ini.

Kelebihan

  • Menjalankan kueri pada tabel yang berisi paling banyak setengah juta baris jauh lebih efektif daripada melakukannya di tabel dengan setengah miliar baris, terutama jika menyangkut kueri agregat.
  • Tabel yang lebih kecil lebih mudah dikelola dan diperbarui. Menambahkan kolom atau indeks bahkan tidak sebanding dengan sebelumnya dalam hal ruang dan waktu. Plus, setiap SeasonCompetition berbeda dan memerlukan analisis yang berbeda. Akibatnya, ini mungkin memerlukan kolom dan indeks khusus, dan partisi yang disebutkan di atas memungkinkan kita untuk menangani ini dengan mudah.
  • Penyedia mungkin mengubah beberapa data. Ini memaksa kami untuk melakukan kueri penghapusan dan pembaruan, yang jauh lebih cepat pada tabel kecil seperti itu. Plus, mereka selalu hanya memperhatikan beberapa game dari SeasonCompetition tertentu , jadi sekarang kita hanya perlu mengoperasikan satu tabel saja.

Kontra

  • Sebelum membuat query pada sub-tabel ini, kita perlu mengetahui seasonCompetitionId terkait dengan permainan yang menarik. Ini karena seasonCompetitionId nilai digunakan dalam nama tabel. Oleh karena itu, backend kami perlu mengambil info ini sebelum menjalankan kueri dengan melihat game dalam analisis, yang menunjukkan sedikit overhead.
  • Saat kueri melibatkan sekumpulan game yang melibatkan banyak SeasonCompetitions , aplikasi backend harus menjalankan kueri pada setiap sub-tabel. Jadi, dalam kasus ini, kita tidak bisa lagi menggabungkan data di tingkat database, dan kita harus melakukannya di tingkat aplikasi. Ini memperkenalkan beberapa kompleksitas dalam logika backend. Pada saat yang sama, kita dapat menjalankan kueri ini secara paralel. Selain itu, kami dapat menggabungkan data yang diambil secara efisien dan paralel.
  • Mengelola database dengan ribuan tabel bukanlah hal yang mudah dan dapat menjadi tantangan untuk dijelajahi di klien. Demikian pula, menambahkan kolom baru atau memperbarui kolom yang ada di setiap tabel tidak praktis dan memerlukan skrip khusus.

Pengaruh partisi berbasis konteks data pada kinerja

Sekarang mari kita lihat peningkatan waktu yang dicapai saat mengeksekusi kueri di database yang dipartisi baru.

  • Peningkatan waktu dalam kasus rata-rata (kueri hanya melibatkan satu SeasonCompetition ):dari 20x hingga 40x
  • Peningkatan waktu dalam kasus umum (kueri yang melibatkan satu atau beberapa SeasonCompetitions ):dari 5x hingga 10x

Pikiran terakhir

Mempartisi database Anda tidak diragukan lagi merupakan cara terbaik untuk meningkatkan kinerja, terutama pada database besar. Namun, melakukannya tanpa mempertimbangkan domain aplikasi khusus Anda mungkin merupakan kesalahan atau mengarah pada solusi yang tidak efisien. Alih-alih, meluangkan waktu Anda untuk mempelajari domain dengan mewawancarai para ahli dan pengguna Anda dan dengan melihat kueri yang paling banyak dieksekusi sangat penting untuk memahami kriteria partisi yang sangat efisien. Artikel ini menunjukkan cara melakukannya dan menunjukkan hasil dari pendekatan semacam itu melalui studi kasus di dunia nyata.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cara Menonaktifkan Pemeriksaan Kunci Asing di MySQL

  2. Maksimum berdasarkan grup

  3. SQL Inner Join – Cara Menggabungkan 3 Tabel di SQL dan MySQL

  4. MySQL:Bagaimana cara mereset atau mengubah kata sandi root MySQL?

  5. Kolom MySQL tidak dikenal dalam klausa ON