Kami menulis dalam PostgreSQL pada sublight: 1 hos, 1 hari, 1TB

Baru-baru ini saya memberitahu anda bagaimana, menggunakan resipi standard meningkatkan prestasi pertanyaan baca SQL daripada pangkalan data PostgreSQL. Hari ini kita akan bercakap tentang bagaimana rakaman dapat dilakukan dengan lebih cekap dalam pangkalan data tanpa menggunakan sebarang "putaran" dalam konfigurasi - hanya dengan mengatur aliran data dengan betul.

Kami menulis dalam PostgreSQL pada sublight: 1 hos, 1 hari, 1TB

#1. Pembahagian

Satu artikel tentang bagaimana dan mengapa ia patut dianjurkan pembahagian gunaan "dalam teori" telah pun, di sini kita akan bercakap tentang amalan mengaplikasikan beberapa pendekatan dalam diri kita perkhidmatan pemantauan untuk ratusan pelayan PostgreSQL.

"Perkara hari-hari berlalu..."

Pada mulanya, seperti mana-mana MVP, projek kami bermula di bawah beban yang agak ringan - pemantauan dijalankan hanya untuk sepuluh pelayan yang paling kritikal, semua jadual adalah agak padat... Tetapi semakin lama, bilangan hos yang dipantau semakin banyak. , dan sekali lagi kami cuba melakukan sesuatu dengan salah satu daripada jadual bersaiz 1.5TB, kami menyedari bahawa walaupun mungkin untuk meneruskan hidup seperti ini, ia sangat menyusahkan.

Masa itu hampir seperti zaman epik, versi PostgreSQL 9.x yang berbeza adalah relevan, jadi semua pembahagian perlu dilakukan "secara manual" - melalui pewarisan jadual dan pencetus penghalaan dengan dinamik EXECUTE.

Kami menulis dalam PostgreSQL pada sublight: 1 hos, 1 hari, 1TB
Penyelesaian yang terhasil ternyata cukup universal sehingga boleh diterjemahkan ke semua jadual:

  • Jadual induk "pengepala" kosong telah diisytiharkan, yang menerangkan semua indeks dan pencetus yang diperlukan.
  • Rekod dari sudut pandangan pelanggan dibuat dalam jadual "root", dan secara dalaman menggunakan pencetus penghalaan BEFORE INSERT rekod itu "secara fizikal" dimasukkan ke dalam bahagian yang diperlukan. Jika belum ada lagi, kami mendapat pengecualian dan...
  • … dengan menggunakan CREATE TABLE ... (LIKE ... INCLUDING ...) telah dicipta berdasarkan templat jadual induk bahagian dengan sekatan pada tarikh yang dikehendakisupaya apabila data diambil, pembacaan hanya dilakukan di dalamnya.

PG10: percubaan pertama

Tetapi pembahagian melalui pewarisan dari segi sejarah tidak sesuai untuk menangani aliran tulis aktif atau sebilangan besar partition kanak-kanak. Sebagai contoh, anda boleh ingat bahawa algoritma untuk memilih bahagian yang diperlukan mempunyai kerumitan kuadratik, bahawa ia berfungsi dengan 100+ bahagian, anda sendiri faham bagaimana...

Dalam PG10 keadaan ini telah dioptimumkan dengan sangat baik dengan melaksanakan sokongan pembahagian asli. Oleh itu, kami segera cuba menerapkannya sejurus selepas memindahkan storan, tetapi...

Ternyata selepas menggali manual, jadual pembahagian asli dalam versi ini ialah:

  • tidak menyokong perihalan indeks
  • tidak menyokong pencetus padanya
  • tidak boleh menjadi "keturunan" sesiapa
  • tidak menyokong INSERT ... ON CONFLICT
  • tidak boleh menjana bahagian secara automatik

Setelah menerima pukulan yang menyakitkan di dahi dengan rake, kami menyedari bahawa mustahil untuk dilakukan tanpa mengubah suai aplikasi, dan menangguhkan penyelidikan lanjut selama enam bulan.

