SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

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

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

Go นั้นดีเป็นพิเศษที่นี่เพราะมีเครื่องมือสร้างโปรไฟล์ pprof ในห้องสมุดมาตรฐาน

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

กลยุทธ์

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

  • เรากำหนดขอบเขตการปรับให้เหมาะสม (ข้อกำหนด)
  • เราคำนวณปริมาณธุรกรรมสำหรับระบบ
  • เราทำการทดสอบ (สร้างข้อมูล)
  • เราสังเกต;
  • เราวิเคราะห์ - ตรงตามข้อกำหนดทั้งหมดหรือไม่
  • เราตั้งสมมติฐานขึ้นมาทางวิทยาศาสตร์
  • เราทำการทดลองเพื่อทดสอบสมมติฐานนี้

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

สถาปัตยกรรมเซิร์ฟเวอร์ HTTP อย่างง่าย

สำหรับบทความนี้ เราจะใช้เซิร์ฟเวอร์ HTTP ขนาดเล็กใน Golang สามารถพบได้รหัสทั้งหมดจากบทความนี้ ที่นี่.

แอปพลิเคชันที่กำลังวิเคราะห์คือเซิร์ฟเวอร์ HTTP ที่สำรวจ Postgresql สำหรับแต่ละคำขอ นอกจากนี้ยังมี Prometheus, node_exporter และ Grafana สำหรับการรวบรวมและแสดงการวัดแอปพลิเคชันและระบบ

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

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

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

การกำหนดเป้าหมาย

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

В หนังสือ Google SRE มีการกล่าวถึงวิธีการคัดเลือกและการสร้างแบบจำลองอย่างละเอียด ลองทำแบบเดียวกันและสร้างโมเดล:

  • เวลาแฝง: 99% ของคำขอควรเสร็จสิ้นภายในเวลาน้อยกว่า 60 มิลลิวินาที
  • ค่าใช้จ่าย: บริการควรใช้จำนวนเงินขั้นต่ำที่เราคิดว่าเป็นไปได้อย่างสมเหตุสมผล ในการทำเช่นนี้ เราเพิ่มปริมาณงานให้สูงสุด
  • การวางแผนความจุ: ต้องทำความเข้าใจและจัดทำเอกสารว่าจะต้องเรียกใช้อินสแตนซ์จำนวนเท่าใด รวมถึงฟังก์ชันการปรับขนาดโดยรวม และจำนวนอินสแตนซ์ที่จำเป็นเพื่อให้เป็นไปตามข้อกำหนดในการโหลดเริ่มต้นและการจัดเตรียม ความซ้ำซ้อน n+1.

เวลาแฝงอาจต้องมีการปรับให้เหมาะสมนอกเหนือจากการวิเคราะห์ แต่จำเป็นต้องวิเคราะห์ปริมาณงานอย่างชัดเจน เมื่อใช้กระบวนการ SRE SLO คำขอล่าช้าจะมาจากลูกค้าหรือธุรกิจ ซึ่งแสดงโดยเจ้าของผลิตภัณฑ์ และบริการของเราจะปฏิบัติตามข้อผูกพันนี้ตั้งแต่เริ่มต้นโดยไม่มีการตั้งค่าใด ๆ !

การตั้งค่าสภาพแวดล้อมการทดสอบ

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

โหลดธุรกรรม

สภาพแวดล้อมนี้ใช้ แพร่พันธุ์ เพื่อสร้างอัตราการร้องขอ HTTP แบบกำหนดเองจนกว่าจะหยุด:

$ make load-test LOAD_TEST_RATE=50
echo "POST http://localhost:8080" | vegeta attack -body tests/fixtures/age_no_match.json -rate=50 -duration=0 | tee results.bin | vegeta report

การเฝ้าดู

