คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

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

เรามีบริการมากมาย: ผู้ใช้ 2,5 ล้านคนทั่วโลก มีผู้ใช้งานมากกว่า 50 คนทุกวัน เซิร์ฟเวอร์ตั้งอยู่ใน Amazone ในภูมิภาคหนึ่งของไอร์แลนด์: เซิร์ฟเวอร์ที่แตกต่างกันกว่า 100 เซิร์ฟเวอร์ทำงานอย่างต่อเนื่อง โดยเกือบ 50 เซิร์ฟเวอร์มีฐานข้อมูล

แบ็กเอนด์ทั้งหมดเป็นแอ็พพลิเคชัน Java stateful แบบเสาหินขนาดใหญ่ที่เชื่อมต่อ websocket กับไคลเอนต์อย่างต่อเนื่อง เมื่อผู้ใช้หลายคนทำงานบนกระดานเดียวกันพร้อมกัน พวกเขาทั้งหมดจะเห็นการเปลี่ยนแปลงตามเวลาจริง เนื่องจากเราเขียนการเปลี่ยนแปลงแต่ละครั้งลงในฐานข้อมูล เรามีคำขอประมาณ 10 รายการต่อวินาทีไปยังฐานข้อมูลของเรา ที่โหลดสูงสุดใน Redis เราเขียนคำขอ 80-100K ต่อวินาที
คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

เหตุใดเราจึงเปลี่ยนจาก Redis เป็น PostgreSQL

ในขั้นต้น บริการของเราทำงานร่วมกับ Redis ซึ่งเป็นที่เก็บคีย์-ค่าที่เก็บข้อมูลทั้งหมดใน RAM ของเซิร์ฟเวอร์

ข้อดีของ Redis:

  1. ความเร็วในการตอบสนองสูงเพราะ ทุกอย่างถูกเก็บไว้ในหน่วยความจำ
  2. ความง่ายในการสำรองข้อมูลและการจำลองแบบ

ข้อเสียของ Redis สำหรับเรา:

  1. ไม่มีการทำธุรกรรมจริง เราพยายามจำลองในระดับแอปพลิเคชันของเรา น่าเสียดายที่สิ่งนี้ไม่ได้ผลดีเสมอไปและจำเป็นต้องเขียนโค้ดที่ซับซ้อนมาก
  2. จำนวนข้อมูลถูกจำกัดโดยจำนวนหน่วยความจำ เมื่อปริมาณข้อมูลเพิ่มขึ้น หน่วยความจำก็จะเพิ่มขึ้น และท้ายที่สุด เราจะพบลักษณะของอินสแตนซ์ที่เลือก ซึ่งใน AWS กำหนดให้หยุดบริการของเราเพื่อเปลี่ยนประเภทของอินสแตนซ์
  3. จำเป็นต้องรักษาระดับเวลาแฝงให้ต่ำอย่างต่อเนื่องเพราะ เรามีคำขอจำนวนมาก ระดับการหน่วงเวลาที่เหมาะสมที่สุดสำหรับเราคือ 17-20 มิลลิวินาที ที่ระดับ 30-40 มิลลิวินาที เราได้รับการตอบสนองที่ยาวนานต่อคำขอจากแอปพลิเคชันของเราและการลดลงของบริการ น่าเสียดายที่สิ่งนี้เกิดขึ้นกับเราในเดือนกันยายน 2018 เมื่อหนึ่งในอินสแตนซ์ที่ใช้ Redis ด้วยเหตุผลบางประการได้รับเวลาในการตอบสนองมากกว่าปกติ 2 เท่า เพื่อแก้ไขปัญหานี้ เราได้หยุดบริการในช่วงกลางวันสำหรับการบำรุงรักษาที่ไม่ได้กำหนดไว้และแทนที่อินสแตนซ์ Redis ที่มีปัญหา
  4. เป็นเรื่องง่ายที่ข้อมูลจะไม่สอดคล้องกันแม้จะมีข้อผิดพลาดเล็กน้อยในโค้ด จากนั้นจึงใช้เวลามากในการเขียนโค้ดเพื่อแก้ไขข้อมูลนี้