PG10: peluang kedua

Jadi, kami mula menyelesaikan masalah yang timbul satu demi satu:

  1. Kerana pencetus dan ON CONFLICT Kami mendapati bahawa kami masih memerlukannya di sana sini, jadi kami membuat peringkat pertengahan untuk menyelesaikannya jadual proksi.
  2. Menyingkirkan "penghalaan" dalam pencetus - iaitu, daripada EXECUTE.
  3. Mereka mengeluarkannya secara berasingan jadual templat dengan semua indekssupaya mereka tidak hadir dalam jadual proksi.

Kami menulis dalam PostgreSQL pada sublight: 1 hos, 1 hari, 1TB
Akhirnya, selepas semua ini, kami membahagikan jadual utama secara asli. Penciptaan bahagian baharu masih diserahkan kepada hati nurani aplikasi.

Kamus "Menggergaji".

Seperti dalam mana-mana sistem analisis, kami juga mempunyai "fakta" dan "potongan" (kamus). Dalam kes kami, dalam kapasiti ini mereka bertindak, sebagai contoh, badan templat pertanyaan perlahan yang serupa atau teks pertanyaan itu sendiri.

"Fakta" telah dibahagikan mengikut hari untuk masa yang lama, jadi kami memadamkan bahagian lapuk dengan tenang, dan ia tidak mengganggu kami (log!). Tetapi terdapat masalah dengan kamus...

Bukan untuk mengatakan bahawa terdapat banyak daripada mereka, tetapi kira-kira 100TB "fakta" menghasilkan kamus 2.5TB. Anda tidak boleh memadam apa-apa dengan mudah daripada jadual sedemikian, anda tidak boleh memampatkannya dalam masa yang mencukupi, dan menulis kepadanya secara beransur-ansur menjadi lebih perlahan.

Seperti kamus... di dalamnya, setiap entri harus dibentangkan tepat sekali... dan ini betul, tetapi!.. Tiada siapa yang menghalang kita daripada mempunyai kamus berasingan untuk setiap hari! Ya, ini membawa redundansi tertentu, tetapi ia membenarkan:

  • menulis/membaca lebih cepat kerana saiz bahagian yang lebih kecil
  • kurang menggunakan ingatan dengan bekerja dengan indeks yang lebih padat
  • menyimpan kurang data kerana keupayaan untuk menghapuskan ketinggalan zaman dengan cepat

Hasil daripada keseluruhan kompleks langkah Beban CPU berkurangan sebanyak ~30%, beban cakera sebanyak ~50%:

Kami menulis dalam PostgreSQL pada sublight: 1 hos, 1 hari, 1TB
Pada masa yang sama, kami terus menulis perkara yang sama ke dalam pangkalan data, hanya dengan sedikit beban.

#2. Evolusi pangkalan data dan pemfaktoran semula

Jadi kami settle dengan apa yang kami ada setiap hari mempunyai bahagian sendiri dengan data. Sebenarnya, CHECK (dt = '2018-10-12'::date) β€” dan terdapat kunci pembahagian dan syarat untuk rekod jatuh ke dalam bahagian tertentu.

Memandangkan semua laporan dalam perkhidmatan kami dibina dalam konteks tarikh tertentu, indeks untuk mereka sejak "masa tidak dibahagikan" adalah semua jenis (Pelayan, Tarikh, Templat Rancangan), (Pelayan, Tarikh, nod pelan), (Tarikh, Kelas ralat, Pelayan), ...

Tetapi kini mereka tinggal di setiap bahagian salinan anda setiap indeks tersebut... Dan dalam setiap bahagian tarikh adalah pemalar... Ternyata kini kita berada dalam setiap indeks tersebut hanya masukkan pemalar sebagai salah satu medan, yang meningkatkan volum dan masa carian untuknya, tetapi tidak membawa apa-apa hasil. Mereka meninggalkan garu untuk diri mereka sendiri, oops...