โหลดของธุรกรรมจะถูกนำไปใช้ที่รันไทม์ นอกเหนือจากตัววัดแอปพลิเคชัน (จำนวนคำขอ เวลาแฝงในการตอบสนอง) และระบบปฏิบัติการ (หน่วยความจำ, CPU, IOPS) แล้ว การทำโปรไฟล์แอปพลิเคชันจะถูกเรียกใช้เพื่อทำความเข้าใจว่ามีปัญหาตรงไหนและใช้เวลา CPU อย่างไร

การทำโปรไฟล์

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

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

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

การดำเนินการ การสังเกต การวิเคราะห์

มาทำการทดลองกัน เราจะดำเนินการ สังเกต และวิเคราะห์จนกว่าเราจะพอใจกับผลงาน ให้เราเลือกค่าโหลดที่ต่ำโดยพลการเพื่อนำไปใช้เพื่อให้ได้ผลลัพธ์ของการสังเกตครั้งแรก ในแต่ละขั้นตอนต่อมา เราจะเพิ่มภาระด้วยปัจจัยสเกลที่แน่นอน โดยเลือกด้วยการเปลี่ยนแปลงบางอย่าง การทดสอบโหลดแต่ละครั้งจะดำเนินการโดยปรับจำนวนคำขอ: make load-test LOAD_TEST_RATE=X.

50 คำขอต่อวินาที

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

ให้ความสนใจกับกราฟสองตัวบนสุด ด้านซ้ายบนแสดงว่าแอปพลิเคชันของเราประมวลผลคำขอ 50 รายการต่อวินาที (คิดว่า) และด้านขวาบนแสดงระยะเวลาของแต่ละคำขอ พารามิเตอร์ทั้งสองช่วยให้เราดูและวิเคราะห์ว่าเราอยู่ในขอบเขตประสิทธิภาพของเราหรือไม่ เส้นสีแดงบนกราฟ เวลาแฝงของคำขอ HTTP แสดง SLO ที่ 60ms บรรทัดแสดงว่าเราอยู่ต่ำกว่าเวลาตอบสนองสูงสุดของเรามาก

ลองดูด้านต้นทุน:

10000 คำขอต่อวินาที / 50 คำขอต่อเซิร์ฟเวอร์ = 200 เซิร์ฟเวอร์ + 1

เรายังคงสามารถปรับปรุงตัวเลขนี้ได้

500 คำขอต่อวินาที

สิ่งที่น่าสนใจอื่นๆ เริ่มเกิดขึ้นเมื่อโหลดมีคำขอถึง 500 คำขอต่อวินาที:

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

อีกครั้งในกราฟด้านซ้ายบน คุณจะเห็นว่าแอปพลิเคชันกำลังบันทึกการโหลดปกติ หากไม่เป็นเช่นนั้น แสดงว่ามีปัญหาบนเซิร์ฟเวอร์ที่แอปพลิเคชันกำลังทำงานอยู่ กราฟเวลาในการตอบสนองจะอยู่ที่มุมขวาบน ซึ่งแสดงว่าคำขอ 500 รายการต่อวินาทีส่งผลให้เกิดความล่าช้าในการตอบสนอง 25-40 มิลลิวินาที เปอร์เซ็นไทล์ที่ 99 ยังคงเข้ากันได้ดีกับ SLO 60 มิลลิวินาทีที่เลือกไว้ด้านบน

ในแง่ของต้นทุน:

10000 คำขอต่อวินาที / 500 คำขอต่อเซิร์ฟเวอร์ = 20 เซิร์ฟเวอร์ + 1

ทุกอย่างยังสามารถปรับปรุงได้

1000 คำขอต่อวินาที

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

เปิดตัวยิ่งใหญ่! แอปพลิเคชันแสดงว่าประมวลผลคำขอ 1000 รายการต่อวินาที แต่ SLO ละเมิดขีดจำกัดเวลาแฝง สามารถดูได้ในบรรทัด p99 ในกราฟมุมขวาบน แม้ว่าสาย p100 จะสูงกว่ามาก แต่ความล่าช้าที่เกิดขึ้นจริงยังสูงกว่าค่าสูงสุด 60ms มาเจาะลึกการทำโปรไฟล์เพื่อดูว่าแอปพลิเคชันทำหน้าที่อะไรจริงๆ

