เราเขียนใน PostgreSQL บนแสงย่อย: 1 โฮสต์ 1 วัน 1TB

ล่าสุดผมบอกวิธีใช้สูตรมาตรฐานแล้ว เพิ่มประสิทธิภาพของการสืบค้นการอ่าน SQL จากฐานข้อมูล PostgreSQL วันนี้เราจะมาพูดถึงวิธีการ การบันทึกสามารถทำได้มีประสิทธิภาพมากขึ้น ในฐานข้อมูลโดยไม่ต้องใช้ "การบิด" ใด ๆ ในการกำหนดค่า - เพียงแค่จัดระเบียบกระแสข้อมูลอย่างถูกต้อง

เราเขียนใน PostgreSQL บนแสงย่อย: 1 โฮสต์ 1 วัน 1TB

#1. การแบ่งส่วน

บทความเกี่ยวกับวิธีการและเหตุผลที่ควรจัดระเบียบ ใช้การแบ่งพาร์ติชัน “ในทางทฤษฎี” ไปแล้ว ที่นี่เราจะพูดถึงแนวปฏิบัติในการประยุกต์แนวทางบางอย่างภายในตัวเรา บริการตรวจสอบเซิร์ฟเวอร์ PostgreSQL หลายร้อยเซิร์ฟเวอร์.

“เรื่องต่างๆ ของวันเวลาที่ผ่านไป...”

ในขั้นต้น เช่นเดียวกับ MVP อื่น ๆ โครงการของเราเริ่มต้นภายใต้ภาระงานที่ค่อนข้างเบา - การตรวจสอบดำเนินการเฉพาะสำหรับเซิร์ฟเวอร์ที่สำคัญที่สุดสิบเครื่องเท่านั้น ตารางทั้งหมดมีขนาดค่อนข้างเล็ก... แต่เมื่อเวลาผ่านไป จำนวนโฮสต์ที่ได้รับการตรวจสอบก็เพิ่มมากขึ้นเรื่อย ๆ และอีกครั้งที่เราพยายามทำอะไรบางอย่างกับหนึ่งในนั้น ตารางขนาด 1.5TBเราตระหนักว่าถึงแม้จะเป็นไปได้ที่จะใช้ชีวิตแบบนี้ต่อไป แต่มันก็ไม่สะดวกมาก

ช่วงเวลานั้นเกือบจะเหมือนกับช่วงเวลาที่ยิ่งใหญ่ PostgreSQL 9.x เวอร์ชันต่างๆ มีความเกี่ยวข้องกัน ดังนั้นการแบ่งพาร์ติชั่นทั้งหมดจึงต้องดำเนินการ "ด้วยตนเอง" ผ่าน การสืบทอดตารางและทริกเกอร์ การกำหนดเส้นทางด้วยไดนามิก EXECUTE.

เราเขียนใน PostgreSQL บนแสงย่อย: 1 โฮสต์ 1 วัน 1TB
ผลลัพธ์ที่ได้กลายเป็นสากลเพียงพอที่จะแปลเป็นตารางทั้งหมดได้:

  • มีการประกาศตารางพาเรนต์ "ส่วนหัว" ที่ว่างเปล่า ซึ่งอธิบายทั้งหมด ดัชนีและทริกเกอร์ที่จำเป็น.
  • บันทึกจากมุมมองของลูกค้าถูกสร้างขึ้นในตาราง "รูท" และใช้งานภายใน ทริกเกอร์การกำหนดเส้นทาง BEFORE INSERT บันทึกถูกแทรก "ทางกายภาพ" ลงในส่วนที่ต้องการ หากไม่มีสิ่งนั้น เราก็ได้รับข้อยกเว้นและ...
  • … โดยใช้ CREATE TABLE ... (LIKE ... INCLUDING ...) ถูกสร้างขึ้นตามเทมเพลตของตารางหลัก ส่วนที่มีข้อจำกัดเรื่องวันที่ต้องการเพื่อว่าเมื่อดึงข้อมูลแล้ว การอ่านจะดำเนินการเฉพาะในนั้นเท่านั้น

