Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Laporan ini menyajikan beberapa pendekatan yang memungkinkan memantau kinerja kueri SQL ketika jumlahnya jutaan per hari, dan ada ratusan server PostgreSQL yang dipantau.

Solusi teknis apa yang memungkinkan kami memproses informasi sebanyak itu secara efisien, dan bagaimana hal ini membuat kehidupan pengembang biasa lebih mudah?


Siapa yang tertarik? analisis masalah tertentu dan berbagai teknik optimasi Kueri SQL dan penyelesaian masalah khas DBA di PostgreSQL - Anda juga bisa membaca serangkaian artikel tentang hal ini.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)
Nama saya Kirill Borovikov, saya mewakili Perusahaan Tensor. Secara khusus, saya berspesialisasi dalam bekerja dengan database di perusahaan kami.

Hari ini saya akan memberi tahu Anda cara kami mengoptimalkan kueri, ketika Anda tidak perlu "memilih" kinerja satu kueri, tetapi menyelesaikan masalah secara massal. Ketika ada jutaan permintaan, dan Anda perlu menemukannya pendekatan terhadap solusi masalah besar ini.

Secara umum, Tensor untuk satu juta klien kami adalah VLSI adalah aplikasi kita: jaringan sosial perusahaan, solusi untuk komunikasi video, untuk aliran dokumen internal dan eksternal, sistem akuntansi untuk akuntansi dan gudang,... Yaitu, “mega-gabungan” untuk manajemen bisnis terintegrasi, di mana terdapat lebih dari 100 jenis yang berbeda proyek internal.

Untuk memastikan bahwa mereka semua berfungsi dan berkembang secara normal, kami memiliki 10 pusat pengembangan di seluruh negeri, dan lebih banyak lagi yang berada di dalamnya 1000 pengembang.

Kami telah bekerja dengan PostgreSQL sejak 2008 dan telah mengumpulkan sejumlah besar dari apa yang kami proses - data klien, statistik, analitis, data dari sistem informasi eksternal - lebih dari 400 TB. Ada sekitar 250 server yang dalam produksi saja, dan total ada sekitar 1000 server database yang kami pantau.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

SQL adalah bahasa deklaratif. Anda tidak mendeskripsikan “bagaimana” sesuatu harus berjalan, namun “apa” yang ingin Anda capai. DBMS lebih tahu cara membuat GABUNG - cara menghubungkan tabel Anda, kondisi apa yang diterapkan, apa yang akan melewati indeks, apa yang tidak...

Beberapa DBMS menerima petunjuk: "Tidak, sambungkan kedua tabel ini dalam antrian ini dan itu," tetapi PostgreSQL tidak dapat melakukan ini. Ini adalah posisi sadar dari pengembang terkemuka: “Kami lebih suka menyelesaikan pengoptimal kueri daripada mengizinkan pengembang menggunakan semacam petunjuk.”

Namun, terlepas dari kenyataan bahwa PostgreSQL tidak mengizinkan "luar" untuk mengontrol dirinya sendiri, PostgreSQL mengizinkannya dengan sempurna melihat apa yang terjadi di dalam dirinyasaat Anda menjalankan kueri, dan di bagian mana kueri tersebut mengalami masalah.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Secara umum, masalah klasik apa yang biasanya dihadapi oleh pengembang [pada DBA]? “Di sini kami memenuhi permintaan itu, dan semuanya lambat bersama kami, semuanya terhenti, sesuatu sedang terjadi... Semacam masalah!”

Alasannya hampir selalu sama:

  • algoritma kueri yang tidak efisien
    Pengembang: "Sekarang saya memberinya 10 tabel dalam SQL melalui JOIN..." - dan berharap bahwa kondisinya secara ajaib akan "terlepas" secara efektif dan dia akan mendapatkan semuanya dengan cepat. Namun keajaiban tidak terjadi, dan sistem apa pun dengan variabilitas seperti itu (10 tabel dalam satu FROM) selalu memberikan semacam kesalahan. [artikel]
  • statistik usang
    Poin ini sangat relevan khususnya untuk PostgreSQL, ketika Anda "menuangkan" kumpulan data besar ke server, membuat permintaan, dan itu "melakukan sexcan" pada tablet Anda. Karena kemarin ada 10 record di dalamnya, dan hari ini ada 10 juta, tapi PostgreSQL belum menyadarinya, dan perlu kita beritahukan. [artikel]
  • "pasang" sumber daya
    Anda telah menginstal database yang besar dan penuh muatan di server lemah yang tidak memiliki cukup kinerja disk, memori, atau prosesor. Dan itu saja... Di suatu tempat ada batas kinerja yang di atasnya Anda tidak dapat lagi melompatinya.
  • pemblokiran
    Ini adalah poin yang sulit, tetapi ini paling relevan untuk berbagai permintaan modifikasi (INSERT, UPDATE, DELETE) - ini adalah topik besar yang terpisah.