การทำโปรไฟล์

สำหรับการจัดทำโปรไฟล์ เราตั้งค่าโหลดเป็น 1000 คำขอต่อวินาที จากนั้นจึงใช้ pprof เพื่อเก็บข้อมูลเพื่อดูว่าแอปพลิเคชันใช้เวลา CPU อยู่ที่ใด ซึ่งสามารถทำได้โดยการเปิดใช้งานตำแหน่งข้อมูล HTTP pprofจากนั้นภายใต้การโหลด ให้บันทึกผลลัพธ์โดยใช้ curl:

$ curl http://localhost:8080/debug/pprof/profile?seconds=29 > cpu.1000_reqs_sec_no_optimizations.prof

ผลลัพธ์สามารถแสดงได้ดังนี้:

$ go tool pprof -http=:12345 cpu.1000_reqs_sec_no_optimizations.prof

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

กราฟแสดงตำแหน่งและจำนวนที่แอปพลิเคชันใช้เวลา CPU จากคำอธิบายจาก เบรนแดน เกร็ก:

แกน X คือประชากรโปรไฟล์สแต็ก เรียงตามตัวอักษร (นี่ไม่ใช่เวลา) แกน Y จะแสดงความลึกของสแต็ก โดยนับจากศูนย์ที่ [บนสุด] แต่ละสี่เหลี่ยมผืนผ้าเป็นกรอบสแต็ก ยิ่งเฟรมกว้างเท่าไรก็ยิ่งปรากฏอยู่ในสแต็กบ่อยขึ้นเท่านั้น สิ่งที่ทำงานอยู่ด้านบนสุดบน CPU และสิ่งที่อยู่ด้านล่างคือองค์ประกอบลูก สีต่างๆ มักจะไม่มีความหมายใดๆ แต่เป็นเพียงการเลือกแบบสุ่มเพื่อแยกแยะเฟรมต่างๆ

การวิเคราะห์-สมมติฐาน

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

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

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

หากคุณวางเคอร์เซอร์ไว้เหนือชื่อของฟังก์ชันบนกราฟ เวลาทั้งหมดที่อยู่ในสแต็กระหว่างการแก้ไขข้อบกพร่องจะปรากฏขึ้น ฟังก์ชัน HTTPServe อยู่ที่นั่น 65% ของเวลา รวมถึงฟังก์ชันรันไทม์อื่นๆ runtime.mcall, mstart и gc,ใช้เวลาที่เหลือ เรื่องน่าสนุก: 5% ของเวลาทั้งหมดถูกใช้ไปกับการสืบค้น DNS:

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

ที่อยู่ที่โปรแกรมค้นหาเป็นของ Postgresql คลิกที่ FindByAge:

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

สิ่งที่น่าสนใจคือ โปรแกรมแสดงให้เห็นว่า โดยหลักการแล้ว มีแหล่งที่มาหลักสามแหล่งที่ทำให้เกิดความล่าช้า ได้แก่ การเปิดและปิดการเชื่อมต่อ การขอข้อมูล และการเชื่อมต่อกับฐานข้อมูล กราฟแสดงให้เห็นว่าคำขอ DNS การเปิดและปิดการเชื่อมต่อใช้เวลาประมาณ 13% ของเวลาดำเนินการทั้งหมด

สมมติฐาน: การใช้การเชื่อมต่อซ้ำโดยใช้การรวมกลุ่มควรลดเวลาของคำขอ HTTP เดียว ทำให้มีปริมาณงานสูงขึ้นและมีเวลาแฝงน้อยลง.

การตั้งค่าแอปพลิเคชัน-การทดลอง