PG10: ความพยายามครั้งแรก

แต่ในอดีตการแบ่งพาร์ติชันผ่านการสืบทอดนั้นไม่เหมาะอย่างยิ่งในการจัดการกับสตรีมการเขียนที่ใช้งานอยู่หรือพาร์ติชันย่อยจำนวนมาก ตัวอย่างเช่นคุณสามารถจำได้ว่ามีอัลกอริธึมในการเลือกส่วนที่ต้องการ ความซับซ้อนกำลังสองว่ามันใช้งานได้กับ 100+ ส่วน คุณเองก็เข้าใจวิธีการ...

ใน PG10 สถานการณ์นี้ได้รับการปรับให้เหมาะสมอย่างมากโดยการใช้การสนับสนุน การแบ่งพาร์ติชันดั้งเดิม. ดังนั้นเราจึงพยายามนำไปใช้ทันทีหลังจากย้ายที่เก็บข้อมูล แต่...

ตามที่ปรากฎหลังจากขุดดูคู่มือ ตารางที่แบ่งพาร์ติชันในเวอร์ชันนี้คือ:

  • ไม่รองรับคำอธิบายดัชนี
  • ไม่รองรับทริกเกอร์
  • ไม่สามารถเป็น “ผู้สืบเชื้อสาย” ของใครได้
  • ไม่สนับสนุน INSERT ... ON CONFLICT
  • ไม่สามารถสร้างส่วนได้โดยอัตโนมัติ

เมื่อได้รับการชกที่หน้าผากอย่างเจ็บปวดด้วยคราดเราก็ตระหนักว่าคงเป็นไปไม่ได้หากไม่มีการแก้ไขแอปพลิเคชันและเลื่อนการวิจัยเพิ่มเติมออกไปเป็นเวลาหกเดือน

PG10: โอกาสครั้งที่สอง

ดังนั้นเราจึงเริ่มแก้ไขปัญหาที่เกิดขึ้นทีละประเด็น:

  1. เพราะสิ่งกระตุ้นและ ON CONFLICT เราพบว่าเรายังต้องการพวกมันอยู่ตรงนี้และตรงนั้น ดังนั้นเราจึงสร้างขั้นตอนกลางเพื่อจัดการพวกมัน ตารางพร็อกซี.
  2. กำจัด "เส้นทาง" ในทริกเกอร์ - นั่นคือจาก EXECUTE.
  3. พวกเขาแยกมันออกมา ตารางเทมเพลตพร้อมดัชนีทั้งหมดเพื่อไม่ให้ปรากฏอยู่ในตารางพร็อกซีด้วยซ้ำ

เราเขียนใน PostgreSQL บนแสงย่อย: 1 โฮสต์ 1 วัน 1TB
ในที่สุด หลังจากทั้งหมดนี้ เราก็แบ่งพาร์ติชันตารางหลักแบบเนทีฟ การสร้างส่วนใหม่ยังคงเหลืออยู่ในจิตสำนึกของแอปพลิเคชัน

พจนานุกรม "เลื่อย"

เช่นเดียวกับในระบบการวิเคราะห์อื่นๆ เราก็มีเช่นกัน "ข้อเท็จจริง" และ "จุดตัด" (พจนานุกรม) ในกรณีของเรา พวกเขาดำเนินการในฐานะนี้ เช่น เนื้อหาเทมเพลต ข้อความค้นหาที่ช้าที่คล้ายกันหรือข้อความของข้อความค้นหานั้นเอง

“ข้อเท็จจริง” ถูกแบ่งส่วนตามวันเป็นเวลานานแล้ว ดังนั้นเราจึงลบส่วนที่ล้าสมัยอย่างใจเย็น และพวกเขาไม่ได้รบกวนเรา (บันทึก!) แต่มีปัญหากับพจนานุกรม...

ไม่ได้บอกว่ามีเยอะแต่ก็ประมาณนี้ “ข้อเท็จจริง” จำนวน 100TB ส่งผลให้มีพจนานุกรมขนาด 2.5TB. คุณไม่สามารถลบสิ่งใดออกจากตารางได้อย่างสะดวก ไม่สามารถบีบอัดได้ในเวลาที่เหมาะสม และการเขียนลงในตารางจะค่อยๆ ช้าลง