Kami menulis dalam PostgreSQL pada sublight: 1 hos, 1 hari, 1TB
Arah pengoptimuman adalah jelas - mudah alih keluar medan tarikh daripada semua indeks pada meja berpartition. Memandangkan jumlah kami, keuntungan adalah kira-kira 1TB/minggu!

Sekarang mari kita ambil perhatian bahawa terabait ini masih perlu direkodkan entah bagaimana. Iaitu, kita juga cakera kini sepatutnya memuatkan kurang! Gambar ini jelas menunjukkan kesan yang diperoleh daripada pembersihan, yang kami menumpukan seminggu:

Kami menulis dalam PostgreSQL pada sublight: 1 hos, 1 hari, 1TB

#3. "Menyebarkan" beban puncak

Salah satu masalah besar sistem yang dimuatkan ialah penyegerakan berlebihan beberapa operasi yang tidak memerlukannya. Kadang-kadang "kerana mereka tidak perasan", kadang-kadang "lebih mudah dengan cara itu", tetapi lambat laun anda perlu menyingkirkannya.

Mari zum masuk pada gambar sebelumnya dan lihat bahawa kita mempunyai cakera "pam" di bawah beban dengan amplitud berganda antara sampel bersebelahan, yang jelas "secara statistik" tidak sepatutnya berlaku dengan bilangan operasi sedemikian:

Kami menulis dalam PostgreSQL pada sublight: 1 hos, 1 hari, 1TB

Ini agak mudah untuk dicapai. Kami sudah mula memantau hampir 1000 pelayan, setiap satu diproses oleh utas logik yang berasingan, dan setiap utas menetapkan semula maklumat terkumpul untuk dihantar ke pangkalan data pada frekuensi tertentu, seperti ini:

setInterval(sendToDB, interval)

Masalahnya di sini terletak tepat pada fakta bahawa semua benang bermula pada masa yang lebih kurang sama, jadi masa penghantaran mereka hampir selalu bertepatan "to the point". Aduh #2...

Nasib baik, ini agak mudah untuk diperbaiki, menambah run-up "rawak". mengikut masa:

setInterval(sendToDB, interval * (1 + 0.1 * (Math.random() - 0.5)))

#4. Kami menyimpan apa yang kami perlukan

Masalah muatan tinggi tradisional ketiga ialah tiada cache di mana dia berada boleh akan menjadi.

Sebagai contoh, kami memungkinkan untuk menganalisis dari segi nod pelan (semua ini Seq Scan on users), tetapi segera berfikir bahawa mereka, sebahagian besarnya, sama - mereka terlupa.

Tidak, sudah tentu, tiada apa yang ditulis ke pangkalan data lagi, ini memotong pencetus dengan INSERT ... ON CONFLICT DO NOTHING. Tetapi data ini masih sampai ke pangkalan data, dan ia tidak diperlukan membaca untuk menyemak konflik perlu buat. Aduh #3...

Perbezaan dalam bilangan rekod yang dihantar ke pangkalan data sebelum/selepas caching didayakan adalah jelas:

Kami menulis dalam PostgreSQL pada sublight: 1 hos, 1 hari, 1TB

Dan ini ialah penurunan beban storan yang disertakan:

Kami menulis dalam PostgreSQL pada sublight: 1 hos, 1 hari, 1TB

Dalam jumlah

"Terabait-setiap-hari" kedengaran menakutkan. Jika anda melakukan semuanya dengan betul, maka ini adalah adil 2^40 bait / 86400 saat = ~12.5MB/swalaupun skru IDE desktop dipegang. πŸ™‚

Tetapi serius, walaupun dengan sepuluh kali ganda "skew" beban pada siang hari, anda boleh dengan mudah memenuhi keupayaan SSD moden.

Kami menulis dalam PostgreSQL pada sublight: 1 hos, 1 hari, 1TB

Sumber: www.habr.com

Tambah komen