เราคำนึงถึงข้อเสียและตระหนักว่าเราจำเป็นต้องเปลี่ยนไปใช้สิ่งที่สะดวกกว่า ด้วยธุรกรรมปกติและพึ่งพาเวลาแฝงน้อยลง ทำการวิจัย วิเคราะห์ตัวเลือกมากมาย และเลือก PostgreSQL

เราได้ย้ายไปยังฐานข้อมูลใหม่เป็นเวลา 1,5 ปีแล้ว และได้ย้ายข้อมูลเพียงส่วนเล็กๆ ดังนั้นตอนนี้เรากำลังทำงานร่วมกับ Redis และ PostgreSQL ไปพร้อมๆ กัน มีการเขียนข้อมูลเพิ่มเติมเกี่ยวกับขั้นตอนการย้ายและสลับข้อมูลระหว่างฐานข้อมูล บทความของเพื่อนร่วมงานของฉัน.

เมื่อเราเริ่มย้ายครั้งแรก แอปพลิเคชันของเราทำงานโดยตรงกับฐานข้อมูลและเข้าถึง Redis และ PostgreSQL หลัก คลัสเตอร์ PostgreSQL ประกอบด้วยต้นแบบและแบบจำลองที่มีการจำลองแบบอะซิงโครนัส นี่คือลักษณะของโครงร่างฐานข้อมูล:
คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

การใช้งาน PgBouncer

ในขณะที่เรากำลังย้าย ผลิตภัณฑ์ก็กำลังพัฒนาเช่นกัน จำนวนผู้ใช้และจำนวนเซิร์ฟเวอร์ที่ทำงานกับ PostgreSQL เพิ่มขึ้น และเราเริ่มขาดการเชื่อมต่อ PostgreSQL สร้างกระบวนการแยกต่างหากสำหรับแต่ละการเชื่อมต่อและใช้ทรัพยากร คุณสามารถเพิ่มจำนวนการเชื่อมต่อได้จนถึงจุดหนึ่ง มิฉะนั้น มีโอกาสได้รับประสิทธิภาพของฐานข้อมูลที่ต่ำกว่ามาตรฐาน ตัวเลือกที่เหมาะสมที่สุดในสถานการณ์เช่นนี้คือการเลือกตัวจัดการการเชื่อมต่อที่จะยืนอยู่หน้าฐาน

เรามีสองตัวเลือกสำหรับเครื่องมือจัดการการเชื่อมต่อ: Pgpool และ PgBouncer แต่อันแรกไม่รองรับโหมดการทำธุรกรรมในการทำงานกับฐานข้อมูล ดังนั้นเราจึงเลือก PgBouncer

เราได้ตั้งค่าโครงร่างการทำงานดังต่อไปนี้: แอปพลิเคชันของเราเข้าถึง PgBouncer หนึ่งตัว ซึ่งเบื้องหลังคือมาสเตอร์ของ PostgreSQL และเบื้องหลังมาสเตอร์แต่ละตัวคือหนึ่งเรพลิเคชันที่มีการจำลองแบบอะซิงโครนัส
คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

ในขณะเดียวกัน เราไม่สามารถเก็บข้อมูลใน PostgreSQL ได้ทั้งหมด และความเร็วในการทำงานกับฐานข้อมูลก็เป็นสิ่งสำคัญสำหรับเรา เราจึงเริ่มแบ่งย่อย PostgreSQL ที่ระดับแอปพลิเคชัน รูปแบบที่อธิบายไว้ข้างต้นค่อนข้างสะดวกสำหรับสิ่งนี้: เมื่อเพิ่มชาร์ด PostgreSQL ใหม่ ก็เพียงพอแล้วที่จะอัปเดตการกำหนดค่า PgBouncer และแอปพลิเคชันสามารถทำงานกับชาร์ดใหม่ได้ทันที

PgBouncer ล้มเหลว