เหมือนพจนานุกรม...ในนั้นควรนำเสนอแต่ละรายการเพียงครั้งเดียว...ซึ่งก็ถูกต้อง แต่!.. ไม่มีใครหยุดเราจากการมี พจนานุกรมแยกต่างหากในแต่ละวัน! ใช่ สิ่งนี้ทำให้เกิดความซ้ำซ้อน แต่จะช่วยให้:

  • เขียน/อ่านเร็วขึ้น เนื่องจากขนาดส่วนเล็กลง
  • ใช้หน่วยความจำน้อยลง โดยทำงานร่วมกับดัชนีที่มีขนาดกะทัดรัดมากขึ้น
  • เก็บข้อมูลน้อยลง เนื่องจากสามารถลบล้าสมัยได้อย่างรวดเร็ว

อันเป็นผลมาจากมาตรการที่ซับซ้อนทั้งหมด โหลด CPU ลดลง ~30% โหลดดิสก์ประมาณ ~50%:

เราเขียนใน PostgreSQL บนแสงย่อย: 1 โฮสต์ 1 วัน 1TB
ในเวลาเดียวกัน เรายังคงเขียนสิ่งเดียวกันนี้ลงในฐานข้อมูลต่อไป โดยมีภาระงานน้อยลง

#2. วิวัฒนาการฐานข้อมูลและการปรับโครงสร้างใหม่

เราจึงตกลงกับสิ่งที่เรามี ในแต่ละวันมีส่วนของตัวเอง พร้อมข้อมูล จริงๆ แล้ว, CHECK (dt = '2018-10-12'::date) — และมีคีย์การแบ่งพาร์ติชั่นและเงื่อนไขสำหรับเรคคอร์ดให้ตกไปอยู่ในส่วนเฉพาะ

เนื่องจากรายงานทั้งหมดในบริการของเราสร้างขึ้นในบริบทของวันที่ที่ระบุ ดัชนีสำหรับรายงานเหล่านั้นตั้งแต่ “เวลาที่ไม่แบ่งพาร์ติชัน” จึงเป็นทุกประเภท (เซิร์ฟเวอร์ วันที่, เทมเพลตแผน), (เซิร์ฟเวอร์ วันที่, โหนดแผน), (วันที่, คลาสข้อผิดพลาด, เซิร์ฟเวอร์)...

แต่ตอนนี้พวกเขาอาศัยอยู่ทุกส่วน สำเนาของคุณ แต่ละดัชนีดังกล่าว... และภายในแต่ละส่วน วันที่เป็นค่าคงที่... ปรากฎว่าตอนนี้เราอยู่ในดัชนีดังกล่าวแล้ว เพียงป้อนค่าคงที่ เป็นหนึ่งในฟิลด์ที่เพิ่มทั้งปริมาณและเวลาในการค้นหา แต่ไม่มีผลลัพธ์ใด ๆ พวกเขาทิ้งคราดไว้กับตัวเอง อุ๊ย...

เราเขียนใน PostgreSQL บนแสงย่อย: 1 โฮสต์ 1 วัน 1TB
ทิศทางของการเพิ่มประสิทธิภาพนั้นชัดเจน - เรียบง่าย ลบฟิลด์วันที่ออกจากดัชนีทั้งหมด บนโต๊ะที่แบ่งพาร์ติชัน เมื่อพิจารณาจากปริมาณของเรา กำไรก็ประมาณนั้น 1TB/สัปดาห์!

ตอนนี้เราทราบว่ายังคงต้องบันทึกเทราไบต์นี้อยู่ นั่นก็คือเรายัง ตอนนี้ดิสก์ควรโหลดน้อยลง! ภาพนี้แสดงให้เห็นอย่างชัดเจนถึงผลกระทบที่ได้รับจากการทำความสะอาดซึ่งเราทุ่มเทให้กับหนึ่งสัปดาห์:

เราเขียนใน PostgreSQL บนแสงย่อย: 1 โฮสต์ 1 วัน 1TB