Mendapatkan rencana

...Dan untuk semua hal lainnya, kami membutuhkan rencana! Kita perlu melihat apa yang terjadi di dalam server.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Rencana eksekusi kueri untuk PostgreSQL adalah pohon algoritma eksekusi kueri dalam representasi teks. Algoritme inilah yang, berdasarkan hasil analisis perencana, ternyata paling efektif.

Setiap node pohon adalah sebuah operasi: mengambil data dari tabel atau indeks, membuat bitmap, menggabungkan dua tabel, menggabungkan, memotong, atau mengecualikan pilihan. Mengeksekusi kueri melibatkan penelusuran melalui node pohon ini.

Untuk mendapatkan rencana kueri, cara termudah adalah dengan menjalankan pernyataan EXPLAIN. Untuk mendapatkan semua atribut nyata, yaitu, untuk benar-benar mengeksekusi kueri di pangkalan - EXPLAIN (ANALYZE, BUFFERS) SELECT ....

Bagian buruknya: ketika Anda menjalankannya, itu terjadi "di sini dan sekarang", jadi ini hanya cocok untuk debugging lokal. Jika Anda menggunakan server dengan muatan tinggi dan berada di bawah aliran perubahan data yang kuat, dan Anda melihat: “Oh! Di sini kita memiliki eksekusi yang lambatsya meminta." Setengah jam, satu jam yang lalu - saat Anda menjalankan dan mendapatkan permintaan ini dari log, membawanya kembali ke server, seluruh kumpulan data dan statistik Anda berubah. Anda menjalankannya untuk melakukan debug - dan itu berjalan dengan cepat! Dan Anda tidak dapat memahami alasannya, mengapa adalah perlahan-lahan.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Untuk memahami apa yang sebenarnya terjadi pada saat permintaan dieksekusi di server, tulis orang pintar modul auto_explain. Ini ada di hampir semua distribusi PostgreSQL yang paling umum, dan dapat dengan mudah diaktifkan di file konfigurasi.

Jika ia menyadari bahwa beberapa permintaan berjalan lebih lama dari batas yang Anda minta, ia akan melakukannya "snapshot" dari rencana permintaan ini dan menuliskannya bersama-sama di log.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Semuanya tampak baik-baik saja sekarang, kita pergi ke log dan melihat di sana... [teks alas kaki]. Namun kami tidak dapat mengatakan apa pun tentang hal ini, selain fakta bahwa ini adalah rencana yang sangat bagus karena memerlukan waktu 11 md untuk mengeksekusinya.

Segalanya tampak baik-baik saja - namun tidak ada yang jelas apa yang sebenarnya terjadi. Selain waktu secara umum, kami tidak melihat apa pun. Karena melihat “domba” teks biasa umumnya tidak visual.

Namun meski tidak jelas, meski merepotkan, ada masalah yang lebih mendasar:

  • Node menunjukkan jumlah sumber daya dari seluruh subpohon di bawahnya. Artinya, Anda tidak bisa begitu saja mengetahui berapa banyak waktu yang dihabiskan pada Pemindaian Indeks tertentu jika terdapat kondisi bersarang di bawahnya. Kita harus secara dinamis melihat apakah ada “anak” dan variabel kondisional, CTE di dalamnya – dan mengurangi semua ini “dalam pikiran kita”.
  • Poin kedua: waktu yang ditunjukkan pada node adalah waktu eksekusi node tunggal. Jika simpul ini dieksekusi sebagai hasil dari, misalnya, perulangan melalui catatan tabel beberapa kali, maka jumlah perulangan—siklus dari simpul ini—bertambah sesuai rencana. Namun waktu eksekusi atomnya sendiri tetap sama dari segi rencana. Artinya, untuk memahami berapa lama total simpul ini telah selesai, Anda perlu mengalikan satu hal dengan hal lainnya - sekali lagi, "di kepala Anda".

Dalam situasi seperti itu, pahami “Siapa mata rantai terlemah?” hampir tidak mungkin. Oleh karena itu, bahkan pengembangnya sendiri menulis di “manual” itu “Memahami rencana adalah seni yang harus dipelajari, dialami…”.