รูปแบบนี้ทำงานจนถึงช่วงเวลาที่ PgBouncer เพียงตัวเดียวเสียชีวิต เราอยู่ใน AWS ซึ่งอินสแตนซ์ทั้งหมดทำงานบนฮาร์ดแวร์ที่หยุดทำงานเป็นระยะๆ ในกรณีเช่นนี้ อินสแตนซ์จะย้ายไปยังฮาร์ดแวร์ใหม่และทำงานอีกครั้ง สิ่งนี้เกิดขึ้นกับ PgBouncer แต่ไม่สามารถใช้งานได้ ผลของการล่มสลายนี้คือบริการของเราไม่พร้อมให้บริการเป็นเวลา 25 นาที AWS แนะนำให้ใช้การซ้ำซ้อนฝั่งผู้ใช้สำหรับสถานการณ์ดังกล่าว ซึ่งไม่ได้นำมาใช้ในประเทศของเราในขณะนั้น

หลังจากนั้น เราคิดอย่างจริงจังเกี่ยวกับความทนทานต่อข้อบกพร่องของคลัสเตอร์ PgBouncer และ PostgreSQL เนื่องจากสถานการณ์ที่คล้ายกันอาจเกิดขึ้นกับอินสแตนซ์ใดๆ ในบัญชี AWS ของเรา

เราสร้างโครงร่างการยอมรับข้อผิดพลาดของ PgBouncer ดังนี้: เซิร์ฟเวอร์แอปพลิเคชันทั้งหมดเข้าถึง Network Load Balancer ซึ่งด้านหลังมี PgBouncer สองตัว PgBouncer แต่ละตัวจะดูที่ต้นแบบ PostgreSQL เดียวกันของแต่ละชาร์ด หากเกิดความผิดพลาดของอินสแตนซ์ AWS อีกครั้ง การรับส่งข้อมูลทั้งหมดจะถูกเปลี่ยนเส้นทางผ่าน PgBouncer อื่น Network Load Balancer ล้มเหลวให้บริการโดย AWS

รูปแบบนี้ช่วยให้เพิ่มเซิร์ฟเวอร์ PgBouncer ใหม่ได้ง่าย
คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

สร้างคลัสเตอร์ล้มเหลว PostgreSQL

เมื่อแก้ปัญหานี้ เราพิจารณาตัวเลือกต่างๆ: เฟลโอเวอร์แบบเขียนเอง, repmgr, AWS RDS, Patroni

สคริปต์ที่เขียนขึ้นเอง

พวกเขาสามารถตรวจสอบการทำงานของต้นแบบและในกรณีที่เกิดความล้มเหลว ส่งเสริมแบบจำลองไปยังต้นแบบและอัปเดตการกำหนดค่า PgBouncer

ข้อดีของวิธีนี้คือความเรียบง่ายสูงสุด เนื่องจากคุณเขียนสคริปต์ด้วยตัวเองและเข้าใจวิธีการทำงานอย่างแท้จริง

จุดด้อย:

  • ต้นแบบอาจไม่ตาย แต่อาจเกิดความล้มเหลวของเครือข่ายแทน ความล้มเหลวโดยไม่ทราบสิ่งนี้ จะเลื่อนระดับแบบจำลองไปยังต้นแบบ ในขณะที่ต้นแบบเก่าจะทำงานต่อไป ดังนั้น เราจะได้เซิร์ฟเวอร์สองเครื่องในบทบาทของมาสเตอร์ และเราจะไม่รู้ว่าเซิร์ฟเวอร์ใดที่มีข้อมูลล่าสุด สถานการณ์นี้เรียกอีกอย่างว่าสมองแตก
  • เราถูกทิ้งไว้โดยไม่มีการตอบสนอง ในการกำหนดค่าของเรา ต้นแบบและหนึ่งแบบจำลอง หลังจากสลับ แบบจำลองจะย้ายขึ้นไปยังต้นแบบและเราไม่มีแบบจำลองอีกต่อไป ดังนั้นเราต้องเพิ่มแบบจำลองใหม่ด้วยตนเอง
  • เราต้องการการตรวจสอบเพิ่มเติมของการดำเนินการเฟลโอเวอร์ ในขณะที่เรามี 12 ชาร์ดของ PostgreSQL ซึ่งหมายความว่าเราต้องตรวจสอบ 12 คลัสเตอร์ ด้วยจำนวนชาร์ดที่เพิ่มขึ้น คุณต้องอย่าลืมอัปเดตเฟลโอเวอร์ด้วย

