การกำหนดค่า PostgreSQL สำหรับประสิทธิภาพการอ่าน


39

ระบบของเราเขียนข้อมูลจำนวนมาก (ระบบ Big Data) ประสิทธิภาพการเขียนดีพอสำหรับความต้องการของเรา แต่ประสิทธิภาพการอ่านช้าเกินไป

โครงสร้างคีย์หลัก (ข้อ จำกัด ) มีความคล้ายคลึงกับตารางทั้งหมดของเรา:

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

ตารางสามารถมีได้หลายล้านแถวแม้กระทั่งหลายพันล้านแถวและการร้องขอการอ่านมักจะเป็นช่วงเวลาที่ระบุ (การประทับเวลา / ดัชนี) และแท็ก เป็นเรื่องปกติที่มีข้อความค้นหาที่ส่งกลับประมาณ 200k บรรทัด ขณะนี้เราสามารถอ่านประมาณ 15k บรรทัดต่อวินาที แต่เราต้องเร็วขึ้น 10 เท่า เป็นไปได้และถ้าเป็นเช่นนั้นได้อย่างไร

หมายเหตุ: PostgreSQL นั้นมาพร้อมกับซอฟต์แวร์ของเราดังนั้นฮาร์ดแวร์จึงแตกต่างจากไคลเอนต์หนึ่งไปอีกไคลเอนต์หนึ่ง

มันเป็น VM ที่ใช้สำหรับการทดสอบ โฮสต์ของ VM คือ Windows Server 2008 R2 x64 พร้อม RAM 24.0 GB

ข้อมูลจำเพาะเซิร์ฟเวอร์ (เครื่องเสมือน VMWare)

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

postgresql.conf optimisations

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)

นิยามของตาราง

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
);

สอบถาม

แบบสอบถามใช้เวลาประมาณ 30 วินาทีในการดำเนินการใน pgAdmin3 แต่เราต้องการให้ผลลัพธ์เดียวกันภายใต้ 5 วินาทีถ้าเป็นไปได้

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;

อธิบาย 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"

อธิบาย 2

ในการทดสอบล่าสุดของฉันใช้เวลา 7 นาทีในการเลือกข้อมูลของฉัน! ดูด้านล่าง:

"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"

คำตอบ:


52

การจัดตำแหน่งข้อมูลและขนาดการจัดเก็บ

ที่จริงแล้วค่าใช้จ่ายต่อ tuple คือ 24 ไบต์สำหรับส่วนหัว tuple บวก 4 ไบต์สำหรับตัวชี้รายการ
รายละเอียดเพิ่มเติมในการคำนวณในคำตอบที่เกี่ยวข้องนี้:

พื้นฐานของการจัดตำแหน่งข้อมูลและการเติมในคำตอบที่เกี่ยวข้องกับ SO:

เรามีสามคอลัมน์สำหรับคีย์หลัก:

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

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

ผลลัพธ์ใน:

 ตัวชี้รายการ 4 ไบต์ในส่วนหัวของหน้า (ไม่นับรวมหลาย ๆ 8 ไบต์)
---
23 ไบต์สำหรับส่วนหัวของ tuple
 padding 1 ไบต์สำหรับการจัดแนวข้อมูล (หรือบิตแมป NULL)
 8 ไบต์ "การประทับเวลา"
 2 ไบต์ "TimestampIndex"
 2 ไบต์ padding สำหรับการจัดตำแหน่งข้อมูล
 4 ไบต์ "KeyTag" 
 0 padding เป็นพหุคูณที่ใกล้ที่สุด 8 ไบต์
-----
44 ไบต์ต่อ tuple

เพิ่มเติมเกี่ยวกับการวัดขนาดวัตถุในคำตอบที่เกี่ยวข้องนี้:

ลำดับของคอลัมน์ในดัชนีหลายคอลัมน์

อ่านคำถามและคำตอบสองข้อต่อไปนี้เพื่อทำความเข้าใจ:

วิธีที่คุณมีดัชนีของคุณ (คีย์หลัก) LIMITคุณสามารถเรียกแถวโดยไม่ต้องมีขั้นตอนการจัดเรียงที่น่าสนใจโดยเฉพาะอย่างยิ่งกับ แต่การดึงแถวดูจะมีราคาแพงมาก

โดยทั่วไปในดัชนีหลายคอลัมน์คอลัมน์ "ความเท่าเทียมกัน" ควรไปก่อนและคอลัมน์ "ช่วง" สุดท้าย:

ดังนั้นให้ลองดัชนีเพิ่มเติมโดยเรียงตามลำดับคอลัมน์ที่กลับด้าน :

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

มันขึ้นอยู่กับการกระจายข้อมูล แต่ด้วยmillions of row, even billion of rowsสิ่งนี้อาจจะเร็วกว่ามาก

ขนาดของ Tuple นั้นใหญ่กว่า 8 ไบต์เนื่องจากการจัดตำแหน่งข้อมูล & ช่องว่างภายใน "Timestamp"หากคุณกำลังใช้นี้เป็นดัชนีธรรมดาคุณอาจจะพยายามที่จะลดลงคอลัมน์ที่สาม อาจเร็วกว่านี้เล็กน้อยหรือไม่ (เนื่องจากอาจช่วยในการจัดเรียง)

คุณอาจต้องการเก็บดัชนีทั้งสองไว้ ทั้งนี้ขึ้นอยู่กับจำนวนของปัจจัยดัชนีเดิมของคุณอาจจะดีกว่า - LIMITโดยเฉพาะอย่างยิ่งที่มีขนาดเล็ก