Namun kami memiliki 1000 pengembang, dan Anda tidak dapat menyampaikan pengalaman ini kepada masing-masing pengembang. Aku, kamu, dia tahu, tapi seseorang di sana tidak tahu lagi. Mungkin dia akan belajar, atau mungkin tidak, tapi dia perlu bekerja sekarang - dan dari mana dia bisa mendapatkan pengalaman ini?

Visualisasi rencana

Oleh karena itu, kami menyadari bahwa untuk mengatasi permasalahan tersebut, kami memerlukannya visualisasi rencana yang bagus. [artikel]

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Pertama-tama kita menelusuri “pasar” – mari kita lihat di Internet untuk melihat apa yang ada.

Namun ternyata hanya ada sedikit solusi yang relatif “hidup” dan kurang lebih berkembang – secara harafiah, hanya ada satu: jelaskan.depesz.com oleh Hubert Lubaczewski. Saat Anda memasukkan bidang "umpan" yang merupakan representasi teks dari rencana tersebut, ini menunjukkan kepada Anda sebuah tabel dengan data yang diurai:

  • waktu pemrosesan node itu sendiri
  • total waktu untuk seluruh subpohon
  • jumlah catatan yang diambil yang diharapkan secara statistik
  • badan node itu sendiri

Layanan ini juga memiliki kemampuan untuk berbagi arsip tautan. Anda melemparkan rencana Anda ke sana dan berkata: "Hei, Vasya, ini tautannya, ada yang salah di sana."

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Tapi ada juga masalah kecil.

Pertama, sejumlah besar “salin-tempel”. Anda mengambil sepotong kayu, menempelkannya di sana, dan lagi, dan lagi.

Kedua, tidak ada analisis jumlah data yang dibaca — buffer yang sama dengan outputnya EXPLAIN (ANALYZE, BUFFERS), kami tidak melihatnya di sini. Dia sama sekali tidak tahu cara membongkar, memahami, dan bekerja dengannya. Saat Anda membaca banyak data dan menyadari bahwa Anda mungkin salah mengalokasikan disk dan cache memori, informasi ini sangat penting.

Poin negatif ketiga adalah sangat lemahnya perkembangan proyek ini. Komitnya kecil sekali, ada baiknya kalau enam bulan sekali, dan kodenya ada di Perl.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Tapi ini semua hanya “lirik”, entah bagaimana kami bisa menerimanya, tapi ada satu hal yang sangat membuat kami menjauh dari layanan ini. Ini adalah kesalahan dalam analisis Common Table Expression (CTE) dan berbagai node dinamis seperti InitPlan/SubPlan.

Jika Anda mempercayai gambaran ini, maka total waktu eksekusi setiap node lebih besar dari total waktu eksekusi seluruh permintaan. Itu mudah - waktu pembuatan CTE ini tidak dikurangi dari node CTE Scan. Oleh karena itu, kami tidak lagi mengetahui jawaban yang benar mengenai berapa lama waktu yang dibutuhkan untuk pemindaian CTE itu sendiri.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Kemudian kami menyadari bahwa sudah waktunya untuk menulis sendiri - hore! Setiap pengembang berkata: “Sekarang kami akan menulis sendiri, ini akan sangat mudah!”

Kami mengambil tumpukan khas untuk layanan web: inti berdasarkan Node.js + Express, menggunakan Bootstrap dan D3.js untuk diagram yang indah. Dan harapan kami sepenuhnya dibenarkan - kami menerima prototipe pertama dalam 2 minggu:

  • pengurai rencana khusus
    Artinya, sekarang kita dapat mengurai rencana apa pun dari rencana yang dihasilkan oleh PostgreSQL.
  • analisis yang benar dari node dinamis - Pemindaian CTE, InitPlan, SubRencana
  • analisis distribusi buffer - dari mana halaman data dibaca dari memori, dari mana dari cache lokal, dari mana dari disk
  • mendapat kejelasan
    Agar tidak "menggali" semua ini di log, tetapi untuk melihat "tautan terlemah" langsung di gambar.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Kami mendapatkan sesuatu seperti ini, dengan penyorotan sintaksis disertakan. Namun biasanya pengembang kami tidak lagi bekerja dengan representasi rencana yang lengkap, melainkan dengan representasi yang lebih pendek. Lagi pula, kita sudah mengurai semua angka dan melemparkannya ke kiri dan ke kanan, dan di tengah kita hanya menyisakan baris pertama, node apa itu: CTE Scan, generasi CTE atau Seq Scan menurut beberapa tanda.