เฟลโอเวอร์ที่เขียนขึ้นเองนั้นดูซับซ้อนมากและต้องการการสนับสนุนที่ไม่สำคัญ ด้วยคลัสเตอร์ PostgreSQL เดียว นี่จะเป็นตัวเลือกที่ง่ายที่สุด แต่ไม่สามารถปรับขนาดได้ จึงไม่เหมาะกับเรา

ตอบกลับ

Replication Manager สำหรับคลัสเตอร์ PostgreSQL ซึ่งสามารถจัดการการทำงานของคลัสเตอร์ PostgreSQL ในขณะเดียวกันก็ไม่มีการเฟลโอเวอร์อัตโนมัติเมื่อแกะกล่อง ดังนั้นในการทำงาน คุณจะต้องเขียน "wrapper" ของคุณเองไว้ด้านบนของโซลูชันที่เสร็จแล้ว ดังนั้นทุกอย่างอาจซับซ้อนกว่าสคริปต์ที่เขียนขึ้นเอง ดังนั้นเราจึงไม่ได้ลองใช้ Repmgr

AWS RDS

รองรับทุกสิ่งที่เราต้องการ รู้วิธีสำรองข้อมูลและดูแลกลุ่มการเชื่อมต่อ มีการสลับอัตโนมัติ: เมื่อต้นแบบตาย แบบจำลองจะกลายเป็นต้นแบบใหม่ และ AWS เปลี่ยนบันทึก DNS เป็นต้นแบบใหม่ ในขณะที่แบบจำลองสามารถอยู่ใน AZ ที่แตกต่างกัน

ข้อเสียรวมถึงการขาดการปรับแต่งที่ดี ตัวอย่างการปรับละเอียด: อินสแตนซ์ของเรามีข้อจำกัดสำหรับการเชื่อมต่อ tcp ซึ่งน่าเสียดายที่ไม่สามารถทำได้ใน RDS:

net.ipv4.tcp_keepalive_time=10
net.ipv4.tcp_keepalive_intvl=1
net.ipv4.tcp_keepalive_probes=5
net.ipv4.tcp_retries2=3

นอกจากนี้ AWS RDS ยังแพงกว่าราคาอินสแตนซ์ปกติเกือบสองเท่า ซึ่งเป็นสาเหตุหลักในการละทิ้งโซลูชันนี้

ผู้มีพระคุณ

นี่คือเทมเพลตหลามสำหรับจัดการ PostgreSQL ด้วยเอกสารประกอบที่ดี เฟลโอเวอร์อัตโนมัติ และซอร์สโค้ดบน GitHub

ข้อดีของ Patroni:

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

จุดด้อย:

  • จากเอกสารวิธีการทำงานกับ PgBouncer อย่างถูกต้องไม่ชัดเจน แม้ว่าจะยากที่จะเรียกว่าเป็นลบ แต่เนื่องจากงานของ Patroni คือการจัดการ PostgreSQL และการเชื่อมต่อกับ Patroni นั้นเป็นปัญหาของเราอยู่แล้ว
  • มีตัวอย่างเล็กน้อยของการนำ Patroni ไปใช้ในปริมาณมาก ในขณะที่มีตัวอย่างมากมายของการนำ Patroni ไปใช้ตั้งแต่เริ่มต้น

ด้วยเหตุนี้ เราจึงเลือก Patroni เพื่อสร้างคลัสเตอร์เฟลโอเวอร์

กระบวนการดำเนินการ Patroni