เราอัปเดตซอร์สโค้ด พยายามลบการเชื่อมต่อกับ Postgresql สำหรับแต่ละคำขอ ตัวเลือกแรกคือการใช้ พูลการเชื่อมต่อ ในระดับแอปพลิเคชัน ในการทดลองนี้เรา มาตั้งค่ากัน การรวมการเชื่อมต่อโดยใช้ไดรเวอร์ sql เพื่อไป:

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)

if err != nil {
   return nil, err
}

การดำเนินการ การสังเกต การวิเคราะห์

หลังจากเริ่มการทดสอบใหม่ด้วย 1000 คำขอต่อวินาที เป็นที่ชัดเจนว่าระดับความหน่วงของ p99 กลับมาเป็นปกติด้วย SLO ที่ 60ms!

ค่าใช้จ่ายเท่าไหร่?

10000 คำขอต่อวินาที / 1000 คำขอต่อเซิร์ฟเวอร์ = 10 เซิร์ฟเวอร์ + 1

มาทำกันดีกว่า!

2000 คำขอต่อวินาที

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

การเพิ่มโหลดเป็นสองเท่าแสดงให้เห็นสิ่งเดียวกัน กราฟด้านซ้ายบนแสดงให้เห็นว่าแอปพลิเคชันจัดการเพื่อประมวลผลคำขอ 2000 รายการต่อวินาที p100 ต่ำกว่า 60ms p99 ตอบสนอง SLO

ในแง่ของต้นทุน:

10000 คำขอต่อวินาที / 2000 คำขอต่อเซิร์ฟเวอร์ = 5 เซิร์ฟเวอร์ + 1

3000 คำขอต่อวินาที

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

ที่นี่แอปพลิเคชันสามารถประมวลผลคำขอได้ 3000 รายการโดยมีเวลาแฝง p99 น้อยกว่า 60ms SLO ไม่ถูกละเมิด และยอมรับต้นทุนดังนี้:

10000 คำขอต่อวินาที / ต่อ 3000 คำขอต่อเซิร์ฟเวอร์ = 4 เซิร์ฟเวอร์ + 1 (ผู้เขียนปัดเศษขึ้นแล้ว ประมาณ นักแปล)

ลองวิเคราะห์อีกรอบ

การวิเคราะห์-สมมติฐาน

เรารวบรวมและแสดงผลลัพธ์ของการดีบักแอปพลิเคชันที่ 3000 คำขอต่อวินาที:

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

ยังคงใช้เวลา 6% ในการสร้างการเชื่อมต่อ การตั้งค่าพูลได้รับการปรับปรุงประสิทธิภาพ แต่คุณยังสามารถเห็นว่าแอปพลิเคชันยังคงทำงานในการสร้างการเชื่อมต่อใหม่ไปยังฐานข้อมูล

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

การตั้งค่าแอปพลิเคชัน-การทดลอง

กำลังพยายามติดตั้ง MaxIdleConns เท่ากับขนาดสระ (อธิบายด้วย ที่นี่):

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
db.SetMaxIdleConns(8)
if err != nil {
   return nil, err
}

การดำเนินการ การสังเกต การวิเคราะห์

3000 คำขอต่อวินาที

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

p99 น้อยกว่า 60ms โดยมี p100 น้อยกว่ามาก!

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

การตรวจสอบกราฟเปลวไฟแสดงว่าการเชื่อมต่อไม่สังเกตเห็นได้อีกต่อไป! มาตรวจสอบรายละเอียดเพิ่มเติมกันดีกว่า pg(*conn).query — เราไม่สังเกตเห็นว่ามีการเชื่อมต่อเกิดขึ้นที่นี่

SRE: การวิเคราะห์ประสิทธิภาพ วิธีการกำหนดค่าโดยใช้เว็บเซิร์ฟเวอร์อย่างง่ายใน Go

ข้อสรุป

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

ที่มา: will.com

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