Dalam artikel ini, saya akan mempertimbangkan untuk meningkatkan kecepatan laporan. Dengan laporan, maksud saya setiap permintaan ke database yang menggunakan fungsi agregat. Juga, saya akan menyentuh masalah yang berkaitan dengan sumber daya yang dihabiskan untuk produksi dan dukungan laporan, baik manusia dan mesin.
Dalam contoh, saya akan menggunakan dataset yang berisi 52.608.000 catatan.
Dengan menggunakan contoh cadangan analitis yang tidak sulit, saya akan menunjukkan bahwa bahkan komputer yang lemah dapat diubah menjadi alat yang baik untuk menganalisis jumlah data yang βlayakβ tanpa banyak usaha.
Setelah menyiapkan eksperimen yang tidak rumit, kita akan melihat bahwa tabel biasa bukan sumber yang cocok untuk kueri analitik.
Jika pembaca dapat dengan mudah menguraikan singkatan OLTP dan OLAP, mungkin masuk akal untuk langsung menuju bagian
ColumnstoreDua pendekatan untuk bekerja dengan data
Di sini saya akan singkat, karena Ada lebih dari cukup informasi tentang topik ini di Internet.
Jadi, pada level tertinggi, hanya ada dua pendekatan untuk bekerja dengan data: OLTP dan OLAP.
OLTP - dapat diterjemahkan sebagai pemrosesan transaksi instan. Bahkan, kita berbicara tentang pemrosesan online transaksi pendek yang bekerja dengan sejumlah kecil data. Misalnya, merekam, memperbarui, atau menghapus pesanan. Dalam sebagian besar kasus, pesanan adalah jumlah data yang sangat kecil, selama pemrosesan yang Anda tidak bisa takut dengan kunci panjang yang diberlakukan oleh RDBMS modern.
OLAP - dapat diterjemahkan sebagai pemrosesan analitis dari sejumlah besar transaksi pada suatu waktu. Setiap laporan menggunakan pendekatan khusus ini, karena dalam sebagian besar kasus, laporan tersebut menghasilkan ringkasan, angka agregat untuk bagian-bagian tertentu.
Setiap pendekatan memiliki teknologinya sendiri. Sebagai contoh, untuk OLTP itu adalah PostgreSQL, dan untuk OLAP itu adalah Microsoft SQL Server Analysis Services. Sementara PostgresSQL menggunakan format terkenal untuk menyimpan data dalam tabel, beberapa format berbeda diciptakan untuk OLAP. Ini adalah tabel multidimensi, ember yang diisi dengan pasangan nilai kunci dan toko kolom favorit saya. Tentang yang terakhir lebih terinci di bawah ini.
Mengapa dua pendekatan dibutuhkan?
Tercatat bahwa setiap gudang data cepat atau lambat menghadapi dua jenis pemuatan: sering membaca (menulis dan memperbarui, tentu saja) dari jumlah data yang sangat kecil dan pembacaan yang jarang, tetapi data dalam jumlah yang sangat besar. Sebenarnya, ini adalah kegiatan, misalnya, dari box office dan kepala. Meja kas, bekerja sepanjang hari, mengisi penyimpanan dengan potongan-potongan kecil data, sementara pada akhirnya volume akumulasi, jika bisnis berjalan dengan baik, mencapai ukuran yang mengesankan. Pada gilirannya, manajer pada akhir hari ingin tahu berapa banyak uang yang diperoleh box office per hari.
Jadi, dalam OLTP kami memiliki tabel dan indeks. Kedua alat ini sangat bagus untuk merekam aktivitas box office dengan semua detailnya. Indeks menyediakan pencarian cepat untuk pesanan yang direkam sebelumnya, sehingga mengubah pesanan itu mudah. Tetapi untuk memenuhi kebutuhan pemimpin, kita perlu mempertimbangkan seluruh jumlah akumulasi data per hari. Selain itu, sebagai aturan, manajer tidak membutuhkan semua detail dari semua pesanan. Yang benar-benar perlu dia ketahui adalah berapa banyak uang yang dihasilkan box office pada umumnya. Tidak masalah di mana kantor tiket berada, ketika ada istirahat makan siang, siapa yang bekerja untuk itu, dll. OLAP ada saat itu, sehingga dalam periode waktu yang singkat sistem dapat menjawab pertanyaan - berapa banyak yang telah diterima perusahaan secara keseluruhan tanpa membaca urutan masing-masing pesanan dan semua rinciannya. Bisakah OLAP menggunakan tabel dan indeks yang sama dengan OLTP? Jawabannya adalah tidak, setidaknya tidak seharusnya. Pertama, karena OLAP tidak membutuhkan semua detail yang dicatat dalam tabel. Masalah ini diselesaikan dengan menyimpan data dalam format lain selain tabel dua dimensi. Kedua, informasi yang dianalisis sering tersebar di berbagai tabel, yang melibatkan banyak asosiasi mereka, termasuk asosiasi dari tipe self-join. Untuk mengatasi masalah ini, sebagai aturan, mereka mengembangkan skema basis data khusus. Skema ini dioptimalkan untuk beban OLAP, serta skema normalisasi normal untuk beban OLTP.
Apa yang terjadi ketika OLAP menggunakan skema OLTP
Bahkan, saya memperkenalkan bagian ini sehingga artikel ini dengan jelas memenuhi persyaratan saya sendiri untuk format materi seperti itu, yaitu masalah, solusi, kesimpulan.
Kami mencantumkan sejumlah kelemahan menggunakan skema OLTP untuk analisis data.
- Terlalu banyak indeks.
Seringkali, Anda harus membuat indeks khusus untuk mendukung laporan. Indeks ini menerapkan skema penyimpanan data OLAP. Mereka tidak digunakan oleh bagian OLTP dari aplikasi, sambil mengerahkan beban di atasnya, membutuhkan dukungan konstan dan mengambil ruang disk. - Jumlah data yang dibaca melebihi yang dibutuhkan.
- Kurangnya skema data yang jelas.
Faktanya adalah bahwa seringkali informasi yang disampaikan oleh laporan dalam satu bentuk tersebar dalam tabel yang berbeda. Informasi tersebut memerlukan transformasi konstan dengan cepat. Contoh paling sederhana adalah jumlah pendapatan, yang terdiri dari uang tunai dan uang non tunai. Contoh mencolok lainnya adalah hierarki data. Karena pengembangan aplikasi bersifat progresif dan tidak selalu diketahui apa yang akan dibutuhkan di masa depan, hierarki yang sama dalam makna dapat disimpan dalam tabel yang berbeda. Dan sementara akuisisi sambil terbang secara aktif digunakan dalam OLAP, ini adalah hal yang sedikit berbeda. - Kompleksitas permintaan yang berlebihan.
Karena Skema OLTP berbeda dari OLAP. Lapisan perangkat lunak yang sangat terkait diperlukan yang membawa skema data OLTP ke bentuk yang tepat. - Kompleksitas dukungan, debugging, dan pengembangan.
Secara umum, kita dapat mengatakan bahwa semakin kompleks basis kode, semakin sulit untuk mempertahankannya dalam keadaan sehat. Ini adalah aksioma. - Kompleksitas cakupan tes.
Banyak salinan yang rusak karena diskusi tentang cara mendapatkan database yang penuh dengan semua skrip tes, tetapi lebih baik untuk mengatakan bahwa dengan memiliki skema data yang lebih sederhana, tugas meliput dengan tes disederhanakan berkali-kali. - Debugging kinerja tanpa akhir.
Ada kemungkinan besar bahwa pengguna akan memesan laporan yang "berat" untuk server database. Probabilitas ini meningkat seiring waktu. Perlu dicatat bahwa OLAP juga rentan terhadap masalah ini, tetapi tidak seperti OLTP, sumber daya OLAP dalam hal ini jauh lebih tinggi.
Kolom tokoArtikel ini akan fokus pada format penyimpanan toko kolom, tetapi tanpa detail tingkat rendah. Format lain yang disebutkan di atas juga patut mendapat perhatian, tetapi ini adalah topik untuk artikel lain.
Sebenarnya, format columnstore sudah dikenal selama 30 tahun, tetapi itu belum diterapkan dalam RDBMS sampai saat ini. Inti dari kolomstore adalah bahwa data disimpan bukan dalam baris, tetapi dalam kolom. Yaitu pada satu halaman (semua diketahui 8 Kb) server merekam data hanya dari satu bidang. Demikian juga dengan masing-masing bidang pada tabel. Ini diperlukan agar Anda tidak harus membaca informasi tambahan. Mari kita bayangkan tabel dengan 10 bidang dan kueri yang hanya memiliki satu bidang yang ditentukan dalam pernyataan SELECT. Jika tabel biasa disimpan dalam format berbasis baris, server akan dipaksa untuk membaca semua 10 bidang, tetapi pada saat yang sama mengembalikan hanya satu. Ternyata server membaca informasi 9 kali lebih banyak dari yang diperlukan. Columnstore sepenuhnya memecahkan masalah ini, karena format penyimpanan memungkinkan Anda membaca hanya satu bidang yang dipesan. Semua ini terjadi karena unit penyimpanan dalam RDBMS adalah halaman. Yaitu server selalu menulis dan membaca setidaknya satu halaman. Satu-satunya pertanyaan adalah berapa banyak bidang yang ada di sana.
Bagaimana Columnstore Dapat Sangat Membantu
Untuk menjawab ini harus memiliki angka yang tepat. Ayo kita ambil. Tetapi angka apa yang dapat memberikan gambaran yang akurat?
- Jumlah ruang disk.
- Performa permintaan.
- Toleransi kesalahan.
- Kemudahan implementasi.
- Keterampilan baru apa yang harus dimiliki pengembang untuk bekerja dengan struktur baru.
Ruang disk
Mari kita buat tabel sederhana, isi dengan data dan periksa berapa banyak ruang yang dibutuhkan.
create foreign table cstore_table ( trd date, org int, op int, it int, wh int, m1 numeric(32, 2), m2 numeric(32, 2), m3 numeric(32, 2), m4 numeric(32, 2), m5 numeric(32, 2) ) server cstore_server options(compression 'pglz');
Seperti yang Anda perhatikan, saya membuat tabel eksternal. Faktanya adalah PostgreSQL tidak memiliki dukungan kolom toko bawaan. Tetapi PostgreSQL memiliki sistem ekstensi yang kuat. Salah satunya memungkinkan untuk membuat tabel toko kolom. Tautan di akhir artikel.
- pglz - memberi tahu ekstensi bahwa data harus dikompres menggunakan algoritma bawaan di PostgreSQL;
- trd - waktu transaksi;
- op, itu, bagian atau pengukuran analitik;
- m1, m2, m3, m4, m5 - indikator atau ukuran numerik;
Mari kita masukkan jumlah data yang "layak" dan lihat berapa banyak ruang yang dibutuhkan pada disk. Pada saat yang sama, kami memeriksa kinerja sisipan. Karena Saya menaruh eksperimen saya di laptop rumah, saya sedikit organik dalam jumlah data. Selain itu, yang bahkan lebih baik, saya akan menggunakan HDD yang menjalankan OS guest Fedora 30. Host OS - Windows 10 Home Edition. Prosesor Intel Core 7. Guest OS menerima 4 GB RAM. Versi PostgreSQL - PostgreSQL 10.10 pada x86_64-pc-linux-gnu, dikompilasi oleh gcc (GCC) 9.1.1 20190503 (Red Hat 9.1.1-1), 64-bit. Saya akan bereksperimen dengan kumpulan data dengan jumlah catatan 52 608 000.
explain (analyze) insert into cstore_table select '2010-01-01'::date + make_interval(days => d) as trd , op , org , wh , it , 100 as m1 , 100 as m2 , 100 as m3 , 100 as m4 , 100 as m5 from generate_series(0, 1) as op cross join generate_series(1, 2) as org cross join generate_series(1, 3) as wh cross join generate_series(1, 4000) as it cross join generate_series(0, 1095) as d;
Rencana implementasi adalah sebagai berikut
Masukkan pada cstore_table (biaya = 0,01..24902714242540.01 baris = lebar 1000000000000000 = 150) (waktu aktual = 119560.456..119560.456 baris = 0 loop = 1)
----> Nested Loop (biaya = 0,01..24902714242540.01 baris = lebar 1000000000000000 = 150) (waktu aktual = 1.823..22339.976 baris = 52608000 putaran = 1)
----------> Pemindaian Fungsi pada gener_series d (biaya = 0,00..10,00 baris = 1000 lebar = 4) (waktu aktual = 0,151..2.198 baris = 1096 loop = 1)
----------> Terwujud (biaya = 0,01..27284555030.01 baris = lebar 1000000000000 = 16) (waktu aktual = 0,002..3.196 baris = 48000 loop = 1096)
----------------> Nested Loop (biaya = 0,01..17401742530.01 baris = lebar 1000000000000 = 16) (waktu aktual = 1.461..15.072 baris = 48.000 loop = 1)
----------------------> Pemindaian Fungsi pada generate_series itu (biaya = 0,00..10,00 baris = 1000 lebar = 4) (waktu aktual = 1.159..2.007 baris = 4000 loop = 1)
----------------------> Terwujud (biaya = 0,01..26312333.01 baris = 1.000.000.000 lebar = 12) (waktu aktual = 0.000..0.001 baris = 12 loop = 4000)
----------------------------> Nested Loop (biaya = 0,01..16429520.01 baris = 1.000.000.000 lebar = 12) (waktu aktual = 0.257 ..0.485 baris = 12 loop = 1)
----------------------------------> Pemindaian Fungsi pada generate_series wh (biaya = 0,00..10,00 baris = 1000 width = 4) (waktu aktual = 0,046..0.049 baris = 3 loop = 1)
----------------------------------> Terwujud (biaya = 0,01..28917.01 baris = 1.000.000 lebar = 8) (waktu aktual = 0,070..0.139 baris = 4 putaran = 3)
---------------------------------------> Nested Loop (biaya = 0,01..20010.01 baris = Lebar 10.00000 = 8) (waktu aktual = 0.173..0.366 baris = 4 loop = 1)
-------------------------------------------> Pemindaian Fungsi pada generate_series op ( biaya = 0,00..10,00 baris = 1000 lebar = 4) (waktu aktual = 0,076..0.079 baris = 2 loop = 1)
---------------------------------------------> Pemindaian Fungsi pada generate_series org (biaya = 0,00..10,00 baris = 1000 lebar = 4) (waktu aktual = 0,043..0.047 baris = 2 loop = 2)
Waktu perencanaan: 0,439 ms
Waktu eksekusi: 119692.051 ms
Total waktu tunggu - 1.994867517 menit
Waktu pembuatan kumpulan data - 22,339976 detik
Waktu penyisipan - 1.620341333 menit
Saya tidak berhasil mengevaluasi ruang disk yang ditempati menggunakan fungsi PostgreSQL. Tidak yakin mengapa, tetapi menunjukkan 0. Mungkin ini adalah perilaku standar untuk tabel eksternal. Digunakan untuk pengelola file ini. Jadi, volume ruang disk yang ditempati adalah 226,2 Mb. Untuk mengevaluasi banyak atau sedikit, mari kita bandingkan dengan tabel biasa.
explain (analyze) create table rbstore_table as select '2010-01-01'::date + make_interval(days => d) as trd , op , org , wh , it , 100 as m1 , 100 as m2 , 100 as m3 , 100 as m4 , 100 as m5 from generate_series(0, 1) as op cross join generate_series(1, 2) as org cross join generate_series(1, 3) as wh cross join generate_series(1, 4000) as it cross join generate_series(0, 1095) as d;
Rencana implementasi adalah sebagai berikut
Nested Loop (biaya = 0,01..22402714242540.01 baris = lebar 1000000000000000 = 44) (waktu aktual = 0,585..23781.942 baris = 52608000 putaran = 1)
---> Function Scan on gener_series d (biaya = 0,00..10,00 baris = 1000 lebar = 4) (waktu aktual = 0,091..2.130 baris = 1096 loop = 1)
---> Terwujud (biaya = 0,01..27284555030.01 baris = lebar 10.00000000000 = 16) (waktu aktual = 0.001..3.574 baris = 48.000 loop = 1096)
----------> Nested Loop (biaya = 0,01..17401742530.01 baris = lebar 1000000000000 = 16) (waktu aktual = 0.489..14.044 baris = 48.000 loop = 1)
----------------> Pemindaian Fungsi pada generate_series itu (biaya = 0,00..10,00 baris = 1000 lebar = 4) (waktu aktual = 0,477..1.352 baris = 4000 loop = 1 )
----------------> Terwujud (biaya = 0,01..26312333.01 baris = 10.000.000 lebar = 12) (waktu aktual = 0.000..0.001 baris = 12 loop = 4000)
----------------------> Nested Loop (biaya = 0,01..16429520.01 baris = 1.000.000.000 lebar = 12) (waktu aktual = 0,010..0.019 baris = 12 loop = 1)
----------------------------> Pemindaian Fungsi pada generate_series wh (biaya = 0,00..10,00 baris = 1000 lebar = 4) (aktual waktu = 0,003..0.003 baris = 3 loop = 1)
----------------------------> Terwujud (biaya = 0,01..28917.01 baris = 1.000.000 lebar = 8) (waktu aktual = 0,002. .0,004 baris = 4 loop = 3)
----------------------------------> Nested Loop (biaya = 0,01..20010.01 baris = 1.000.000 lebar = 8 ) (waktu aktual = 0,006..0.009 baris = 4 loop = 1)
----------------------------------------> Pemindaian Fungsi pada menghasilkan_series op (biaya = 0,00 ..10.00 baris = 1000 lebar = 4) (waktu aktual = 0.002..0.002 baris = 2 loop = 1)
----------------------------------------> Pemindaian Fungsi pada generate_series org (biaya = 0,00 ..10.00 baris = 1000 lebar = 4) (waktu aktual = 0.001..0.001 baris = 2 loop = 2)
Waktu perencanaan: 0,569 ms
Waktu eksekusi: 378883.989 ms
Waktu yang dihabiskan untuk implementasi rencana ini tidak menarik bagi kami, karena dalam kehidupan nyata, sisipan seperti itu tidak seharusnya. Kami tertarik pada seberapa banyak ruang disk yang ditempati tabel ini. Setelah memenuhi permintaan fungsi sistem, saya menerima 3,75 GB.
Jadi, cstore_table - 226 MB, rbstore_table - 3,75 GB. Perbedaan 16,99 kali mencolok, tetapi tidak mungkin bahwa perbedaan yang sama dapat diperoleh dalam produksi, terutama karena distribusi data. Sebagai aturan, perbedaan ini akan lebih sedikit dan sekitar 5 kali.
Tapi tunggu, tidak ada yang menggunakan data mentah dalam format berbasis baris untuk keperluan analisis. Misalnya, mereka mencoba menggunakan data yang diindeks untuk pelaporan. Dan karena "Raw" data akan selalu, Anda perlu membandingkan ukuran dengan ukuran indeks. Mari buat setidaknya satu indeks. Biarkan itu menjadi indeks pada bidang tanggal dan jenis operasi - trd + op.
Jadi, saya hanya mengindeks dua bidang, dan indeks mengambil 1.583 MB, yang jauh lebih dari cstore_table. Namun, sebagai aturan, lebih dari satu indeks diperlukan untuk memuat OLAP. Penting untuk dicatat di sini bahwa cstore_table tidak perlu pengindeksan tambahan. Tabel ini bertindak sebagai indeks yang mencakup pertanyaan apa pun.
Dari semua hal di atas, kesimpulan sederhana dapat dibuat - menggunakan tabel columnstore, Anda dapat mengurangi jumlah ruang disk yang digunakan.
Performa Permintaan
Untuk mengevaluasi kinerja, mari jalankan kueri yang mengembalikan data ringkasan untuk bulan tertentu untuk jenis operasi tertentu.
explain (analyze, costs, buffers) select sum(m1) from cstore_table where trd = '2011-01-01' and op = 1;
Rencana implementasi adalah sebagai berikut
Agregat (biaya = 793602.69..793602.70 baris = 1 lebar = 32) (waktu aktual = 79.708..79.708 baris = 1 loop = 1)
--Buffers: hit bersama = 44226
---> Pemindaian Asing di cstore_table (biaya = 0,00..793544.70 baris = 23197 lebar = 5) (waktu aktual = 23.209..76.628 baris = 24000 loop = 1)
-------- Filter: ((trd = '2011-01-01' :: date) AND (op = 1))
-------- Baris Dihapus oleh Filter: 26000
-------- File CStore: / var / lib / pgsql / 10 / data / cstore_fdw / 14028/16417
-------- CStore Ukuran File: 120818897
-------- Buffer: klik bersama = 44226
Waktu perencanaan: 0,165 ms
Waktu pelaksanaan: 79.887 ms
Dan
explain (analyze, costs, buffers) select sum(m1) from rbstore_table where trd = '2011-01-01' and op = 1;
Rencana implementasi adalah sebagai berikut
Agregat (biaya = 40053.80..40053.81 baris = 1 lebar = 8) (waktu aktual = 389.183..389.183 baris = 1 loop = 1)
--Buffers: shared read = 545
---> Pemindaian Indeks menggunakan trd_op_ix pada rbstore_table (biaya = 0,56..39996.70 baris = lebar 22841 = 4) (waktu aktual = 55.955..385.283 baris = 24000 loop = 1)
-------- Indeks Cond: ((trd = '2011-01-01 00:00:00' :: timestamp tanpa zona waktu) AND (op = 1))
-------- Buffer: shared read = 545
Waktu perencanaan: 112.175 ms
Waktu pelaksanaan: 389.219 ms
389.219 ms vs 79.887 ms. Di sini kita melihat bahwa bahkan pada sejumlah kecil data toko kolom, sebuah tabel secara signifikan lebih cepat daripada indeks pada tabel berbasis baris.
Mari ubah permintaan dan coba dapatkan unit untuk keseluruhan tahun 2011.
explain (analyze, costs, buffers) select sum(m1) from cstore_table where trd between '2011-01-01' and '2011-12-31' and op = 1;
Rencana implementasi adalah sebagai berikut
Agregat (biaya = 946625.58..946625.59 baris = 1 lebar = 32) (waktu aktual = 3123.604..3123.604 baris = 1 putaran = 1)
--Buffers: hit bersama = 44226
---> Pemindaian Asing di cstore_table (biaya = 0,00..925064.70 baris = 8624349 lebar = 5) (waktu aktual = 21.728..2100.665 baris = 8760000 loop = 1)
-------- Filter: ((trd> = '2011-01-01' :: date) AND (trd <= '2011-12-31' :: date) AND (op = 1))
-------- Baris Dihapus oleh Filter: 8760000
-------- File CStore: / var / lib / pgsql / 10 / data / cstore_fdw / 14028/16411
-------- CStore Ukuran File: 120818897
-------- Buffer: klik bersama = 44226
Waktu perencanaan: 0,212 ms
Waktu pelaksanaan: 3123.960 ms
Dan
explain (analyze, costs, buffers) select sum(m1) from rbstore_table where trd between '2011-01-01' and '2011-12-31' and op = 1;
Rencana implementasi adalah sebagai berikut
Finalisasi Agregat (biaya = 885214,33..885214,34 baris = 1 lebar = 8) (waktu aktual = 98512,560..98512,560 baris = 1 putaran = 1)
--Buffers: hit bersama = 2565 baca = 489099
---> Kumpulkan (biaya = 885214.12..885214.33 baris = 2 lebar = 8) (waktu aktual = 98427.034..98523.194 baris = 3 loop = 1)
-------- Pekerja yang Direncanakan: 2
-------- Peluncuran Pekerja: 2
-------- Buffer: klik bersama = 2565 baca = 489099
---------> Agregat Parsial (biaya = 884214.12..884214.13 baris = 1 lebar = 8) (waktu aktual = 97907.608..97907.608 baris = 1 loop = 3)
-------------- Buffer: klik bersama = 2565 baca = 489099
---------------> Pemindaian Seq Paralel pada rbstore_table (biaya = 0,00..875264,00 baris = 3580047 lebar = 4) (waktu aktual = 40820,004..97405.250 baris = 2920000 loop = 3)
--------------------- Filter: ((trd> = '2011-01-01 00:00:00' :: timestamp tanpa zona waktu) DAN (trd <= '2011-12-31 00:00:00' :: stempel waktu tanpa zona waktu) DAN (op = 1))
-------------------- Baris Dihapus oleh Filter: 14616000
-------------------- Buffer: klik bersama = 2565 baca = 489099
Waktu perencanaan: 7,899 ms
Waktu pelaksanaan: 98523.278 ms
98523.278 ms vs 3123.960 ms. Mungkin sebagian indeks akan membantu kami, tetapi lebih baik tidak mengambil risiko dan membuat struktur berbasis row_ yang cocok di mana nilai-nilai yang sudah jadi akan disimpan.
Agregat manual
Struktur yang cocok untuk agregat manual bisa berupa tabel row_based reguler yang berisi nilai yang dikomputasi. Misalnya, mungkin berisi catatan yang terkait dengan 2011 dengan jenis operasi sama dengan 1, sedangkan di bidang m1, m2, m3, m4 dan m5 nilai agregat akan disimpan secara tepat untuk bagian analitik ini. Dengan demikian, dengan memiliki sekumpulan agregat dan indeks yang memadai, permintaan analitik memperoleh kinerja yang belum pernah terjadi sebelumnya. Yang menarik, Layanan Analisis Microsoft SQL Server memiliki wizard khusus yang memungkinkan Anda untuk mengonfigurasi jumlah dan kedalaman nilai yang dihitung sebelumnya.
Solusi ini memiliki keunggulan sebagai berikut:
- Analisis waktu nyata.
Tolong jangan bingung dengan istilah "analitik waktu-nyata". Di sini kita berbicara tentang fakta bahwa kenaikan unit terjadi selama periode waktu yang dapat diterima di sebagian besar kasus.
Sebenarnya, nilai plus ini kontroversial, tetapi jangan bicarakan hal ini. Faktanya tetap. Arsitektur solusinya sedemikian rupa sehingga unit tetap "segar" hampir selalu. - Merdeka sepenuhnya dari volume data.
Ini merupakan nilai tambah yang sangat serius. Tidak peduli berapa banyak data yang diproses, cepat atau lambat mereka akan diproses, dan agregat diterima. - Kompleksitas relatif.
Untuk mendapatkan analitik waktu nyata dan kemandirian volume data, solusinya harus menggunakan teknologi canggih seperti multithreading dan manajemen kunci manual di tingkat DBMS. - Tes kesulitan.
Di sini kita berbicara tentang pengujian unit dan pengujian manual. Saya pikir pembaca seharusnya tidak menjelaskan bahwa mengidentifikasi kesalahan multithreading bukanlah tugas yang mudah. - Persyaratan ruang disk yang ditingkatkan.
Penggunaan kolomstore yang sebenarnya
Di sini kita harus kembali menyelami teori dan menganalisis pertanyaan apa itu data analitik secara lebih rinci.
Ambil pimpinan rata-rata perusahaan. Sebagai aturan, ia khawatir tentang dua pertanyaan global: "Bagaimana keadaan saat ini?" dan "Apa yang telah berubah akhir-akhir ini?".
Untuk menjawab pertanyaan "Bagaimana keadaan saat ini" kita sama sekali tidak memerlukan data historis. Yaitu tidak peduli bagaimana keadaannya sebulan yang lalu.
Untuk tetap mengikuti denyut nadi, pertanyaan sering diajukan. Jenis analisis data ini disebut operasional.
Untuk menjawab pertanyaan "Apa yang telah berubah akhir-akhir ini", kita membutuhkan data historis yang tepat. Selain itu, sebagai suatu peraturan, analisis dilakukan pada interval waktu yang sama. Misalnya, sebulan dibandingkan dengan sebulan, tahun ke tahun, dll. Tentu saja, sistem tidak boleh membatasi pengguna dari kemampuan untuk membandingkan periode sewenang-wenang, tetapi kasus seperti itu harus diakui langka, karena membandingkan tahun yang ditutup dengan setengah yang tidak ditutup tidak masuk akal. Ciri khas dari analisis komparatif adalah tidak diperlukan sesering operasional. Kami akan menyebut jenis analisis ini historis.
Jelas, analisis operasional harus terjadi dengan cepat. Oleh karena itu, ini menempatkan tuntutan tinggi pada kinerja. Sedangkan untuk analisis historis, persyaratan seperti itu tidak bisa dikedepankan. Meskipun kinerja analisis historis harus tetap di level yang sangat tinggi. Setidaknya agar sistem analisisnya sendiri tetap kompetitif.
Jadi, sesuai dengan dua jenis analisis, kita dapat membedakan dua jenis data analitik: data operasional dan historis. Dari sisi pengguna seharusnya tidak terlihat dengan data khusus apa yang sedang dia kerjakan saat ini.
Dari pertimbangan ini bahwa dalam server database kemungkinan telah muncul pembagian tabel menjadi bagian yang terpisah.
Berkenaan dengan kolom toko, dimungkinkan untuk mencampur bagian dalam format berbasis baris dan toko kolom. Diketahui bahwa data analisis operasional dapat sering berubah, yang mencegah penyimpanannya dalam format columnstore. Dan mengingat fakta bahwa data operasional tidak terjadi terlalu banyak, mereka dapat disimpan dalam format berbasis baris.
Data historis tidak berubah. Ada banyak data ini, dan karenanya format kolomstore lebih cocok untuk mereka. Ingat bahwa kinerja kueri tebal pada sumber toko kolom lebih tinggi daripada pada sumber berbasis baris.
Mari kita lihat contoh dari semua hal di atas.
Di bawah ini saya membuat tabel gudang utama dan melampirkan bagian analisis operasional dan historis untuk itu.
create table warehouse ( trd date, org int, op int, it int, wh int, m1 numeric(32, 2), m2 numeric(32, 2), m3 numeric(32, 2), m4 numeric(32, 2), m5 numeric(32, 2) ) partition by range(trd); create foreign table historycal_data ( trd date, org int, op int, it int, wh int, m1 numeric(32, 2), m2 numeric(32, 2), m3 numeric(32, 2), m4 numeric(32, 2), m5 numeric(32, 2) ) server cstore_server options(compression 'pglz'); insert into historycal_data select '2010-01-01'::date + make_interval(days => d) as trd , op , org , wh , it , 100 as m1 , 100 as m2 , 100 as m3 , 100 as m4 , 100 as m5 from generate_series(0, 1) as op cross join generate_series(1, 2) as org cross join generate_series(1, 3) as wh cross join generate_series(1, 4000) as it cross join generate_series(0, (1095 - 31)) as d; analyze historycal_data; create table operational_data as select ('2012-12-01'::date + make_interval(days => d))::date as trd , op , org , wh , it , 100::numeric(32, 2) as m1 , 100::numeric(32, 2) as m2 , 100::numeric(32, 2) as m3 , 100::numeric(32, 2) as m4 , 100::numeric(32, 2) as m5 from generate_series(0, 1) as op cross join generate_series(1, 2) as org cross join generate_series(1, 3) as wh cross join generate_series(1, 4000) as it cross join generate_series(0, 30) as d; create index trd_op_ix on operational_data (trd, op); analyze operational_data; alter table warehouse attach partition operational_data for values from ('2012-12-01') to ('2112-01-01'); alter table warehouse attach partition historycal_data for values from ('2010-01-01') to ('2012-12-01');
Semuanya sudah siap. Mari kita coba memesan beberapa laporan. Mari kita mulai dengan memesan data untuk satu hari di bulan berjalan.
explain (analyze, costs, buffers) select sum(m1) from warehouse where trd = '2012-12-01' and op = 1;
Agregat (biaya = 15203,37..15203,38 baris = 1 lebar = 32) (waktu aktual = 17,320..17,320 baris = 1 loop = 1)
--Buffers: hit bersama = 3 baca = 515
---> Tambahkan (biaya = 532,59..15140.89 baris = 24991 lebar = 5) (waktu aktual = 1.924..13.838 baris = 24000 putaran = 1)
------- Buffer: klik bersama = 3 baca = 515
---------> Bitmap Heap Scan pada operational_data (biaya = 532,59..15140,89 baris = 24991 lebar = 5) (waktu aktual = 1,924..11,992 baris = 24000 putaran = 1)
--------------- Periksa kembali Cond: ((trd = '2012-12-01' :: date) AND (op = 1))
--------------- Heap Blocks: tepat = 449
--------------- Buffer: klik bersama = 3 baca = 515
----------------> Pemindaian Indeks Bitmap di trd_op_ix (biaya = 0,00..526,34 baris = 24991 lebar = 0) (waktu aktual = 1,877..1.877 baris = 24000 putaran = 1 )
--------------------- Indeks Cond: ((trd = '2012-12-01' :: date) AND (op = 1))
--------------------- Buffer: klik bersama = 2 baca = 67
Waktu perencanaan: 0,388 ms
Waktu pelaksanaan: 100.941 ms
Sekarang kami akan memesan data untuk seluruh tahun 2012, di mana jumlah transaksi adalah 8.784.000.
explain (analyze, costs, buffers) select sum(m1) from warehouse where trd between '2012-01-01' and '2012-12-31' and op = 1;
Agregat (biaya = 960685.82..960685.83 baris = 1 lebar = 32) (waktu aktual = 4124.681..4124.681 baris = 1 putaran = 1)
--Buffers: hit bersama = 45591 baca = 11282
---> Tambahkan (biaya = 0,00..938846,60 baris = lebar 8735687 = 5) (waktu aktual = 66,581..3036,394 baris = 8784000 putaran = 1)
--------- Buffer: klik bersama = 45591 baca = 11282
----------> Pemindaian Asing pada historycal_data (biaya = 0,00..898899,60 baris = 7994117 lebar = 5) (waktu aktual = 66,579..2193,801 baris = 8040000 loop = 1)
--------------- Filter: ((trd> = '2012-01-01' :: date) AND (trd <= '2012-12-31' :: date) AND (op = 1))
--------------- Baris Dihapus oleh Filter: 8040000
--------------- File CStore: / var / lib / pgsql / 10 / data / cstore_fdw / 14028/16448
--------------- Ukuran File CStore: 117401470
--------------- Buffer: klik bersama = 42966
----------> Pemindaian Seq pada operational_data (biaya = 0,00..39947,00 baris = 741570 lebar = 5) (waktu aktual = 0,019..284.824 baris = 744000 putaran = 1)
--------------- Filter: ((trd> = '2012-01-01' :: date) AND (trd <= '2012-12-31' :: date) AND (op = 1))
--------------- Baris Dihapus oleh Filter: 744000
--------------- Buffer: klik bersama = 2625 baca = 11282
Waktu perencanaan: 0,256 ms
Waktu pelaksanaan: 4125.239 ms
Pada akhirnya, mari kita lihat apa yang terjadi jika pengguna ingin, misalnya, tanpa niat jahat, untuk memesan laporan tentang semua transaksi dalam sistem, yang ada 52 608.000.
explain (analyze, costs, buffers) select sum(m1) from warehouse
Agregat (biaya = 672940.20..672940.21 baris = 1 lebar = 32) (waktu aktual = 15907.886..15907.886 baris = 1 loop = 1)
--Buffers: hit bersama = 17075 baca = 11154
---> Tambahkan (biaya = 0,00..541420.20 baris = lebar 52608000 = 5) (waktu aktual = 0,192..9115.144 baris = 52608000 putaran = 1)
--------- Buffer: klik bersama = 17075 baca = 11154
----------> Pemindaian Asing pada historycal_data (biaya = 0,00..512633,20 baris = 51120000 lebar = 5) (waktu aktual = 0,191..5376.449 baris = 51120000 loop = 1)
--------------- File CStore: / var / lib / pgsql / 10 / data / cstore_fdw / 14028/16448
--------------- Ukuran File CStore: 117401470
--------------- Buffer: klik bersama = 14322
----------> Pemindaian Seq pada operational_data (biaya = 0,00..28787,00 baris = 1488000 lebar = 5) (waktu aktual = 0,032..246.978 baris = 1488000 putaran = 1)
--------------- Buffer: klik bersama = 2753 baca = 11154
Waktu perencanaan: 0,157 ms
Waktu pelaksanaan: 15908.096 ms
Harap dicatat bahwa saya masih menulis artikel saya, seolah-olah tidak ada yang terjadi. Saya bahkan tidak harus me-reboot laptop saya yang tidak terlalu kuat dengan HDD dan RAM 4 GB. Meskipun masalah konsumsi sumber daya membutuhkan studi yang lebih cermat.
Toleransi kesalahan
Sebagian, toleransi kesalahan diuji tepat pada saat penulisan ini. Laptop saya hidup, dan, secara umum, saya tidak melihat ada perlambatan dalam pekerjaannya, selain yang biasa.
Biarkan pembaca memaafkan saya untuk fakta bahwa saya tidak punya waktu untuk menyelesaikan masalah toleransi kesalahan secara detail, tetapi saya dapat mengatakan bahwa ekstensi tersebut memiliki toleransi kesalahan - cadangan dimungkinkan.
Kemudahan implementasi
Ternyata, saat membuat tabel yang menyimpan data dalam format columnstore, tidak ada pilihan selain algoritma kompresi. Kompresi itu sendiri mutlak diperlukan.
Formatnya sendiri memiliki struktur tertentu. Dengan mengatur parameter yang sesuai, Anda dapat mencapai percepatan tertentu dari pertanyaan analitis, atau menyesuaikan tingkat kompresi informasi.
Seperti yang ditunjukkan di atas, membuat tabel columnstore tidak merepotkan sama sekali. Ekstensi dapat bekerja dengan 40 tipe data PostgreSQL. Webinar berbicara tentang semua jenis yang didukung oleh PostgreSQL.
Keterampilan baru apa yang harus dimiliki pengembang untuk bekerja dengan struktur baru
Pengembang SQL tidak memerlukan keterampilan khusus untuk menulis kueri ke tabel kolomstore. Tabel seperti itu terlihat di semua kueri, seperti tabel berbasis baris biasa. Meskipun ini tidak mengecualikan kebutuhan untuk optimasi permintaan.
Kesimpulan
Pada artikel ini, saya menunjukkan bagaimana sebuah tabel dengan format penyimpanan columnstore dapat bermanfaat. Ini menghemat ruang disk dan kueri analitik kinerja tinggi. Kemudahan bekerja dengan tabel secara otomatis mengurangi biaya pembuatan gudang data analitik lengkap, karena penggunaannya tidak memerlukan pengembangan algoritma yang rumit dan sulit untuk di-debug. Pengujian disederhanakan.
Terlepas dari kenyataan bahwa percobaan yang diajukan di atas menginspirasi optimisme, banyak masalah belum terselesaikan. Misalnya, rencana kueri apa yang akan dihasilkan ketika tabel columnstore bergabung dengan tabel lainnya. Saya berharap untuk melanjutkan pekerjaan ini di bagian selanjutnya. Berapa banyak bagian akan tergantung pada bagaimana perilaku cstore_fdw pada data nyata lebih atau kurang.
Tautan ke materi tambahan
Ulasan singkat cstore_fdwcstore_fdw di githubPeta jalan cstore_fdw