ก่อน Patroni เรามี PostgreSQL shards 12 ชิ้นในการกำหนดค่าของต้นแบบหนึ่งรายการและแบบจำลองหนึ่งรายการที่มีการจำลองแบบอะซิงโครนัส เซิร์ฟเวอร์แอปพลิเคชันเข้าถึงฐานข้อมูลผ่าน Network Load Balancer ซึ่งเบื้องหลังคือสองอินสแตนซ์ที่มี PgBouncer และเบื้องหลังคือเซิร์ฟเวอร์ PostgreSQL ทั้งหมด
คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

ในการปรับใช้ Patroni เราจำเป็นต้องเลือกการกำหนดค่าคลัสเตอร์พื้นที่เก็บข้อมูลแบบกระจาย Patroni ทำงานร่วมกับระบบจัดเก็บการกำหนดค่าแบบกระจาย เช่น etcd, Zookeeper, Consul เราเพิ่งมีกลุ่ม Consul เต็มรูปแบบในตลาด ซึ่งทำงานร่วมกับ Vault และเราไม่ได้ใช้อีกต่อไป เหตุผลที่ดีในการเริ่มใช้ Consul ตามจุดประสงค์

Patroni ทำงานร่วมกับกงสุลอย่างไร

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

คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

ในการเชื่อมต่อ Patroni กับ Consul ก็เพียงพอแล้วที่จะศึกษาเอกสารอย่างเป็นทางการซึ่งระบุว่าคุณต้องระบุโฮสต์ในรูปแบบ http หรือ https ขึ้นอยู่กับวิธีที่เราทำงานร่วมกับ Consul และรูปแบบการเชื่อมต่อ ทางเลือก:

host: the host:port for the Consul endpoint, in format: http(s)://host:port
scheme: (optional) http or https, defaults to http

มันดูเรียบง่าย แต่ที่นี่มีข้อผิดพลาดเริ่มต้นขึ้น ด้วย Consul เราทำงานผ่านการเชื่อมต่อที่ปลอดภัยผ่าน https และการกำหนดค่าการเชื่อมต่อของเราจะมีลักษณะดังนี้:

consul:
  host: https://server.production.consul:8080 
  verify: true
  cacert: {{ consul_cacert }}
  cert: {{ consul_cert }}
  key: {{ consul_key }}

แต่นั่นไม่ได้ผล เมื่อเริ่มต้น Patroni ไม่สามารถเชื่อมต่อกับ Consul ได้ เนื่องจากยังคงพยายามผ่าน http

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

consul:
  host: server.production.consul:8080
  scheme: https
  verify: true
  cacert: {{ consul_cacert }}
  cert: {{ consul_cert }}
  key: {{ consul_key }}

แม่แบบกงสุล

ดังนั้นเราจึงเลือกที่เก็บข้อมูลสำหรับการกำหนดค่า ตอนนี้เราต้องเข้าใจว่า PgBouncer จะเปลี่ยนการกำหนดค่าอย่างไรเมื่อเปลี่ยนผู้นำในคลัสเตอร์ Patroni ไม่มีคำตอบสำหรับคำถามนี้ในเอกสารเพราะ โดยหลักการแล้วไม่มีการอธิบายการทำงานกับ PgBouncer

ในการค้นหาวิธีแก้ไข เราพบบทความหนึ่ง (ขออภัยจำชื่อเรื่องไม่ได้) ซึ่งเขียนว่า เทมเพลต Сonsul ช่วยได้มากในการจับคู่ PgBouncer และ Patroni สิ่งนี้ทำให้เราต้องตรวจสอบว่าแม่แบบกงสุลทำงานอย่างไร

ปรากฎว่าเทมเพลต Consul ตรวจสอบการกำหนดค่าของคลัสเตอร์ PostgreSQL ใน Consul อย่างต่อเนื่อง เมื่อลีดเดอร์เปลี่ยน จะอัปเดตการกำหนดค่า PgBouncer และส่งคำสั่งให้โหลดซ้ำ

คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

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

สถาปัตยกรรมใหม่กับ Patroni

เป็นผลให้เราได้รูปแบบการทำงานดังต่อไปนี้:
คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