Ini adalah representasi singkat yang kami sebut templat rencana.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Apa lagi yang nyaman? Akan lebih mudah untuk melihat berapa bagian dari total waktu kita yang dialokasikan ke node mana - dan cukup “tempelkan” ke samping Pie chart.

Kami menunjuk ke node dan melihat - ternyata Seq Scan memakan waktu kurang dari seperempat dari total waktu, dan 3/4 sisanya diambil oleh CTE Scan. Kengerian! Ini adalah catatan kecil tentang “rate of fire” CTE Scan jika Anda secara aktif menggunakannya dalam pertanyaan Anda. Mereka tidak terlalu cepat - bahkan lebih rendah daripada pemindaian tabel biasa. [artikel] [artikel]

Namun biasanya diagram seperti itu lebih menarik, lebih kompleks, ketika kita langsung menunjuk pada suatu segmen dan melihat, misalnya, bahwa lebih dari separuh waktu Seq Scan “makan”. Selain itu, ada semacam Filter di dalamnya, banyak catatan yang dibuang karenanya... Anda dapat langsung melemparkan gambar ini ke pengembang dan berkata: “Vasya, semuanya buruk di sini untukmu! Cari tahu, lihat - ada yang tidak beres!”

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Tentu saja, ada beberapa “penggaruk” yang terlibat.

Hal pertama yang kami temui adalah masalah pembulatan. Waktu setiap node dalam rencana ditunjukkan dengan akurasi 1 s. Dan ketika jumlah siklus node melebihi, misalnya, 1000 - setelah eksekusi PostgreSQL dibagi "dalam akurasi", maka ketika menghitung kembali kita mendapatkan total waktu "antara 0.95 md dan 1.05 md". Jika penghitungannya mencapai mikrodetik, tidak apa-apa, tetapi jika sudah mencapai [mili]detik, Anda harus memperhitungkan informasi ini saat "melepaskan" sumber daya ke node dari rencana "siapa yang mengonsumsi berapa banyak".

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Poin kedua, yang lebih kompleks, adalah distribusi sumber daya (buffer tersebut) di antara node dinamis. Hal ini membuat kami kehilangan 2 minggu pertama pembuatan prototipe ditambah 4 minggu berikutnya.

Sangat mudah untuk mendapatkan masalah seperti ini - kami melakukan CTE dan membaca sesuatu di dalamnya. Faktanya, PostgreSQL adalah “pintar” dan tidak akan membaca apa pun secara langsung di sana. Kemudian kita ambil rekor pertama darinya, dan rekor keseratus pertama dari CTE yang sama.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Kami melihat rencananya dan memahami - anehnya, kami memiliki 3 buffer (halaman data) yang "dikonsumsi" di Seq Scan, 1 lagi di CTE Scan, dan 2 lagi di CTE Scan kedua. Artinya, jika kita jumlahkan semuanya, kita akan mendapatkan 6, tetapi dari tablet kita hanya membaca 3! CTE Scan tidak membaca apa pun dari mana pun, tetapi bekerja langsung dengan memori proses. Artinya, jelas ada sesuatu yang salah di sini!

Ternyata di sini ada 3 halaman data yang diminta dari Seq Scan, pertama 1 diminta CTE Scan pertama, lalu halaman ke-1, dan 2 lagi dibacakan kepadanya, jadi totalnya 2 halaman adalah data yang dibaca, bukan 3.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Dan gambaran ini membawa kita pada pemahaman bahwa pelaksanaan suatu rencana bukan lagi sebuah pohon, tetapi hanyalah semacam grafik asiklik. Dan kita mendapatkan diagram seperti ini, sehingga kita memahami “apa yang berasal dari mana.” Artinya, di sini kita membuat CTE dari pg_class, dan memintanya dua kali, dan hampir seluruh waktu kita dihabiskan di cabang ketika kita memintanya untuk kedua kalinya. Jelas bahwa membaca entri ke-2 jauh lebih mahal daripada sekadar membaca entri pertama dari tablet.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Kami menghela napas sebentar. Mereka berkata: “Sekarang, Neo, kamu tahu kung fu! Sekarang pengalaman kami ada di layar Anda. Sekarang kamu bisa menggunakannya." [artikel]

Konsolidasi log

1000 pengembang kami menarik napas lega. Namun kami memahami bahwa kami hanya memiliki ratusan server "tempur", dan semua "salin-tempel" dari pihak pengembang sama sekali tidak nyaman. Kami menyadari bahwa kami harus mengumpulkannya sendiri.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Secara umum, ada modul standar yang dapat mengumpulkan statistik, namun juga perlu diaktifkan di konfigurasi - ini modul pg_stat_statements. Tapi dia tidak cocok untuk kita.