สถิติ autovacuum และตาราง

สถิติตารางของคุณต้องทันสมัย ผมแน่ใจว่าคุณมีautovacuumทำงาน

เนื่องจากตารางของคุณมีขนาดใหญ่และสถิติสำคัญสำหรับแผนการค้นหาที่เหมาะสมฉันจะเพิ่มเป้าหมายสถิติสำหรับคอลัมน์ที่เกี่ยวข้องอย่างมาก:

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

... หรือสูงขึ้นไปด้วยแถวหลายพันล้านแถว สูงสุดคือ 10000 ค่าเริ่มต้นคือ 100

ทำเช่นนั้นสำหรับคอลัมน์ทั้งหมดที่เกี่ยวข้องWHEREหรือORDER BYข้อ ANALYZEจากนั้นเรียก

เค้าโครงตาราง

ในขณะที่อยู่ในนั้นหากคุณใช้สิ่งที่คุณได้เรียนรู้เกี่ยวกับการจัดตำแหน่งข้อมูลและการขยายช่องว่างเค้าโครงตารางที่ดีที่สุดนี้ควรประหยัดพื้นที่ดิสก์บางส่วนและช่วยให้มีประสิทธิภาพเล็กน้อย (ละเว้น 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

ในการเพิ่มประสิทธิภาพการอ่านสำหรับเคียวรีที่ใช้ดัชนีที่แน่นอน (ไม่ว่าจะเป็นแบบดั้งเดิมหรือแบบอื่นที่ฉันแนะนำ) คุณสามารถเขียนตารางตามลำดับทางกายภาพของดัชนีได้ CLUSTERทำเช่นนั้น แต่มันค่อนข้างจะรุกรานและต้องการการล็อคแบบเอกสิทธิ์เฉพาะบุคคลสำหรับช่วงเวลาของการดำเนินการ pg_repackเป็นทางเลือกที่ซับซ้อนยิ่งขึ้นที่สามารถทำได้โดยไม่ต้องล็อกเฉพาะบนโต๊ะ
สิ่งนี้สามารถช่วยได้อย่างมากกับตารางขนาดใหญ่เนื่องจากต้องอ่านบล็อกจำนวนน้อย

แกะ

โดยทั่วไปแล้วแรมทางกายภาพ 2GB นั้นไม่เพียงพอที่จะจัดการกับพันล้านแถวอย่างรวดเร็ว แรมเพิ่มเติมอาจไปได้ไกล - พร้อมด้วยการตั้งค่าที่ปรับแล้ว: ยิ่งใหญ่กว่าeffective_cache_sizeสำหรับการเริ่มต้น


2
ฉันเพิ่มดัชนีอย่างง่ายใน KeyTag เท่านั้นและดูเหมือนว่าจะค่อนข้างเร็วในขณะนี้ ฉันจะใช้คำแนะนำของคุณเกี่ยวกับการจัดตำแหน่งข้อมูล ขอบคุณมาก!
JPelletier

9

ดังนั้นจากแผนการที่ฉันเห็นสิ่งหนึ่ง: คุณดัชนีเป็นป่อง (แล้วพร้อมกับตารางต้นแบบ) หรือเพียงแค่ไม่ดีจริง ๆ สำหรับแบบสอบถามชนิดนี้ (ฉันพยายามที่จะอยู่ในความคิดเห็นล่าสุดของฉันด้านบน)

แถวหนึ่งของดัชนีมีข้อมูล 14 ไบต์ (และบางแถวสำหรับส่วนหัว) ตอนนี้การคำนวณจากตัวเลขที่ให้ไว้ในแผน: คุณมี 500,000 แถวจาก 190147 หน้า - นั่นหมายความว่าโดยเฉลี่ยแล้วมีน้อยกว่า 3 แถวที่มีประโยชน์ต่อหน้านั่นคือประมาณ 37 ไบต์ต่อหน้า 8 kb อัตราส่วนนี้แย่มากใช่ไหม เนื่องจากคอลัมน์แรกของดัชนีเป็นTimestampฟิลด์และใช้ในแบบสอบถามเป็นช่วงผู้วางแผนสามารถ - และ - เลือก - ดัชนีเพื่อค้นหาแถวที่ตรงกัน แต่ไม่มีการTimestampIndexกล่าวถึงในWHEREเงื่อนไขดังนั้นการกรองKeyTagจึงไม่มีประสิทธิภาพมากนักเนื่องจากค่าเหล่านั้นควรจะปรากฏแบบสุ่มในหน้าดัชนี

ดังนั้นสิ่งหนึ่งที่เป็นไปได้คือการเปลี่ยนนิยามดัชนีเป็น

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

(หรือให้ระบบของคุณโหลดสร้างดัชนีนี้เป็นดัชนีใหม่:

CREATE INDEX CONCURRENTLY "idx_AnalogTransition" 
    ON "AnalogTransition" ("Timestamp", "KeyTag", "TimestampIndex");
  • อาจใช้เวลาสักครู่ แต่คุณยังสามารถทำงานได้ในระหว่างนี้)

ความเป็นไปได้อื่นที่สัดส่วนหน้าดัชนีขนาดใหญ่ถูกครอบครองโดยแถวที่ตายแล้วซึ่งสามารถลบออกได้โดยการดูดฝุ่น คุณสร้างตารางโดยใช้การตั้งค่าautovacuum_enabled=true- แต่คุณเคยเริ่มต้นการบันทึกอัตโนมัติหรือไม่? หรือเรียกใช้VACUUMด้วยตนเอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.