เซิร์ฟเวอร์แอปพลิเคชันทั้งหมดเข้าถึงบาลานเซอร์ → มี PgBouncer สองอินสแตนซ์อยู่เบื้องหลัง → ในแต่ละอินสแตนซ์ เทมเพลต Consul จะเปิดใช้งาน ซึ่งจะตรวจสอบสถานะของแต่ละคลัสเตอร์ Patroni และตรวจสอบความเกี่ยวข้องของการกำหนดค่า PgBouncer ซึ่งส่งคำขอไปยังผู้นำปัจจุบัน ของแต่ละคลัสเตอร์

การทดสอบด้วยตนเอง

เราเรียกใช้โครงร่างนี้ก่อนที่จะเปิดใช้งานในสภาพแวดล้อมการทดสอบขนาดเล็ก และตรวจสอบการทำงานของการสลับอัตโนมัติ พวกเขาเปิดกระดาน ย้ายสติกเกอร์ และในขณะนั้น พวกเขา "ฆ่า" หัวหน้าของกลุ่ม ใน AWS สิ่งนี้ทำได้ง่ายเพียงแค่ปิดอินสแตนซ์ผ่านคอนโซล

คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

สติกเกอร์กลับมาภายใน 10-20 วินาทีจากนั้นก็เริ่มเคลื่อนไหวตามปกติอีกครั้ง ซึ่งหมายความว่าคลัสเตอร์ Patroni ทำงานได้อย่างถูกต้อง: เปลี่ยนผู้นำ ส่งข้อมูลไปยัง Сonsul และเทมเพลต Сonsul รับข้อมูลนี้ทันที แทนที่การกำหนดค่า PgBouncer และส่งคำสั่งให้โหลดซ้ำ

จะอยู่รอดภายใต้โหลดสูงและรักษาเวลาหยุดทำงานให้น้อยที่สุดได้อย่างไร

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

สภาพแวดล้อมการทดสอบที่เราทำการทดสอบโหลดช่วยให้เราตอบคำถามแรกได้ มันเหมือนกับการผลิตโดยสิ้นเชิงในแง่ของสถาปัตยกรรม และได้สร้างข้อมูลการทดสอบที่มีปริมาณใกล้เคียงกับการผลิตโดยประมาณ เราตัดสินใจที่จะ "ฆ่า" หนึ่งในมาสเตอร์ของ PostgreSQL ในระหว่างการทดสอบและดูว่าเกิดอะไรขึ้น แต่ก่อนหน้านั้น สิ่งสำคัญคือต้องตรวจสอบการกลิ้งอัตโนมัติ เนื่องจากในสภาพแวดล้อมนี้ เรามีเศษ PostgreSQL จำนวนมาก ดังนั้นเราจะได้รับการทดสอบที่ยอดเยี่ยมของสคริปต์การกำหนดค่าก่อนการผลิต

ทั้งสองงานดูทะเยอทะยาน แต่เรามี PostgreSQL 9.6 เราสามารถอัปเกรดเป็น 11.2 ได้ทันทีหรือไม่

เราตัดสินใจที่จะดำเนินการใน 2 ขั้นตอน: ขั้นแรกอัปเกรดเป็น 11.2 จากนั้นเปิดตัว Patroni

อัพเดต PostgreSQL

หากต้องการอัปเดตเวอร์ชัน PostgreSQL อย่างรวดเร็ว ให้ใช้ตัวเลือก -kซึ่งฮาร์ดลิงก์ถูกสร้างขึ้นบนดิสก์และไม่จำเป็นต้องคัดลอกข้อมูลของคุณ บนพื้นฐาน 300-400 GB การอัปเดตจะใช้เวลา 1 วินาที

เรามีชาร์ดจำนวนมาก ดังนั้นการอัปเดตจำเป็นต้องทำโดยอัตโนมัติ ในการทำเช่นนี้ เราได้เขียน Playbook ของ Ansible ที่จัดการกระบวนการอัปเดตทั้งหมดให้กับเรา:

/usr/lib/postgresql/11/bin/pg_upgrade 
<b>--link </b>
--old-datadir='' --new-datadir='' 
 --old-bindir=''  --new-bindir='' 
 --old-options=' -c config_file=' 
 --new-options=' -c config_file='

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

เปิดตัว Patroni

เพื่อแก้ปัญหาที่สอง ให้ดูที่การกำหนดค่า Patroni พื้นที่เก็บข้อมูลอย่างเป็นทางการมีการกำหนดค่าตัวอย่างด้วย initdb ซึ่งมีหน้าที่รับผิดชอบในการเริ่มต้นฐานข้อมูลใหม่เมื่อคุณเริ่ม Patroni เป็นครั้งแรก แต่เนื่องจากเรามีฐานข้อมูลสำเร็จรูปอยู่แล้ว เราจึงลบส่วนนี้ออกจากการกำหนดค่า

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

rm -rf /var/lib/postgresql/

นี้ต้องทำบนทาสเท่านั้น!

เมื่อเชื่อมต่อแบบจำลองใหม่ทั้งหมด Patroni จะสร้างผู้นำการสำรองข้อมูลฐานและคืนค่าไปยังแบบจำลอง จากนั้นจะติดตามสถานะปัจจุบันตามบันทึกของวอล

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

โหลดทดสอบ

เราได้เปิดตัวการทดสอบที่จำลองประสบการณ์ของผู้ใช้บนกระดาน เมื่อโหลดถึงค่าเฉลี่ยรายวันของเรา เราก็ทำการทดสอบเดิมซ้ำอีกครั้ง เราปิดหนึ่งอินสแตนซ์ด้วยตัวนำ PostgreSQL ระบบเฟลโอเวอร์อัตโนมัติทำงานตามที่เราคาดไว้: Patroni เปลี่ยนผู้นำ เทมเพลตกงสุลอัปเดตการกำหนดค่า PgBouncer และส่งคำสั่งเพื่อโหลดซ้ำ จากกราฟของเราใน Grafana เห็นได้ชัดว่ามีความล่าช้า 20-30 วินาทีและข้อผิดพลาดเล็กน้อยจากเซิร์ฟเวอร์ที่เกี่ยวข้องกับการเชื่อมต่อกับฐานข้อมูล นี่เป็นสถานการณ์ปกติ ค่าดังกล่าวเป็นที่ยอมรับสำหรับความล้มเหลวของเรา และดีกว่าการหยุดทำงานของบริการอย่างแน่นอน

นำ Patroni ไปสู่การผลิต

เป็นผลให้เราได้แผนต่อไปนี้:

  • ปรับใช้เทมเพลตกงสุลกับเซิร์ฟเวอร์ PgBouncer และเปิดใช้งาน
  • PostgreSQL อัปเดตเป็นเวอร์ชัน 11.2;
  • เปลี่ยนชื่อคลัสเตอร์
  • การเริ่มต้น Patroni Cluster

ในขณะเดียวกัน แบบแผนของเราช่วยให้เราสร้างจุดแรกได้เกือบตลอดเวลา เราสามารถลบ PgBouncer แต่ละตัวออกจากงานได้ และปรับใช้และเรียกใช้แม่แบบกงสุลบนนั้น ดังนั้นเราจึงทำ

สำหรับการปรับใช้อย่างรวดเร็ว เราใช้ Ansible เนื่องจากเราได้ทดสอบ playbooks ทั้งหมดในสภาพแวดล้อมการทดสอบแล้ว และเวลาดำเนินการของสคริปต์แบบเต็มคือ 1,5 ถึง 2 นาทีสำหรับแต่ละส่วนย่อย เราสามารถนำทุกอย่างไปใช้กับแต่ละชาร์ดโดยไม่ต้องหยุดบริการของเรา แต่เราจะต้องปิด PostgreSQL แต่ละอันเป็นเวลาหลายนาที ในกรณีนี้ ผู้ใช้ที่มีข้อมูลอยู่ในชาร์ดนี้ไม่สามารถทำงานได้อย่างเต็มที่ในขณะนี้ ซึ่งเป็นสิ่งที่ยอมรับไม่ได้สำหรับเรา

