การวิเคราะห์และปรับแต่งประสิทธิภาพเป็นเครื่องมือที่มีประสิทธิภาพในการตรวจสอบการปฏิบัติตามข้อกำหนดด้านประสิทธิภาพสำหรับลูกค้า
การวิเคราะห์ประสิทธิภาพสามารถใช้เพื่อตรวจสอบปัญหาคอขวดในโปรแกรมโดยการใช้วิธีการทางวิทยาศาสตร์เพื่อทดสอบการทดลองปรับแต่ง บทความนี้กำหนดแนวทางทั่วไปในการวิเคราะห์และปรับแต่งประสิทธิภาพ โดยใช้เว็บเซิร์ฟเวอร์ Go เป็นตัวอย่าง
Go นั้นดีเป็นพิเศษที่นี่เพราะมีเครื่องมือสร้างโปรไฟล์ pprof
ในห้องสมุดมาตรฐาน
กลยุทธ์
มาสร้างรายการสรุปสำหรับการวิเคราะห์โครงสร้างของเรากันดีกว่า เราจะพยายามใช้ข้อมูลบางอย่างในการตัดสินใจแทนการเปลี่ยนแปลงตามสัญชาตญาณหรือการคาดเดา โดยเราจะทำสิ่งนี้:
- เรากำหนดขอบเขตการปรับให้เหมาะสม (ข้อกำหนด)
- เราคำนวณปริมาณธุรกรรมสำหรับระบบ
- เราทำการทดสอบ (สร้างข้อมูล)
- เราสังเกต;
- เราวิเคราะห์ - ตรงตามข้อกำหนดทั้งหมดหรือไม่
- เราตั้งสมมติฐานขึ้นมาทางวิทยาศาสตร์
- เราทำการทดลองเพื่อทดสอบสมมติฐานนี้
สถาปัตยกรรมเซิร์ฟเวอร์ HTTP อย่างง่าย
สำหรับบทความนี้ เราจะใช้เซิร์ฟเวอร์ HTTP ขนาดเล็กใน Golang สามารถพบได้รหัสทั้งหมดจากบทความนี้
แอปพลิเคชันที่กำลังวิเคราะห์คือเซิร์ฟเวอร์ HTTP ที่สำรวจ Postgresql สำหรับแต่ละคำขอ นอกจากนี้ยังมี Prometheus, node_exporter และ Grafana สำหรับการรวบรวมและแสดงการวัดแอปพลิเคชันและระบบ
เพื่อให้ง่ายขึ้น เราจะพิจารณาว่าสำหรับการปรับขนาดแนวนอน (และทำให้การคำนวณง่ายขึ้น) แต่ละบริการและฐานข้อมูลจะถูกปรับใช้ร่วมกัน:
การกำหนดเป้าหมาย
ในขั้นตอนนี้ เราจะตัดสินใจเกี่ยวกับเป้าหมาย เรากำลังพยายามวิเคราะห์อะไร? เราจะรู้ได้อย่างไรว่าเมื่อถึงเวลาสิ้นสุด? ในบทความนี้ เราจะจินตนาการว่าเรามีลูกค้าและบริการของเราจะประมวลผลคำขอ 10 รายการต่อวินาที
В
- เวลาแฝง: 99% ของคำขอควรเสร็จสิ้นภายในเวลาน้อยกว่า 60 มิลลิวินาที
- ค่าใช้จ่าย: บริการควรใช้จำนวนเงินขั้นต่ำที่เราคิดว่าเป็นไปได้อย่างสมเหตุสมผล ในการทำเช่นนี้ เราเพิ่มปริมาณงานให้สูงสุด
- การวางแผนความจุ: ต้องทำความเข้าใจและจัดทำเอกสารว่าจะต้องเรียกใช้อินสแตนซ์จำนวนเท่าใด รวมถึงฟังก์ชันการปรับขนาดโดยรวม และจำนวนอินสแตนซ์ที่จำเป็นเพื่อให้เป็นไปตามข้อกำหนดในการโหลดเริ่มต้นและการจัดเตรียม
ความซ้ำซ้อน n+1 .
เวลาแฝงอาจต้องมีการปรับให้เหมาะสมนอกเหนือจากการวิเคราะห์ แต่จำเป็นต้องวิเคราะห์ปริมาณงานอย่างชัดเจน เมื่อใช้กระบวนการ SRE SLO คำขอล่าช้าจะมาจากลูกค้าหรือธุรกิจ ซึ่งแสดงโดยเจ้าของผลิตภัณฑ์ และบริการของเราจะปฏิบัติตามข้อผูกพันนี้ตั้งแต่เริ่มต้นโดยไม่มีการตั้งค่าใด ๆ !
การตั้งค่าสภาพแวดล้อมการทดสอบ
ด้วยความช่วยเหลือของสภาพแวดล้อมการทดสอบ เราจะสามารถวางโหลดที่วัดได้บนระบบของเรา สำหรับการวิเคราะห์ ข้อมูลเกี่ยวกับประสิทธิภาพของบริการบนเว็บจะถูกสร้างขึ้น
โหลดธุรกรรม
สภาพแวดล้อมนี้ใช้
$ 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 ไปถึงไหนเมื่อแอปพลิเคชันทำงานอยู่ ช่วยให้คุณระบุได้อย่างชัดเจนว่าจะใช้เวลาประมวลผลที่ไหนและเท่าไร:
ข้อมูลนี้สามารถนำมาใช้ในระหว่างการวิเคราะห์เพื่อรับข้อมูลเชิงลึกเกี่ยวกับเวลา CPU ที่เสียไปและงานที่ไม่จำเป็นที่กำลังดำเนินการอยู่ Go (pprof) สามารถสร้างโปรไฟล์และแสดงภาพเป็นกราฟเปลวไฟโดยใช้ชุดเครื่องมือมาตรฐาน ฉันจะพูดถึงการใช้งานและคู่มือการตั้งค่าในบทความต่อไป
การดำเนินการ การสังเกต การวิเคราะห์
มาทำการทดลองกัน เราจะดำเนินการ สังเกต และวิเคราะห์จนกว่าเราจะพอใจกับผลงาน ให้เราเลือกค่าโหลดที่ต่ำโดยพลการเพื่อนำไปใช้เพื่อให้ได้ผลลัพธ์ของการสังเกตครั้งแรก ในแต่ละขั้นตอนต่อมา เราจะเพิ่มภาระด้วยปัจจัยสเกลที่แน่นอน โดยเลือกด้วยการเปลี่ยนแปลงบางอย่าง การทดสอบโหลดแต่ละครั้งจะดำเนินการโดยปรับจำนวนคำขอ: make load-test LOAD_TEST_RATE=X
.
50 คำขอต่อวินาที
ให้ความสนใจกับกราฟสองตัวบนสุด ด้านซ้ายบนแสดงว่าแอปพลิเคชันของเราประมวลผลคำขอ 50 รายการต่อวินาที (คิดว่า) และด้านขวาบนแสดงระยะเวลาของแต่ละคำขอ พารามิเตอร์ทั้งสองช่วยให้เราดูและวิเคราะห์ว่าเราอยู่ในขอบเขตประสิทธิภาพของเราหรือไม่ เส้นสีแดงบนกราฟ เวลาแฝงของคำขอ HTTP แสดง SLO ที่ 60ms บรรทัดแสดงว่าเราอยู่ต่ำกว่าเวลาตอบสนองสูงสุดของเรามาก
ลองดูด้านต้นทุน:
10000 คำขอต่อวินาที / 50 คำขอต่อเซิร์ฟเวอร์ = 200 เซิร์ฟเวอร์ + 1
เรายังคงสามารถปรับปรุงตัวเลขนี้ได้
500 คำขอต่อวินาที
สิ่งที่น่าสนใจอื่นๆ เริ่มเกิดขึ้นเมื่อโหลดมีคำขอถึง 500 คำขอต่อวินาที:
อีกครั้งในกราฟด้านซ้ายบน คุณจะเห็นว่าแอปพลิเคชันกำลังบันทึกการโหลดปกติ หากไม่เป็นเช่นนั้น แสดงว่ามีปัญหาบนเซิร์ฟเวอร์ที่แอปพลิเคชันกำลังทำงานอยู่ กราฟเวลาในการตอบสนองจะอยู่ที่มุมขวาบน ซึ่งแสดงว่าคำขอ 500 รายการต่อวินาทีส่งผลให้เกิดความล่าช้าในการตอบสนอง 25-40 มิลลิวินาที เปอร์เซ็นไทล์ที่ 99 ยังคงเข้ากันได้ดีกับ SLO 60 มิลลิวินาทีที่เลือกไว้ด้านบน
ในแง่ของต้นทุน:
10000 คำขอต่อวินาที / 500 คำขอต่อเซิร์ฟเวอร์ = 20 เซิร์ฟเวอร์ + 1
ทุกอย่างยังสามารถปรับปรุงได้
1000 คำขอต่อวินาที
เปิดตัวยิ่งใหญ่! แอปพลิเคชันแสดงว่าประมวลผลคำขอ 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
กราฟแสดงตำแหน่งและจำนวนที่แอปพลิเคชันใช้เวลา CPU จากคำอธิบายจาก
แกน X คือประชากรโปรไฟล์สแต็ก เรียงตามตัวอักษร (นี่ไม่ใช่เวลา) แกน Y จะแสดงความลึกของสแต็ก โดยนับจากศูนย์ที่ [บนสุด] แต่ละสี่เหลี่ยมผืนผ้าเป็นกรอบสแต็ก ยิ่งเฟรมกว้างเท่าไรก็ยิ่งปรากฏอยู่ในสแต็กบ่อยขึ้นเท่านั้น สิ่งที่ทำงานอยู่ด้านบนสุดบน CPU และสิ่งที่อยู่ด้านล่างคือองค์ประกอบลูก สีต่างๆ มักจะไม่มีความหมายใดๆ แต่เป็นเพียงการเลือกแบบสุ่มเพื่อแยกแยะเฟรมต่างๆ
การวิเคราะห์-สมมติฐาน
สำหรับการปรับแต่ง เราจะมุ่งเน้นไปที่การพยายามค้นหาเวลา CPU ที่เสียไป เราจะค้นหาแหล่งการใช้จ่ายไร้ประโยชน์ที่ใหญ่ที่สุดและลบออก เนื่องจากการทำโปรไฟล์เผยให้เห็นอย่างแม่นยำว่าแอปพลิเคชันใช้เวลาประมวลผลอยู่ที่ใด คุณอาจต้องดำเนินการหลายครั้ง และคุณจะต้องเปลี่ยนซอร์สโค้ดของแอปพลิเคชัน รันการทดสอบอีกครั้ง และดูว่าประสิทธิภาพเข้าใกล้เป้าหมายหรือไม่
ตามคำแนะนำของ Brendan Gregg เราจะอ่านแผนภูมิจากบนลงล่าง แต่ละบรรทัดจะแสดงเฟรมสแต็ก (การเรียกใช้ฟังก์ชัน) บรรทัดแรกคือจุดเริ่มต้นเข้าสู่โปรแกรม ซึ่งเป็นพาเรนต์ของการเรียกอื่นๆ ทั้งหมด (หรืออีกนัยหนึ่ง การเรียกอื่นๆ ทั้งหมดจะมีมันอยู่บนสแต็ก) บรรทัดถัดไปแตกต่างไปแล้ว:
หากคุณวางเคอร์เซอร์ไว้เหนือชื่อของฟังก์ชันบนกราฟ เวลาทั้งหมดที่อยู่ในสแต็กระหว่างการแก้ไขข้อบกพร่องจะปรากฏขึ้น ฟังก์ชัน HTTPServe อยู่ที่นั่น 65% ของเวลา รวมถึงฟังก์ชันรันไทม์อื่นๆ runtime.mcall
, mstart
и gc
,ใช้เวลาที่เหลือ เรื่องน่าสนุก: 5% ของเวลาทั้งหมดถูกใช้ไปกับการสืบค้น DNS:
ที่อยู่ที่โปรแกรมค้นหาเป็นของ Postgresql คลิกที่ FindByAge
:
สิ่งที่น่าสนใจคือ โปรแกรมแสดงให้เห็นว่า โดยหลักการแล้ว มีแหล่งที่มาหลักสามแหล่งที่ทำให้เกิดความล่าช้า ได้แก่ การเปิดและปิดการเชื่อมต่อ การขอข้อมูล และการเชื่อมต่อกับฐานข้อมูล กราฟแสดงให้เห็นว่าคำขอ DNS การเปิดและปิดการเชื่อมต่อใช้เวลาประมาณ 13% ของเวลาดำเนินการทั้งหมด
สมมติฐาน: การใช้การเชื่อมต่อซ้ำโดยใช้การรวมกลุ่มควรลดเวลาของคำขอ HTTP เดียว ทำให้มีปริมาณงานสูงขึ้นและมีเวลาแฝงน้อยลง.
การตั้งค่าแอปพลิเคชัน-การทดลอง
เราอัปเดตซอร์สโค้ด พยายามลบการเชื่อมต่อกับ Postgresql สำหรับแต่ละคำขอ ตัวเลือกแรกคือการใช้
db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
if err != nil {
return nil, err
}
การดำเนินการ การสังเกต การวิเคราะห์
หลังจากเริ่มการทดสอบใหม่ด้วย 1000 คำขอต่อวินาที เป็นที่ชัดเจนว่าระดับความหน่วงของ p99 กลับมาเป็นปกติด้วย SLO ที่ 60ms!
ค่าใช้จ่ายเท่าไหร่?
10000 คำขอต่อวินาที / 1000 คำขอต่อเซิร์ฟเวอร์ = 10 เซิร์ฟเวอร์ + 1
มาทำกันดีกว่า!
2000 คำขอต่อวินาที
การเพิ่มโหลดเป็นสองเท่าแสดงให้เห็นสิ่งเดียวกัน กราฟด้านซ้ายบนแสดงให้เห็นว่าแอปพลิเคชันจัดการเพื่อประมวลผลคำขอ 2000 รายการต่อวินาที p100 ต่ำกว่า 60ms p99 ตอบสนอง SLO
ในแง่ของต้นทุน:
10000 คำขอต่อวินาที / 2000 คำขอต่อเซิร์ฟเวอร์ = 5 เซิร์ฟเวอร์ + 1
3000 คำขอต่อวินาที
ที่นี่แอปพลิเคชันสามารถประมวลผลคำขอได้ 3000 รายการโดยมีเวลาแฝง p99 น้อยกว่า 60ms SLO ไม่ถูกละเมิด และยอมรับต้นทุนดังนี้:
10000 คำขอต่อวินาที / ต่อ 3000 คำขอต่อเซิร์ฟเวอร์ = 4 เซิร์ฟเวอร์ + 1 (ผู้เขียนปัดเศษขึ้นแล้ว ประมาณ นักแปล)
ลองวิเคราะห์อีกรอบ
การวิเคราะห์-สมมติฐาน
เรารวบรวมและแสดงผลลัพธ์ของการดีบักแอปพลิเคชันที่ 3000 คำขอต่อวินาที:
ยังคงใช้เวลา 6% ในการสร้างการเชื่อมต่อ การตั้งค่าพูลได้รับการปรับปรุงประสิทธิภาพ แต่คุณยังสามารถเห็นว่าแอปพลิเคชันยังคงทำงานในการสร้างการเชื่อมต่อใหม่ไปยังฐานข้อมูล
สมมติฐาน: การเชื่อมต่อแม้ว่าจะมีพูลอยู่ แต่การเชื่อมต่อยังคงหลุดและถูกล้าง ดังนั้นแอปพลิเคชันจึงต้องรีเซ็ตการเชื่อมต่อ การตั้งค่าจำนวนการเชื่อมต่อที่ค้างอยู่เป็นขนาดพูลควรช่วยลดเวลาแฝงด้วยการลดเวลาที่แอปพลิเคชันใช้ในการสร้างการเชื่อมต่อ.
การตั้งค่าแอปพลิเคชัน-การทดลอง
กำลังพยายามติดตั้ง
db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
db.SetMaxIdleConns(8)
if err != nil {
return nil, err
}
การดำเนินการ การสังเกต การวิเคราะห์
3000 คำขอต่อวินาที
p99 น้อยกว่า 60ms โดยมี p100 น้อยกว่ามาก!
การตรวจสอบกราฟเปลวไฟแสดงว่าการเชื่อมต่อไม่สังเกตเห็นได้อีกต่อไป! มาตรวจสอบรายละเอียดเพิ่มเติมกันดีกว่า pg(*conn).query
— เราไม่สังเกตเห็นว่ามีการเชื่อมต่อเกิดขึ้นที่นี่
ข้อสรุป
การวิเคราะห์ประสิทธิภาพเป็นสิ่งสำคัญในการทำความเข้าใจว่าความคาดหวังของลูกค้าและข้อกำหนดที่ไม่เป็นไปตามหน้าที่นั้นได้รับการตอบสนอง การวิเคราะห์โดยการเปรียบเทียบข้อสังเกตกับความคาดหวังของลูกค้าสามารถช่วยระบุได้ว่าสิ่งใดเป็นที่ยอมรับและสิ่งที่ไม่ยอมรับ Go นำเสนอเครื่องมืออันทรงพลังที่สร้างไว้ในไลบรารีมาตรฐานที่ทำให้การวิเคราะห์ง่ายและเข้าถึงได้
ที่มา: will.com