Pertama, ia menetapkan kueri yang sama menggunakan skema berbeda dalam database yang sama QueryId yang berbeda. Yaitu jika Anda melakukannya terlebih dahulu SET search_path = '01'; SELECT * FROM user LIMIT 1;dan kemudian SET search_path = '02'; dan permintaan yang sama, maka statistik modul ini akan memiliki catatan yang berbeda, dan saya tidak akan dapat mengumpulkan statistik umum secara khusus dalam konteks profil permintaan ini, tanpa memperhitungkan skemanya.

Poin kedua yang menghalangi kami untuk menggunakannya adalah kurangnya rencana. Artinya, tidak ada rencana, yang ada hanya permintaan itu sendiri. Kami melihat apa yang melambat, namun kami tidak memahami alasannya. Dan di sini kita kembali ke masalah kumpulan data yang berubah dengan cepat.

Dan saat terakhir - kurangnya "fakta". Artinya, Anda tidak dapat mengatasi contoh spesifik dari eksekusi kueri - tidak ada, yang ada hanya statistik agregat. Meskipun hal ini mungkin untuk dilakukan, namun hal ini sangatlah sulit.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Oleh karena itu, kami memutuskan untuk melawan copy-paste dan mulai menulis kolektor.

Kolektor terhubung melalui SSH, membuat koneksi aman ke server dengan database menggunakan sertifikat, dan tail -F "menempel" padanya di file log. Jadi pada sesi ini kami mendapatkan "cermin" lengkap dari seluruh file log, yang dihasilkan server. Beban di servernya sendiri minimal, karena kami tidak mengurai apa pun di sana, kami hanya mencerminkan lalu lintasnya.

Karena kami sudah mulai menulis antarmuka di Node.js, kami terus menulis kolektor di dalamnya. Dan teknologi ini membenarkan dirinya sendiri, karena sangat nyaman menggunakan JavaScript untuk bekerja dengan data teks berformat lemah, yaitu log. Dan infrastruktur Node.js itu sendiri sebagai platform backend memungkinkan Anda bekerja dengan mudah dan nyaman dengan koneksi jaringan, dan tentu saja dengan aliran data apa pun.

Oleh karena itu, kami “meregangkan” dua koneksi: yang pertama untuk “mendengarkan” log itu sendiri dan membawanya ke diri kami sendiri, dan yang kedua untuk menanyakan pangkalan secara berkala. “Tetapi log menunjukkan bahwa tanda dengan oid 123 diblokir,” tetapi ini tidak berarti apa-apa bagi pengembang, dan alangkah baiknya jika bertanya kepada database, “Apa itu OID = 123?” Jadi kita secara berkala menanyakan hal-hal mendasar yang belum kita ketahui tentang diri kita sendiri.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

“Hanya ada satu hal yang tidak Anda perhitungkan, ada spesies lebah mirip gajah!..” Kami mulai mengembangkan sistem ini ketika kami ingin memantau 10 server. Yang paling kritis dalam pemahaman kami, dimana muncul beberapa permasalahan yang sulit untuk ditangani. Namun selama kuartal pertama, kami menerima seratus untuk pemantauan - karena sistemnya berfungsi, semua orang menginginkannya, semua orang merasa nyaman.

Semua ini perlu dijumlahkan, aliran datanya besar dan aktif. Padahal, apa yang kita pantau, apa yang bisa kita tangani, itulah yang kita manfaatkan. Kami juga menggunakan PostgreSQL sebagai penyimpanan data. Dan tidak ada yang lebih cepat untuk “menuangkan” data ke dalamnya selain operator COPY Belum.

Namun sekadar “menuangkan” data bukanlah teknologi kami. Karena jika Anda memiliki sekitar 50 ribu permintaan per detik di seratus server, maka ini akan menghasilkan 100-150 GB log per hari. Oleh karena itu, kami harus “memotong” alasnya dengan hati-hati.

Pertama, kami melakukannya partisi pada siang hari, karena pada umumnya tidak ada yang tertarik dengan korelasi antar hari. Apa bedanya apa yang Anda miliki kemarin, jika malam ini Anda meluncurkan aplikasi versi baru - dan sudah ada beberapa statistik baru.

