PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Kinerja aplikasi berbasis PostgreSQL:latensi dan penundaan tersembunyi

Jalur Pipa Goldfields, oleh SeanMac (Wikimedia Commons)

Jika Anda mencoba mengoptimalkan kinerja aplikasi berbasis PostgreSQL, Anda mungkin berfokus pada alat yang biasa:MENJELASKAN (BUFFERS, ANALYZE) , pg_stat_statements , penjelasan_otomatis , log_statement_min_duration , dll.

Mungkin Anda sedang mencari pertikaian kunci dengan log_lock_wais , memantau kinerja pos pemeriksaan Anda, dll juga.

Tapi apakah Anda berpikir tentang latensi jaringan ? Gamer tahu tentang latensi jaringan, tetapi apakah menurut Anda itu penting untuk server aplikasi Anda?

Latensi itu penting

Latensi jaringan pulang pergi klien/server biasa dapat berkisar dari 0,01 md (localhost) hingga ~ 0,5 md jaringan yang diaktifkan, 5 md WiFi, 20 md ADSL, 300 md perutean antarbenua, dan bahkan lebih untuk hal-hal seperti tautan satelit dan WWAN .

PILIH trivial yang sepele dapat mengambil urutan 0,1 ms untuk mengeksekusi sisi server. MASUKKAN trivial yang sepele bisa memakan waktu 0,5 md.

Setiap kali aplikasi Anda menjalankan kueri, ia harus menunggu server merespons dengan sukses/gagal dan mungkin kumpulan hasil, metadata kueri, dll. Ini menimbulkan setidaknya satu penundaan perjalanan pulang pergi jaringan.

Saat Anda bekerja dengan latensi jaringan kueri yang kecil dan sederhana dapat menjadi signifikan dibandingkan dengan waktu eksekusi kueri Anda jika database Anda tidak berada di host yang sama dengan aplikasi Anda.

Banyak aplikasi, terutama ORM, sangat rentan menjalankan lot dari pertanyaan yang cukup sederhana. Misalnya, jika aplikasi Hibernate Anda mengambil entitas dengan @OneToMany yang diambil dengan malas hubungan dengan 1000 item anak itu mungkin akan melakukan 1001 kueri berkat masalah pemilihan n+1, jika tidak lebih. Itu berarti mungkin menghabiskan 1000 kali latensi pulang pergi jaringan Anda hanya menunggu . Anda dapat kiri bergabung mengambil untuk menghindarinya… tetapi kemudian Anda mentransfer entitas induk 1000 kali dalam bergabung dan harus menghapus duplikatnya.

Demikian pula, jika Anda mengisi database dari ORM, Anda mungkin melakukan ratusan ribu hal sepele INSERT s… dan menunggu setiap server untuk mengonfirmasi bahwa semuanya baik-baik saja.

Sangat mudah untuk mencoba fokus pada waktu eksekusi kueri dan mencoba mengoptimalkannya, tetapi hanya ada banyak hal yang dapat Anda lakukan dengan INSERT INTO ...VALUES ... yang sepele. . Jatuhkan beberapa indeks dan batasan, pastikan itu dikelompokkan ke dalam transaksi, dan Anda sudah selesai.

Bagaimana menyingkirkan semua jaringan menunggu? Bahkan di LAN, mereka mulai menambahkan lebih dari ribuan kueri.

SALIN

Salah satu cara untuk menghindari latensi adalah dengan menggunakan COPY . Untuk menggunakan dukungan COPY PostgreSQL, aplikasi atau driver Anda harus menghasilkan serangkaian baris seperti CSV dan mengalirkannya ke server secara berurutan. Atau server dapat diminta untuk mengirimkan aliran seperti CSV ke aplikasi Anda.