#3. “การแพร่กระจาย” ภาระสูงสุด

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

ลองซูมเข้าที่ภาพก่อนหน้าแล้วดูว่าเรามีดิสก์ “ปั๊ม” ใต้โหลดที่มีแอมพลิจูดสองเท่า ระหว่างตัวอย่างที่อยู่ติดกัน ซึ่งชัดเจนว่า "ทางสถิติ" ไม่ควรเกิดขึ้นกับการดำเนินการจำนวนดังกล่าว:

เราเขียนใน PostgreSQL บนแสงย่อย: 1 โฮสต์ 1 วัน 1TB

มันค่อนข้างง่ายที่จะบรรลุผล เราได้เริ่มติดตามแล้ว เกือบ 1000 เซิร์ฟเวอร์แต่ละเธรดจะถูกประมวลผลโดยเธรดลอจิคัลที่แยกจากกัน และแต่ละเธรดจะรีเซ็ตข้อมูลที่สะสมเพื่อส่งไปยังฐานข้อมูลที่ความถี่ที่แน่นอน บางอย่างเช่นนี้:

setInterval(sendToDB, interval)

ปัญหาตรงนี้อยู่ที่ข้อเท็จจริงที่ว่า เธรดทั้งหมดเริ่มต้นในเวลาเดียวกันโดยประมาณดังนั้นเวลาในการส่งจึงมักจะตรงกัน "ตรงประเด็น" เสมอ อุ๊ย #2...

โชคดีที่มันค่อนข้างง่ายที่จะแก้ไข เพิ่มการวิ่งขึ้นแบบ "สุ่ม" ตามเวลา:

setInterval(sendToDB, interval * (1 + 0.1 * (Math.random() - 0.5)))

#4. เราแคชสิ่งที่เราต้องการ

ปัญหาการโหลดสูงแบบดั้งเดิมประการที่สามคือ ไม่มีแคช เขาอยู่ไหน สามารถ เป็น.

ตัวอย่างเช่น เราทำให้สามารถวิเคราะห์ในแง่ของโหนดแผนได้ (ทั้งหมดนี้ Seq Scan on users) แต่คิดทันทีว่าส่วนใหญ่เหมือนกัน - พวกเขาลืมไป

ไม่ แน่นอนว่าไม่มีสิ่งใดถูกเขียนลงฐานข้อมูลอีก ซึ่งเป็นการตัดทริกเกอร์ด้วย INSERT ... ON CONFLICT DO NOTHING. แต่ข้อมูลนี้ยังเข้าถึงฐานข้อมูลและไม่จำเป็น การอ่านเพื่อตรวจสอบความขัดแย้ง ต้องทำ. อุ๊ย#3...

ความแตกต่างในจำนวนบันทึกที่ส่งไปยังฐานข้อมูลก่อน/หลังการเปิดใช้งานแคชนั้นชัดเจน:

เราเขียนใน PostgreSQL บนแสงย่อย: 1 โฮสต์ 1 วัน 1TB

และนี่คือปริมาณการจัดเก็บข้อมูลที่ลดลงตามมา:

เราเขียนใน PostgreSQL บนแสงย่อย: 1 โฮสต์ 1 วัน 1TB

เบ็ดเสร็จ

“เทราไบต์ต่อวัน” ฟังดูน่ากลัวจริงๆ หากคุณทำทุกอย่างถูกต้องนี่ก็เป็นเพียง 2^40 ไบต์ / 86400 วินาที = ~12.5MB/วินาทีที่แม้แต่สกรู IDE บนเดสก์ท็อปก็ยังถืออยู่ 🙂

แต่จริงๆ แล้ว แม้ว่าภาระงานจะ "เอียง" ถึงสิบเท่าในระหว่างวัน คุณก็ยังสามารถตอบสนองความสามารถของ SSD สมัยใหม่ได้อย่างง่ายดาย

เราเขียนใน PostgreSQL บนแสงย่อย: 1 โฮสต์ 1 วัน 1TB

ที่มา: will.com

เพิ่มความคิดเห็น