Kedua, kami belajar (dipaksa) sangat, sangat cepat untuk menulis menggunakan COPY. Artinya, tidak hanya COPYkarena dia lebih cepat dari INSERT, dan bahkan lebih cepat.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Poin ketiga - saya harus melakukannya mengabaikan pemicu, masing-masing, dan kunci asing. Artinya, kita tidak mempunyai integritas referensial sama sekali. Karena jika Anda memiliki tabel yang memiliki sepasang FK, dan Anda mengatakan dalam struktur database bahwa "ini adalah catatan log yang direferensikan oleh FK, misalnya ke sekelompok catatan", maka ketika Anda memasukkannya, PostgreSQL tidak ada yang tersisa selain bagaimana menerimanya dan melakukannya dengan jujur SELECT 1 FROM master_fk1_table WHERE ... dengan pengidentifikasi yang Anda coba masukkan - hanya untuk memeriksa apakah catatan ini ada di sana, bahwa Anda tidak "memutus" Kunci Asing ini dengan penyisipan Anda.

Daripada hanya satu catatan pada tabel target dan indeksnya, kita mendapatkan manfaat tambahan dengan membaca semua tabel yang dirujuknya. Tapi kita tidak memerlukan ini sama sekali - tugas kita adalah merekam sebanyak mungkin dan secepat mungkin dengan beban paling sedikit. Jadi FK - turun!

Poin selanjutnya adalah agregasi dan hashing. Awalnya, kami mengimplementasikannya dalam database - lagipula, akan lebih mudah untuk segera melakukannya di beberapa jenis tablet ketika ada catatan tiba. "plus satu" tepat di pelatuknya. Memang nyaman, tetapi sama buruknya - Anda memasukkan satu catatan, tetapi terpaksa membaca dan menulis sesuatu yang lain dari tabel lain. Apalagi Anda tidak hanya membaca dan menulis, Anda juga melakukannya setiap saat.

Sekarang bayangkan Anda memiliki tabel di mana Anda menghitung jumlah permintaan yang melewati host tertentu: +1, +1, +1, ..., +1. Dan Anda, pada prinsipnya, tidak memerlukan ini - semuanya mungkin jumlahkan dalam memori pada kolektor dan kirim ke database sekaligus +10.

Ya, jika terjadi beberapa masalah, integritas logis Anda mungkin "berantakan", tetapi ini adalah kasus yang hampir tidak realistis - karena Anda memiliki server normal, memiliki baterai di pengontrol, Anda memiliki log transaksi, log di berkas sistem... Secara umum, tidak sepadan. Hilangnya produktivitas yang Anda peroleh dari menjalankan trigger/FK tidak sebanding dengan biaya yang Anda keluarkan.

Sama halnya dengan hashing. Permintaan tertentu terbang ke Anda, Anda menghitung pengidentifikasi tertentu darinya di database, menuliskannya ke database dan kemudian memberitahukannya kepada semua orang. Semuanya baik-baik saja sampai, pada saat perekaman, orang kedua mendatangi Anda yang ingin merekam hal yang sama - dan Anda diblokir, dan ini sudah buruk. Oleh karena itu, jika Anda dapat mentransfer pembuatan beberapa ID ke klien (relatif terhadap database), lebih baik melakukan ini.

Sangat cocok bagi kami untuk menggunakan MD5 dari teks - permintaan, rencana, templat,... Kami menghitungnya di sisi kolektor, dan “menuangkan” ID yang sudah jadi ke dalam database. Panjangnya MD5 dan partisi harian membuat kita tidak perlu khawatir tentang kemungkinan tabrakan.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Namun untuk merekam semua ini dengan cepat, kami perlu memodifikasi prosedur perekaman itu sendiri.

Bagaimana biasanya Anda menulis data? Kami memiliki semacam kumpulan data, kami membaginya menjadi beberapa tabel, lalu SALINAN - pertama ke yang pertama, lalu ke yang kedua, ke yang ketiga... Ini merepotkan, karena sepertinya kami menulis satu aliran data dalam tiga langkah secara berurutan. Tidak menyenangkan. Bisakah itu dilakukan lebih cepat? Bisa!

Untuk melakukan ini, cukup menguraikan aliran-aliran ini secara paralel satu sama lain. Ternyata kami memiliki kesalahan, permintaan, templat, pemblokiran, ... terbang di utas terpisah - dan kami menulis semuanya secara paralel. Cukup untuk ini menjaga saluran COPY tetap terbuka untuk setiap tabel target individual.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Artinya, di kolektor selalu ada aliran, di mana saya dapat menulis data yang saya perlukan. Namun agar database dapat melihat data ini, dan seseorang tidak terjebak menunggu data ini ditulis, COPY harus diinterupsi pada interval tertentu. Bagi kami, periode paling efektif adalah sekitar 100 md - kami menutupnya dan segera membukanya kembali ke tabel yang sama. Dan jika kita tidak memiliki cukup satu aliran pada beberapa puncak, maka kita melakukan pengumpulan hingga batas tertentu.