Apa pun itu, aplikasi tidak dapat menyisipkan SALIN dengan kueri lain, dan sisipan salin harus dimuat langsung ke tabel tujuan. Pendekatan umum adalah dengan COPY ke tabel sementara, lalu dari sana lakukan INSERT INTO ... SELECT ... , PERBARUI ... DARI .... , HAPUS DARI ... MENGGUNAKAN... , dll untuk menggunakan data yang disalin untuk memodifikasi tabel utama dalam satu operasi.

Itu berguna jika Anda menulis SQL Anda sendiri secara langsung, tetapi banyak kerangka kerja aplikasi dan ORM tidak mendukungnya, ditambah itu hanya dapat langsung menggantikan INSERT sederhana . Aplikasi, kerangka kerja, atau driver klien Anda harus berurusan dengan konversi untuk representasi khusus yang dibutuhkan oleh COPY , cari sendiri jenis metadata yang diperlukan, dll.

(Pengemudi terkenal yang melakukannya mendukung COPY termasuk libpq, PgJDBC, psycopg2, dan permata Pg… tetapi tidak harus kerangka kerja dan ORM yang dibangun di atasnya.)

PgJDBC – mode batch

Driver JDBC PostgreSQL memiliki solusi untuk masalah ini. Itu bergantung pada dukungan yang ada di server PostgreSQL sejak 8.4 dan pada fitur batching JDBC API untuk mengirim batch kueri ke server, lalu tunggu hanya sekali untuk konfirmasi bahwa seluruh batch berjalan dengan baik.

Nah, secara teori. Pada kenyataannya beberapa tantangan implementasi membatasi ini sehingga kumpulan hanya dapat dilakukan dalam potongan beberapa ratus kueri. Pengemudi juga hanya dapat menjalankan kueri yang mengembalikan baris hasil dalam potongan batch jika dapat mengetahui seberapa besar hasil sebelumnya. Terlepas dari keterbatasan tersebut, penggunaan Statement.executeBatch() dapat menawarkan peningkatan kinerja yang sangat besar untuk aplikasi yang melakukan tugas seperti pemuatan data massal instans basis data jarak jauh.

Karena ini adalah API standar, ini dapat digunakan oleh aplikasi yang bekerja di beberapa mesin basis data. Hibernate, misalnya, dapat menggunakan batching JDBC meskipun tidak melakukannya secara default.

libpq dan pengelompokan

Sebagian besar (semua?) driver PostgreSQL lainnya tidak memiliki dukungan untuk batching. PgJDBC mengimplementasikan protokol PostgreSQL sepenuhnya secara independen, sedangkan sebagian besar driver lain secara internal menggunakan pustaka C libpq yang disediakan sebagai bagian dari PostgreSQL.

libpq tidak mendukung pengelompokan. Itu memang memiliki API non-pemblokiran asinkron, tetapi klien masih hanya dapat memiliki satu kueri "dalam penerbangan" pada satu waktu. Itu harus menunggu sampai hasil kueri itu diterima sebelum bisa mengirim yang lain.

PostgreSQL server mendukung batching dengan baik, dan PgJDBC sudah menggunakannya. Jadi saya telah menulis dukungan batch untuk libpq dan mengirimkannya sebagai kandidat untuk versi PostgreSQL berikutnya. Karena itu hanya mengubah klien, jika diterima, itu akan tetap mempercepat saat menghubungkan ke server yang lebih lama.

Saya akan sangat tertarik dengan masukan dari penulis dan pengguna tingkat lanjut libpq driver klien berbasis dan pengembang libpq -aplikasi berbasis. Tambalan ini berlaku baik di atas PostgreSQL 9.6beta1 jika Anda ingin mencobanya. Dokumentasinya terperinci dan ada contoh program yang komprehensif.

Kinerja

Saya pikir layanan database yang di-host seperti RDS atau Heroku Postgres akan menjadi contoh yang baik di mana fungsi semacam ini akan berguna. Secara khusus, mengakses mereka dari jaringan mereka sendiri benar-benar menunjukkan seberapa banyak latensi yang dapat merugikan.

