Saya mencoba membuat indeks parsial untuk tabel statis besar (1.2TB) di Postgres 9.4.
Data saya benar-benar statis, jadi saya bisa memasukkan semua data, lalu membuat semua indeks.
Dalam tabel 1.2TB ini, saya memiliki kolom bernama run_id
yang membagi data dengan bersih. Kami mendapatkan kinerja luar biasa dengan membuat indeks yang mencakup kisaran run_id
s. Ini sebuah contoh:
CREATE INDEX perception_run_frame_idx_run_266_thru_270
ON run.perception
(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;
Indeks parsial ini memberi kami kecepatan permintaan yang diinginkan. Sayangnya, pembuatan setiap indeks parsial membutuhkan waktu sekitar 70 menit.
Sepertinya kami adalah CPU terbatas ( top
menunjukkan 100% untuk proses).
Adakah yang bisa saya lakukan untuk mempercepat pembuatan indeks parsial kami?
Spesifikasi sistem:
- 18 inti Xeon
- RAM 192 GB
- 12 SSD dalam RAID
- Autovacuum dimatikan
- maintenance_work_mem: 64GB (Terlalu tinggi?)
Spesifikasi tabel:
- Ukuran: 1,26 TB
- Jumlah baris: 10,537 Miliar
- Ukuran indeks umum: 3.2GB (ada varian ~ .5GB)
Definisi tabel:
CREATE TABLE run.perception(
id bigint NOT NULL,
run_id bigint NOT NULL,
frame bigint NOT NULL,
by character varying(45) NOT NULL,
by_anyone bigint NOT NULL,
by_me bigint NOT NULL,
by_s_id integer,
owning_p_id bigint NOT NULL,
obj_type_set bigint,
seq integer,
subj_id bigint NOT NULL,
subj_state_frame bigint NOT NULL,
CONSTRAINT perception_pkey PRIMARY KEY (id))
(Jangan terlalu banyak membaca nama kolom - Saya agak mengaburkannya.)
Info latar belakang:
- Kami memiliki tim tamu di tempat yang menggunakan data ini, tetapi sebenarnya hanya ada satu atau dua pengguna. (Data ini semua dihasilkan melalui simulasi.) Pengguna hanya mulai menganalisis data setelah sisipan selesai dan indeks dibuat sepenuhnya. Perhatian utama kami adalah mengurangi waktu yang diperlukan untuk menghasilkan data yang dapat digunakan, dan saat ini bottleneck adalah waktu pembuatan indeks.
- Kecepatan permintaan sudah sepenuhnya memadai saat menggunakan sebagian. Bahkan, saya pikir kita bisa meningkatkan jumlah proses yang dicakup oleh masing-masing indeks, dan masih mempertahankan kinerja kueri yang cukup baik.
- Dugaan saya adalah bahwa kita harus mempartisi tabel. Kami mencoba menguras semua opsi lain sebelum mengambil rute itu.
run_id
? Didistribusikan secara merata? Ukuran indeks yang dihasilkan pada disk? Data statis, ok. Tetapi apakah Anda satu-satunya pengguna?completely static
, lalu apa yang Anda maksudWe have a separate team onsite that consumes this data
? Apakah Anda hanya mengindeks rentangrun_id >= 266 AND run_id <= 270
atau seluruh tabel? Berapa harapan hidup setiap indeks / berapa banyak permintaan akan menggunakannya? Berapa banyak nilai yang berbeda untukrun_id
? Kedengarannya seperti ~ 15 Mio. baris perrun_id
, yang akan membuatnya sekitar 800 nilai berbeda untukrun_id
? Mengapaobj_type_set
,by_s_id
,seq
tidak didefinisikan NOT NULL? Berapa persentase kasar nilai NULL untuk masing-masing?Jawaban:
Indeks BRIN
Tersedia sejak Postgres 9.5 dan mungkin hanya apa yang Anda cari. Pembuatan indeks jauh lebih cepat, indeks jauh lebih kecil. Tetapi pertanyaan biasanya tidak secepat. Manual:
Baca terus, masih ada lagi.
Depesz melakukan tes pendahuluan.
Optimum untuk kasus Anda: Jika Anda dapat menulis baris berkerumun di
run_id
, indeks Anda menjadi sangat kecil dan penciptaan jauh lebih murah.Anda bahkan mungkin hanya mengindeks seluruh tabel .
Tata letak meja
Apa pun yang Anda lakukan, Anda dapat menyimpan 8 byte yang hilang karena pengisian karena persyaratan pelurusan per baris dengan menyusun kolom seperti ini:
Jadikan meja Anda 79 GB lebih kecil jika tidak ada kolom yang memiliki nilai NULL. Detail:
Selain itu, Anda hanya memiliki tiga kolom yang bisa NULL. Bitmap NULL menempati 8 byte untuk 9 - 72 kolom. Jika hanya satu kolom integer adalah NULL, ada kasus sudut untuk paradoks penyimpanan: akan lebih murah untuk menggunakan nilai dummy sebagai gantinya: 4 byte terbuang tetapi 8 byte disimpan dengan tidak memerlukan bitmap NULL untuk baris. Lebih detail di sini:
Indeks sebagian
Bergantung pada permintaan Anda yang sebenarnya, mungkin lebih efisien untuk memiliki lima indeks parsial ini daripada yang di atas:
Jalankan satu transaksi untuk masing-masing.
Menghapus
run_id
sebagai kolom indeks dengan cara ini menghemat 8 byte per entri indeks - 32 bukannya 40 byte per baris. Setiap indeks juga lebih murah untuk dibuat, tetapi membuat lima alih-alih hanya satu membutuhkan waktu yang jauh lebih lama untuk tabel yang terlalu besar untuk disimpan dalam cache (seperti @ Jürgen dan @Chris berkomentar). Sehingga mungkin atau mungkin tidak bermanfaat bagi Anda.Partisi
Berdasarkan warisan - satu-satunya pilihan hingga Postgres 9.5.
(Partisi deklaratif baru di Postgres 11 atau, lebih disukai, 12 lebih pintar.)
Manual:
Penekanan berani saya. Akibatnya, memperkirakan 1000 nilai yang berbeda untuk
run_id
, Anda akan membuat partisi yang masing-masing sekitar 10 nilai.maintenance_work_mem
Saya melewatkan bahwa Anda sudah menyesuaikan untuk
maintenance_work_mem
di baca pertama saya. Saya akan meninggalkan kutipan dan saran dalam jawaban saya untuk referensi. Per dokumentasi:Saya hanya akan mengaturnya setinggi yang diperlukan - yang tergantung pada ukuran indeks yang tidak diketahui (untuk kami). Dan hanya secara lokal untuk sesi eksekusi. Seperti yang dijelaskan dalam kutipan, pengaturan umum yang terlalu tinggi dapat membuat server kelaparan, karena autovacuum dapat mengklaim lebih banyak RAM. Juga, jangan mengaturnya jauh lebih tinggi dari yang dibutuhkan, bahkan dalam sesi eksekusi, RAM bebas mungkin digunakan dengan baik dalam data caching.
Itu bisa terlihat seperti ini:
Tentang
SET LOCAL
:Untuk mengukur ukuran objek:
Server umumnya harus dikonfigurasi secara wajar jika tidak, jelas.
sumber
Mungkin ini hanya rekayasa berlebihan. Sudahkah Anda benar-benar mencoba menggunakan satu indeks penuh? Indeks parsial yang mencakup seluruh tabel bersama-sama tidak memberikan banyak keuntungan, jika ada, untuk pencarian indeks, dan dari teks Anda, saya menyimpulkan bahwa Anda memiliki indeks untuk semua run_ids? Mungkin ada beberapa keuntungan untuk memindai indeks dengan indeks parsial, masih saya akan membandingkan solusi satu indeks sederhana terlebih dahulu.
Untuk setiap pembuatan indeks, Anda memerlukan pemindaian IO terikat penuh melalui tabel. Jadi membuat beberapa indeks parsial membutuhkan jauh lebih banyak IO membaca tabel daripada untuk indeks tunggal, meskipun pengurutan akan tumpah ke disk untuk indeks tunggal besar. Jika Anda bersikeras pada indeks parsial, Anda dapat mencoba membangun semua (atau beberapa) indeks pada saat yang bersamaan secara paralel (memungkinkan memori).
Untuk perkiraan kasar tentang maintenance_work_mem diperlukan untuk mengurutkan semua run_ids, yang merupakan bigint 8-byte, dalam memori Anda akan memerlukan 10,5 * 8 GB + beberapa overhead.
sumber
Anda juga bisa membuat indeks pada tablespace lain selain default. Tablespace ini bisa mengarah ke disk yang tidak berlebihan (hanya membuat ulang indeks jika gagal), atau berada di array yang lebih cepat.
Anda juga dapat mempertimbangkan mempartisi tabel menggunakan kriteria yang sama dengan indeks parsial Anda. Ini akan memungkinkan untuk kecepatan yang sama dengan indeks saat query, tanpa benar-benar membuat indeks apa pun.
sumber