Selain itu, kami menemukan bahwa untuk profil beban seperti itu, agregasi apa pun, ketika catatan dikumpulkan dalam batch, adalah tindakan yang buruk. Kejahatan klasik adalah INSERT ... VALUES dan 1000 catatan selanjutnya. Karena pada saat itu Anda mengalami puncak penulisan pada media, dan semua orang yang mencoba menulis sesuatu ke disk akan menunggu.

Untuk menghilangkan anomali seperti itu, jangan gabungkan apa pun, jangan buffering sama sekali. Dan jika buffering pada disk memang terjadi (untungnya, Stream API di Node.js memungkinkan Anda mengetahuinya) - tunda koneksi ini. Saat Anda menerima acara gratis lagi, tulis ke acara tersebut dari antrian yang terakumulasi. Dan selagi sibuk, ambillah yang gratis berikutnya dari kolam dan tulislah surat ke sana.

Sebelum memperkenalkan pendekatan ini pada perekaman data, kami memiliki operasi penulisan sekitar 4K, dan dengan cara ini kami mengurangi beban sebanyak 4 kali lipat. Sekarang mereka telah berkembang 6 kali lipat karena database baru yang dipantau - hingga 100 MB/s. Dan sekarang kami menyimpan log selama 3 bulan terakhir dalam volume sekitar 10-15TB, dengan harapan hanya dalam tiga bulan pengembang mana pun dapat menyelesaikan masalah apa pun.

Kami memahami masalahnya

Namun mengumpulkan semua data ini saja sudah baik, berguna, relevan, namun tidak cukup - hal ini perlu dipahami. Karena ini adalah jutaan rencana berbeda setiap hari.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Namun jutaan orang tidak dapat dikendalikan, pertama-tama kita harus melakukan hal yang “lebih kecil”. Dan, pertama-tama, Anda perlu memutuskan bagaimana Anda akan mengatur hal yang “lebih kecil” ini.

Kami telah mengidentifikasi tiga poin utama:

  • siapa mengirimkan permintaan ini
    Artinya, dari aplikasi apa ia “tiba”: antarmuka web, backend, sistem pembayaran, atau yang lainnya.
  • dimana itu terjadi
    Di server spesifik apa? Karena jika Anda memiliki beberapa server dalam satu aplikasi, dan tiba-tiba salah satu server “menjadi bodoh” (karena “disk busuk”, “memori bocor”, beberapa masalah lainnya), maka Anda perlu menangani server tersebut secara khusus.
  • sebagai masalahnya terwujud dalam satu atau lain cara

Untuk memahami "siapa" yang mengirimi kami permintaan, kami menggunakan alat standar - menyetel variabel sesi: SET application_name = '{bl-host}:{bl-method}'; — kami mengirimkan nama host logika bisnis asal permintaan, dan nama metode atau aplikasi yang memulainya.

Setelah kita meneruskan permintaan ke "pemilik", permintaan itu harus dikeluarkan ke log - untuk ini kita mengkonfigurasi variabel log_line_prefix = ' %m [%p:%v] [%d] %r %a'. Bagi yang berminat, mungkin lihat di manualapa arti semua itu. Ternyata yang kita lihat di log:

  • waktu
  • pengidentifikasi proses dan transaksi
  • nama basis data
  • IP orang yang mengirim permintaan ini
  • dan nama metode

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Kemudian kami menyadari bahwa tidak terlalu menarik untuk melihat korelasi satu permintaan antara server yang berbeda. Jarang sekali Anda menghadapi situasi di mana satu aplikasi mengalami kegagalan yang sama di sana-sini. Tetapi meskipun sama, lihatlah salah satu server ini.

Jadi inilah potongannya "satu server - satu hari" ternyata cukup bagi kami untuk analisis apa pun.

Bagian analitis pertama juga sama "Sampel" - bentuk presentasi rencana yang disingkat, dibersihkan dari semua indikator numerik. Potongan kedua adalah aplikasi atau metode, dan potongan ketiga adalah simpul rencana spesifik yang menyebabkan masalah bagi kita.