Pada latensi jaringan ~320 md:

  • 500 sisipan tanpa pengelompokan:167.0s
  • 500 sisipan dengan pengelompokan:1.2s

… yang lebih dari 120x lebih cepat.

Anda biasanya tidak akan menjalankan aplikasi Anda melalui tautan antarbenua antara server aplikasi dan database, tetapi ini berfungsi untuk menyoroti dampak latensi. Bahkan melalui soket unix ke localhost saya melihat peningkatan kinerja lebih dari 50% untuk 10.000 sisipan.

Mengumpulkan di aplikasi yang ada

Sayangnya tidak mungkin untuk secara otomatis mengaktifkan batching untuk aplikasi yang ada. Aplikasi harus menggunakan antarmuka yang sedikit berbeda tempat mereka mengirim serangkaian kueri dan baru kemudian menanyakan hasilnya.

Seharusnya cukup mudah untuk mengadaptasi aplikasi yang sudah menggunakan antarmuka libpq asinkron, terutama jika mereka menggunakan mode non-pemblokiran dan select() /jajak pendapat() /epoll() /WaitForMultipleObjectsEx lingkaran. Aplikasi yang menggunakan libpq sinkron antarmuka akan membutuhkan lebih banyak perubahan.

Batching di driver klien lain

Demikian pula, driver klien, kerangka kerja dan ORM umumnya akan membutuhkan antarmuka dan perubahan internal untuk mengizinkan penggunaan batching. Jika mereka sudah menggunakan loop peristiwa dan I/O non-pemblokiran, mereka seharusnya cukup mudah untuk dimodifikasi.

Saya ingin melihat pengguna Python, Ruby, dll dapat mengakses fungsi ini, jadi saya ingin tahu siapa yang tertarik. Bayangkan bisa melakukan ini:

import psycopg2
conn = psycopg2.connect(...)
cur = conn.cursor()

# this is just an idea, this code does not work with psycopg2:
futures = [ cur.async_execute(sql) for sql in my_queries ]
for future in futures:
    result = future.result  # waits if result not ready yet
    ... process the result ...
conn.commit()

Eksekusi batch asinkron tidak harus rumit di tingkat klien.

COPY adalah yang tercepat

Di mana klien praktis harus tetap menyukai COPY . Berikut beberapa hasil dari laptop saya:

inserting 1000000 rows batched, unbatched and with COPY
batch insert elapsed:      23.715315s
sequential insert elapsed: 36.150162s
COPY elapsed:              1.743593s
Done.

Mengelompokkan pekerjaan memberikan peningkatan kinerja yang sangat besar bahkan pada koneksi soket unix lokal…. tapi COPY meninggalkan kedua sisipan individu mendekati jauh di belakangnya dalam debu.

Gunakan SALIN .

Gambar

Gambar untuk pos ini adalah jalur pipa Skema Penyediaan Air Goldfields dari Mundaring Weir dekat Perth di Australia Barat ke ladang emas pedalaman (gurun). Ini relevan karena butuh waktu lama untuk menyelesaikannya dan berada di bawah kritik keras sehingga perancang dan pendukung utamanya, C. Y. O'Connor, bunuh diri 12 bulan sebelum ditugaskan. Orang lokal sering (salah) mengatakan bahwa dia meninggal after pipa dibangun ketika tidak ada air yang mengalir – karena hanya butuh waktu lama semua orang mengira proyek pipa itu gagal. Kemudian berminggu-minggu kemudian, air mengalir keluar.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Permintaan SQL untuk mendapatkan baris terbaru untuk setiap instance dari kunci yang diberikan

  2. Apa Kerangka Ketersediaan Tinggi PostgreSQL Terbaik? Infografis PAF vs. repmgr vs. Patroni

  3. Adakah kerugian menggunakan teks tipe data untuk menyimpan string?

  4. Instalasi PostgreSQL di Docker

  5. Melacak Ketersediaan Tinggi untuk PostgreSQL Dengan Detak Jantung