ทางออกของสถานการณ์นี้คือการบำรุงรักษาตามแผนซึ่งจะเกิดขึ้นทุกๆ 3 เดือน นี่คือหน้าต่างสำหรับงานตามกำหนดเวลา เมื่อเราปิดบริการของเราอย่างสมบูรณ์และอัปเกรดอินสแตนซ์ฐานข้อมูลของเรา เหลือเวลาอีกหนึ่งสัปดาห์ก่อนจะถึงหน้าต่างถัดไป และเราตัดสินใจที่จะรอและเตรียมตัวต่อไป ในระหว่างเวลาที่รอ เราได้เพิ่มความปลอดภัยให้กับตัวเอง: สำหรับแต่ละชาร์ดของ PostgreSQL เราได้เพิ่มแบบจำลองสำรองในกรณีที่ไม่สามารถเก็บข้อมูลล่าสุดได้ และเพิ่มอินสแตนซ์ใหม่สำหรับแต่ละชาร์ด ซึ่งควรจะกลายเป็นแบบจำลองใหม่ในคลัสเตอร์ Patroni เพื่อไม่ให้ดำเนินการคำสั่งเพื่อลบข้อมูล ทั้งหมดนี้ช่วยลดความเสี่ยงของข้อผิดพลาด
คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

เราเริ่มต้นบริการของเราใหม่ ทุกอย่างทำงานตามปกติ ผู้ใช้ยังคงทำงานต่อไป แต่บนกราฟ เราสังเกตเห็นว่าเซิร์ฟเวอร์ Consul มีโหลดสูงผิดปกติ
คลัสเตอร์ล้มเหลว PostgreSQL + Patroni ประสบการณ์การใช้งาน

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

รีสตาร์ทคลัสเตอร์ Patroni

อย่างไรก็ตาม เราพบปัญหาใหม่ซึ่งเราไม่ได้สงสัยด้วยซ้ำ เมื่ออัปเดต Consul เราเพียงลบโหนด Consul ออกจากคลัสเตอร์โดยใช้คำสั่ง consul leave → Patroni เชื่อมต่อกับเซิร์ฟเวอร์ Consul อื่น → ทุกอย่างทำงานได้ แต่เมื่อเราไปถึงตัวอย่างสุดท้ายของคลัสเตอร์ Consul และส่งคำสั่งให้กงสุลออกไป คลัสเตอร์ Patroni ทั้งหมดก็เริ่มต้นใหม่ และในบันทึก เราเห็นข้อผิดพลาดต่อไปนี้:

ERROR: get_cluster
Traceback (most recent call last):
...
RetryFailedError: 'Exceeded retry deadline'
ERROR: Error communicating with DCS
<b>LOG: database system is shut down</b>

คลัสเตอร์ Patroni ไม่สามารถดึงข้อมูลเกี่ยวกับคลัสเตอร์และรีสตาร์ทได้

เพื่อหาทางออก เราได้ติดต่อผู้เขียน Patroni ผ่านทางปัญหาเกี่ยวกับ GitHub พวกเขาแนะนำการปรับปรุงไฟล์การกำหนดค่าของเรา:

consul:
 consul.checks: []
bootstrap:
 dcs:
   retry_timeout: 8

เราสามารถทำซ้ำปัญหาในสภาพแวดล้อมการทดสอบและทดสอบตัวเลือกเหล่านี้ที่นั่น แต่น่าเสียดายที่มันไม่ได้ผล

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

  • ใช้ Consul-agent ในแต่ละอินสแตนซ์ของ Patroni คลัสเตอร์
  • แก้ไขปัญหาในรหัส

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

โชคดีที่เราไม่พบข้อผิดพลาดอีก

ผลลัพธ์ของการใช้ Patroni

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

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

บทสรุปเล็กน้อยของการใช้ Patroni:

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

ที่มา: will.com

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