Mengkonfigurasi PostgreSQL untuk kinerja baca

39

Sistem kami menulis banyak data (semacam sistem Big Data). Kinerja menulis cukup baik untuk kebutuhan kita, tetapi kinerja membaca benar-benar terlalu lambat.

Struktur primary key (constraint) serupa untuk semua tabel kami:

timestamp(Timestamp) ; index(smallint) ; key(integer).

Sebuah tabel dapat memiliki jutaan baris, bahkan miliaran baris, dan permintaan baca biasanya untuk periode tertentu (cap waktu / indeks) dan tag. Adalah umum untuk memiliki kueri yang mengembalikan sekitar 200 ribu baris. Saat ini, kami dapat membaca sekitar 15k baris per detik tetapi kami harus 10 kali lebih cepat. Apakah ini mungkin dan jika iya, bagaimana?

Catatan: PostgreSQL dikemas dengan perangkat lunak kami, jadi perangkat kerasnya berbeda dari satu klien ke klien lainnya.

Ini adalah VM yang digunakan untuk pengujian. Tuan rumah VM adalah Windows Server 2008 R2 x64 dengan 24.0 GB RAM.

Server Spec (VMWare Mesin Virtual)

Server 2008 R2 x64
2.00 GB of memory
Intel Xeon W3520 @ 2.67GHz (2 cores)

postgresql.conf optimisasi

shared_buffers = 512MB (default: 32MB)
effective_cache_size = 1024MB (default: 128MB)
checkpoint_segment = 32 (default: 3)
checkpoint_completion_target = 0.9 (default: 0.5)
default_statistics_target = 1000 (default: 100)
work_mem = 100MB (default: 1MB)
maintainance_work_mem = 256MB (default: 16MB)

Definisi Tabel

CREATE TABLE "AnalogTransition"
(
  "KeyTag" integer NOT NULL,
  "Timestamp" timestamp with time zone NOT NULL,
  "TimestampQuality" smallint,
  "TimestampIndex" smallint NOT NULL,
  "Value" numeric,
  "Quality" boolean,
  "QualityFlags" smallint,
  "UpdateTimestamp" timestamp without time zone, -- (UTC)
  CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag" ),
  CONSTRAINT "FK_AnalogTransition_Tag" FOREIGN KEY ("KeyTag")
      REFERENCES "Tag" ("Key") MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
  OIDS=FALSE,
  autovacuum_enabled=true
);

Pertanyaan

Query membutuhkan sekitar 30 detik untuk dieksekusi di pgAdmin3, tetapi kami ingin memiliki hasil yang sama di bawah 5 detik jika memungkinkan.

SELECT 
    "AnalogTransition"."KeyTag", 
    "AnalogTransition"."Timestamp" AT TIME ZONE 'UTC', 
    "AnalogTransition"."TimestampQuality", 
    "AnalogTransition"."TimestampIndex", 
    "AnalogTransition"."Value", 
    "AnalogTransition"."Quality", 
    "AnalogTransition"."QualityFlags", 
    "AnalogTransition"."UpdateTimestamp"
FROM "AnalogTransition"
WHERE "AnalogTransition"."Timestamp" >= '2013-05-16 00:00:00.000' AND "AnalogTransition"."Timestamp" <= '2013-05-17 00:00:00.00' AND ("AnalogTransition"."KeyTag" = 56 OR "AnalogTransition"."KeyTag" = 57 OR "AnalogTransition"."KeyTag" = 58 OR "AnalogTransition"."KeyTag" = 59 OR "AnalogTransition"."KeyTag" = 60)
ORDER BY "AnalogTransition"."Timestamp" DESC, "AnalogTransition"."TimestampIndex" DESC
LIMIT 500000;

Jelaskan 1