Saat kami berpindah dari contoh tertentu ke templat, kami mendapat dua keuntungan sekaligus:

  • pengurangan berganda dalam jumlah objek untuk dianalisis
    Kita harus menganalisis masalahnya tidak lagi berdasarkan ribuan pertanyaan atau rencana, tetapi berdasarkan lusinan templat.
  • linimasa
    Artinya, dengan merangkum “fakta” ​​dalam suatu bagian tertentu, Anda bisa menampilkan penampakannya sepanjang hari. Dan di sini Anda dapat memahami bahwa jika Anda memiliki pola yang terjadi, misalnya, sekali dalam satu jam, tetapi seharusnya terjadi sekali sehari, Anda harus memikirkan apa yang salah - siapa penyebabnya dan mengapa, mungkin seharusnya ada di sini. tidak seharusnya. Ini adalah metode analisis non-numerik, murni visual.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Metode selebihnya didasarkan pada indikator yang kami ekstrak dari rencana: berapa kali pola seperti itu terjadi, total dan waktu rata-rata, berapa banyak data yang dibaca dari disk, dan berapa banyak dari memori...

Karena, misalnya, Anda membuka halaman analitik untuk host, lihat - ada sesuatu yang mulai terlalu banyak terbaca di disk. Disk di server tidak dapat menanganinya - siapa yang membacanya?

Dan Anda dapat mengurutkan berdasarkan kolom mana saja dan memutuskan apa yang akan Anda tangani saat ini - beban pada prosesor atau disk, atau jumlah total permintaan... Kami mengurutkannya, melihat yang "atas", memperbaikinya dan meluncurkan versi baru aplikasi.
[video ceramah]

Dan segera Anda dapat melihat berbagai aplikasi yang datang dengan template yang sama dari permintaan suka SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, pemrosesan... Dan Anda bertanya-tanya mengapa pemrosesan akan membaca pengguna jika dia tidak berinteraksi dengannya.

Cara sebaliknya adalah dengan langsung melihat dari aplikasi apa fungsinya. Misalnya, frontendnya adalah ini, ini, ini, dan ini satu kali dalam satu jam (garis waktu membantu). Dan pertanyaan segera muncul: sepertinya bukan tugas frontend untuk melakukan sesuatu sekali dalam satu jam...

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Setelah beberapa waktu, kami menyadari bahwa kami kekurangan agregat statistik berdasarkan node rencana. Kami mengisolasi dari rencana hanya node-node yang melakukan sesuatu dengan data tabel itu sendiri (membaca/menulisnya berdasarkan indeks atau tidak). Faktanya, hanya satu aspek yang ditambahkan dibandingkan dengan gambar sebelumnya - berapa banyak catatan yang diberikan node ini kepada kita?, dan berapa banyak yang dibuang (Baris Dihapus oleh Filter).

Anda tidak memiliki indeks yang sesuai di piring, Anda membuat permintaan padanya, indeks tersebut terbang melewati indeks, masuk ke Seq Scan... Anda telah memfilter semua catatan kecuali satu. Mengapa Anda memerlukan 100 juta catatan yang difilter per hari? Bukankah lebih baik jika Anda menggabungkan indeks?

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Setelah menganalisis semua rencana dari titik ke titik, kami menyadari bahwa ada beberapa struktur umum dalam rencana yang kemungkinan besar terlihat mencurigakan. Dan alangkah baiknya untuk memberi tahu pengembang: "Sobat, di sini Anda membaca terlebih dahulu berdasarkan indeks, lalu mengurutkannya, lalu memotongnya" - sebagai aturan, ada satu catatan.

Setiap orang yang menulis pertanyaan mungkin pernah menemukan pola ini: "Beri saya urutan terakhir untuk Vasya, tanggalnya." Dan jika Anda tidak memiliki indeks berdasarkan tanggal, atau tidak ada tanggal dalam indeks yang Anda gunakan, maka Anda akan menginjak “rake” yang persis sama.

Namun kita tahu bahwa ini adalah “penggaruk” - jadi mengapa tidak segera memberi tahu pengembang apa yang harus dia lakukan. Oleh karena itu, ketika membuka rencana sekarang, pengembang kami segera melihat gambar yang indah dengan tip, di mana mereka segera mengatakan kepadanya: "Anda memiliki masalah di sana-sini, tetapi masalah tersebut diselesaikan dengan cara ini dan itu."

Akibatnya, jumlah pengalaman yang dibutuhkan untuk memecahkan masalah di awal dan sekarang telah menurun secara signifikan. Ini adalah jenis alat yang kami miliki.

Pengoptimalan massal kueri PostgreSQL. Kirill Borovikov (Tensor)

Sumber: www.habr.com

Tambah komentar