Untuk waktu yang lama, salah satu kekurangan PostgreSQL yang paling terkenal adalah kemampuan untuk memparalelkan kueri. Dengan rilis versi 9.6, ini tidak akan menjadi masalah lagi. Pekerjaan yang bagus telah dilakukan dalam hal ini, mulai dari commit 80558c1, pengenalan pemindaian sekuensial paralel, yang akan kita lihat dalam artikel ini.
Pertama, Anda harus memperhatikan:pengembangan fitur ini terus menerus dan beberapa parameter telah berganti nama antara komit dan komit lainnya. Artikel ini ditulis menggunakan checkout yang diambil pada 17 Juni dan beberapa fitur yang diilustrasikan di sini hanya akan hadir dalam versi 9.6 beta2.
Dibandingkan dengan rilis 9.5, parameter baru telah diperkenalkan di dalam file konfigurasi. Ini adalah:
- max_parallel_workers_per_gather :jumlah pekerja yang dapat membantu pemindaian tabel secara berurutan;
- min_parallel_relation_size :ukuran minimum yang harus dimiliki suatu relasi agar perencana mempertimbangkan penggunaan pekerja tambahan;
- parallel_setup_cost :parameter perencana yang memperkirakan biaya instantiate seorang pekerja;
- parallel_tuple_cost :parameter perencana yang memperkirakan biaya transfer tuple dari satu pekerja ke pekerja lain;
- force_parallel_mode :parameter yang berguna untuk pengujian, paralelisme yang kuat, dan juga kueri di mana perencana akan beroperasi dengan cara lain.
Mari kita lihat bagaimana pekerja tambahan dapat digunakan untuk mempercepat kueri kita. Kami membuat tabel pengujian dengan bidang INT dan seratus juta catatan:
postgres=# CREATE TABLE test (i int);
CREATE TABLE
postgres=# INSERT INTO test SELECT generate_series(1,100000000);
INSERT 0 100000000
postgres=# ANALYSE test;
ANALYZE
PostgreSQL memiliki max_parallel_workers_per_gather
diatur ke 2 secara default, di mana dua pekerja akan diaktifkan selama pemindaian berurutan.
Pemindaian sekuensial sederhana tidak menghadirkan hal baru:
postgres=# EXPLAIN ANALYSE SELECT * FROM test;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Seq Scan on test (cost=0.00..1442478.32 rows=100000032 width=4) (actual time=0.081..21051.918 rows=100000000 loops=1)
Planning time: 0.077 ms
Execution time: 28055.993 ms
(3 rows)
Bahkan, kehadiran WHERE
klausa diperlukan untuk paralelisasi:
postgres=# EXPLAIN ANALYZE SELECT * FROM test WHERE i=1;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..964311.60 rows=1 width=4) (actual time=3.381..9799.942 rows=1 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on test (cost=0.00..963311.50 rows=0 width=4) (actual time=6525.595..9791.066 rows=0 loops=3)
Filter: (i = 1)
Rows Removed by Filter: 33333333
Planning time: 0.130 ms
Execution time: 9804.484 ms
(8 rows)
Kita dapat kembali ke tindakan sebelumnya dan mengamati perbedaan pengaturan max_parallel_workers_per_gather
ke 0:
postgres=# SET max_parallel_workers_per_gather TO 0;
SET
postgres=# EXPLAIN ANALYZE SELECT * FROM test WHERE i=1;
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Seq Scan on test (cost=0.00..1692478.40 rows=1 width=4) (actual time=0.123..25003.221 rows=1 loops=1)
Filter: (i = 1)
Rows Removed by Filter: 99999999
Planning time: 0.105 ms
Execution time: 25003.263 ms
(5 rows)
Waktu 2,5 kali lebih besar.
Perencana tidak selalu menganggap pemindaian sekuensial paralel sebagai pilihan terbaik. Jika kueri tidak cukup selektif dan ada banyak tupel untuk ditransfer dari pekerja ke pekerja, mungkin lebih memilih pemindaian sekuensial "klasik":
postgres=# SET max_parallel_workers_per_gather TO 2;
SET
postgres=# EXPLAIN ANALYZE SELECT * FROM test WHERE i<90000000;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Seq Scan on test (cost=0.00..1692478.40 rows=90116088 width=4) (actual time=0.073..31410.276 rows=89999999 loops=1)
Filter: (i < 90000000)
Rows Removed by Filter: 10000001
Planning time: 0.133 ms
Execution time: 37939.401 ms
(5 rows)
Faktanya, jika kita mencoba untuk memaksa pemindaian sekuensial paralel, kita mendapatkan hasil yang lebih buruk:
postgres=# SET parallel_tuple_cost TO 0;
SET
postgres=# EXPLAIN ANALYZE SELECT * FROM test WHERE i<90000000;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..964311.50 rows=90116088 width=4) (actual time=0.454..75546.078 rows=89999999 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on test (cost=0.00..1338795.20 rows=37548370 width=4) (actual time=0.088..20294.670 rows=30000000 loops=3)
Filter: (i < 90000000)
Rows Removed by Filter: 3333334
Planning time: 0.128 ms
Execution time: 83423.577 ms
(8 rows)
Jumlah pekerja dapat ditingkatkan hingga max_worker_processes
(standar:8). Kami mengembalikan nilai parallel_tuple_cost
dan kami melihat apa yang terjadi dengan meningkatkan max_parallel_workers_per_gather
ke 8.
postgres=# SET parallel_tuple_cost TO DEFAULT ;
SET
postgres=# SET max_parallel_workers_per_gather TO 8;
SET
postgres=# EXPLAIN ANALYZE SELECT * FROM test WHERE i=1;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..651811.50 rows=1 width=4) (actual time=3.684..8248.307 rows=1 loops=1)
Workers Planned: 6
Workers Launched: 6
-> Parallel Seq Scan on test (cost=0.00..650811.40 rows=0 width=4) (actual time=7053.761..8231.174 rows=0 loops=7)
Filter: (i = 1)
Rows Removed by Filter: 14285714
Planning time: 0.124 ms
Execution time: 8250.461 ms
(8 rows)
Meskipun PostgreSQL dapat menggunakan hingga 8 pekerja, PostgreSQL hanya menggunakan enam. Ini karena Postgres juga mengoptimalkan jumlah pekerja sesuai dengan ukuran tabel dan min_parallel_relation_size
. Jumlah pekerja yang disediakan oleh postgres didasarkan pada perkembangan geometris dengan 3 sebagai rasio umum 3 dan min_parallel_relation_size
sebagai faktor skala. Berikut adalah contoh. Mempertimbangkan 8MB parameter default:
Ukuran | Pekerja |
---|---|
<8MB | 0 |
<24MB | 1 |
<72MB | 2 |
<216MB | 3 |
<648MB | 4 |
<1944MB | 5 |
<5822MB | 6 |
Ukuran meja kami adalah 3458MB, jadi 6 adalah jumlah maksimum pekerja yang tersedia.
postgres=# \dt+ test
List of relations
Schema | Name | Type | Owner | Size | Description
--------+------+-------+----------+---------+-------------
public | test | table | postgres | 3458 MB |
(1 row)
Akhirnya, saya akan memberikan demonstrasi singkat tentang peningkatan yang dicapai melalui patch ini. Menjalankan kueri kami dengan jumlah pekerja yang terus bertambah, kami memperoleh hasil berikut:
Pekerja | Waktu |
---|---|
0 | 24767.848 md |
1 | 14855.961 md |
2 | 10415.661 md |
3 | 8041.187 md |
4 | 8090.855 md |
5 | 8082.937 md |
6 | 8061.939 md |
Kita dapat melihat bahwa waktu meningkat secara dramatis, sampai Anda mencapai sepertiga dari nilai awal. Juga sederhana untuk menjelaskan fakta bahwa kami tidak melihat peningkatan antara penggunaan 3 dan 6 pekerja:mesin tempat pengujian dijalankan memiliki 4 CPU, sehingga hasilnya stabil setelah menambahkan 3 pekerja lagi ke proses awal .
Terakhir, PostgreSQL 9.6 telah menetapkan tahapan untuk paralelisasi kueri, di mana pemindaian sekuensial paralel hanya merupakan hasil pertama yang bagus. Kita juga akan melihat bahwa di 9.6, agregasi telah diparalelkan, tetapi itu adalah informasi untuk artikel lain yang akan dirilis dalam beberapa minggu mendatang!