"Limit  (cost=0.00..125668.31 rows=500000 width=33) (actual time=2.193..3241.319 rows=500000 loops=1)"
"  Buffers: shared hit=190147"
"  ->  Index Scan Backward using "PK_AnalogTransition" on "AnalogTransition"  (cost=0.00..389244.53 rows=1548698 width=33) (actual time=2.187..1893.283 rows=500000 loops=1)"
"        Index Cond: (("Timestamp" >= '2013-05-16 01:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-16 15:00:00-04'::timestamp with time zone))"
"        Filter: (("KeyTag" = 56) OR ("KeyTag" = 57) OR ("KeyTag" = 58) OR ("KeyTag" = 59) OR ("KeyTag" = 60))"
"        Buffers: shared hit=190147"
"Total runtime: 3863.028 ms"

Jelaskan 2

Dalam tes terakhir saya, butuh 7 menit untuk memilih data saya! Lihat di bawah:

"Limit  (cost=0.00..313554.08 rows=250001 width=35) (actual time=0.040..410721.033 rows=250001 loops=1)"
"  ->  Index Scan using "PK_AnalogTransition" on "AnalogTransition"  (cost=0.00..971400.46 rows=774511 width=35) (actual time=0.037..410088.960 rows=250001 loops=1)"
"        Index Cond: (("Timestamp" >= '2013-05-22 20:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-24 20:00:00-04'::timestamp with time zone) AND ("KeyTag" = 16))"
"Total runtime: 411044.175 ms"
JPelletier
sumber

Jawaban:

52

Penyelarasan data dan ukuran penyimpanan

Sebenarnya, overhead per tuple adalah 24 byte untuk header tuple plus 4 byte untuk pointer item.
Lebih detail dalam perhitungan dalam jawaban terkait ini:

Dasar-dasar penyelarasan dan padding data dalam jawaban terkait ini di SO:

Kami memiliki tiga kolom untuk kunci utama:

PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag")

"Timestamp"      timestamp (8 bytes)
"TimestampIndex" smallint  (2 bytes)
"KeyTag"         integer   (4 bytes)

Hasil dalam:

 Penunjuk item 4 byte di header halaman (tidak termasuk kelipatan 8 byte)
---
23 byte untuk tuple header
 Padding 1 byte untuk penyelarasan data (atau bitmap NULL)
 8 byte "Cap waktu"
 2 byte "TimestampIndex"
 2 byte padding untuk penyelarasan data
 4 byte "KeyTag" 
 0 padding ke kelipatan 8 byte terdekat
-----
44 byte per tuple

Lebih lanjut tentang mengukur ukuran objek dalam jawaban terkait ini:

Urutan kolom dalam indeks multikolom

Baca dua pertanyaan dan jawaban ini untuk memahami:

Cara Anda memiliki indeks (kunci utama), Anda dapat mengambil baris tanpa langkah penyortiran, itu menarik, terutama dengan LIMIT. Tetapi mengambil baris tampaknya sangat mahal.

Secara umum, dalam indeks multi-kolom, kolom "kesetaraan" harus lebih dulu dan kolom "rentang" bertahan:

Karena itu, coba indeks tambahan dengan urutan kolom terbalik :

CREATE INDEX analogransition_mult_idx1
   ON "AnalogTransition" ("KeyTag", "TimestampIndex", "Timestamp");

Itu tergantung pada distribusi data. Tetapi dengan millions of row, even billion of rowsini mungkin jauh lebih cepat.

Ukuran Tuple lebih besar 8 byte, karena perataan & bantalan data. Jika Anda menggunakan ini sebagai indeks biasa, Anda dapat mencoba untuk menjatuhkan kolom ketiga "Timestamp". Mungkin sedikit lebih cepat atau tidak (karena mungkin membantu menyortir).

Anda mungkin ingin menyimpan kedua indeks. Bergantung pada sejumlah faktor, indeks asli Anda mungkin lebih disukai - khususnya dengan yang kecil LIMIT.

statistik autovacuum dan tabel

Statistik tabel Anda harus terkini. Saya yakin Anda menjalankan autovacuum .

Karena tabel Anda tampaknya sangat besar dan statistik penting untuk rencana kueri yang tepat, saya akan secara substansial meningkatkan target statistik untuk kolom yang relevan:

ALTER TABLE "AnalogTransition" ALTER "Timestamp" SET STATISTICS 1000;

... atau bahkan lebih tinggi dengan miliaran baris. Maksimum adalah 10000, standarnya adalah 100.

Lakukan itu untuk semua kolom yang terlibat WHEREatau ORDER BYklausa. Kemudian jalankan ANALYZE.

Tata letak meja

Sementara berada di dalamnya, jika Anda menerapkan apa yang telah Anda pelajari tentang perataan dan bantalan data, tata letak tabel yang dioptimalkan ini akan menghemat ruang disk dan sedikit membantu kinerja (mengabaikan pk & fk):

CREATE TABLE "AnalogTransition"(
  "Timestamp" timestamp with time zone NOT NULL,
  "KeyTag" integer NOT NULL,
  "TimestampIndex" smallint NOT NULL,
  "TimestampQuality" smallint,
  "UpdateTimestamp" timestamp without time zone, -- (UTC)
  "QualityFlags" smallint,
  "Quality" boolean,
  "Value" numeric
);

CLUSTER / pg_repack

Untuk mengoptimalkan kinerja baca untuk kueri yang menggunakan indeks tertentu (baik itu yang asli atau alternatif yang saya sarankan), Anda dapat menulis ulang tabel dalam urutan fisik indeks. CLUSTERmelakukan itu, tetapi agak invasif dan memerlukan kunci eksklusif selama operasi. pg_repackadalah alternatif yang lebih canggih yang dapat melakukan hal yang sama tanpa kunci eksklusif di atas meja.
Ini dapat membantu secara substansial dengan tabel besar, karena jauh lebih sedikit blok tabel harus dibaca.

RAM

Secara umum, 2GB RAM fisik tidak cukup untuk menangani miliaran baris dengan cepat. RAM yang lebih banyak mungkin berjalan jauh - disertai dengan pengaturan yang disesuaikan: jelas lebih besar effective_cache_sizeuntuk memulai.

Erwin Brandstetter
sumber
2
Saya menambahkan indeks sederhana pada KeyTag saja dan tampaknya cukup cepat sekarang. Saya juga akan menerapkan rekomendasi Anda tentang penyelarasan data. Terima kasih banyak!
JPelletier
9

Jadi, dari rencana saya melihat satu hal: indeks Anda membengkak (kemudian bersama dengan tabel yang mendasari) atau tidak benar-benar baik untuk pertanyaan semacam ini (saya mencoba untuk mengatasinya dalam komentar terakhir saya di atas).

Satu baris indeks berisi 14 byte data (dan beberapa untuk header). Sekarang, menghitung dari angka-angka yang diberikan dalam rencana: Anda mendapat 500.000 baris dari 190147 halaman - itu berarti, rata-rata, kurang dari 3 baris berguna per halaman, yaitu, sekitar 37 byte per halaman 8 kb. Ini adalah rasio yang sangat buruk, bukan? Karena kolom pertama dari indeks adalah Timestampbidang dan digunakan dalam kueri sebagai rentang, perencana dapat - dan memang - memilih indeks untuk menemukan baris yang cocok. Tetapi tidak ada TimestampIndexdisebutkan dalam WHEREkondisi, jadi penyaringan pada KeyTagtidak terlalu efektif karena nilai-nilai itu seharusnya muncul secara acak di halaman indeks.

Jadi, satu kemungkinan adalah mengubah definisi indeks menjadi

CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp", "KeyTag", "TimestampIndex")

(atau, mengingat beban sistem Anda, buat indeks ini sebagai yang baru:

CREATE INDEX CONCURRENTLY "idx_AnalogTransition" 
    ON "AnalogTransition" ("Timestamp", "KeyTag", "TimestampIndex");
  • ini akan memakan waktu cukup lama tetapi Anda masih bisa bekerja sementara itu.)

Kemungkinan lain bahwa sebagian besar halaman indeks ditempati oleh baris mati, yang dapat dihilangkan dengan menyedot debu. Anda membuat tabel dengan pengaturan autovacuum_enabled=true- tetapi apakah Anda pernah memulai autovacuuming? Atau jalankan VACUUMsecara manual?

dezso
sumber