คุณได้นำ Endpoint ไปใช้งานแล้ว และมันทำงานได้ดีในเบราว์เซอร์ของคุณ แต่คุณไม่มีทางรู้เลยว่าจะเกิดอะไรขึ้นเมื่อมีคน 400 คนเข้าใช้งานพร้อมกัน ค่า Latency จะคงที่ หรือว่าค่าเปอร์เซ็นไทล์ที่ 99 จะพุ่งสูงขึ้นจนควบคุมไม่ได้? เซิร์ฟเวอร์จะรองรับได้ 1,000 requests ต่อวินาที หรือจะล่มที่ 300 requests?
wrk คือคำตอบสำหรับคำถามเหล่านี้ มันเป็นเครื่องมือ command-line ขนาดเล็กที่ส่งทราฟฟิก HTTP จำนวนมากไปยัง URL และรายงานว่าเซิร์ฟเวอร์ตอบสนองได้เร็วแค่ไหนภายใต้โหลดนั้น
wrk คืออะไร และควรใช้เมื่อใด
wrk เป็นเครื่องมือสำหรับทดสอบประสิทธิภาพ HTTP ที่ทันสมัย มันสร้างโหลดจากเครื่อง Multi-core เพียงเครื่องเดียว และวัดค่า Latency และอัตรา Request ที่เซิร์ฟเวอร์ตอบกลับมา มันใช้ Multithreading พร้อมกับ Scalable Event Loop (epoll บน Linux, kqueue บน macOS) ดังนั้น Instance เดียวจึงสามารถสร้างทราฟฟิกจำนวนมากได้โดยไม่จำเป็นต้องใช้ Load Box หลายเครื่อง
เลือกใช้ wrk เมื่อคุณต้องการตัวเลขประสิทธิภาพดิบๆ:
- Endpoint นี้สามารถรองรับ Requests ต่อวินาทีได้เท่าไหร่?
- ค่า Latency ดูเป็นอย่างไรเมื่อเทียบระหว่าง Median กับ Tail?
- เซิร์ฟเวอร์ยังคงทำงานได้ดีภายใต้โหลดที่ต่อเนื่อง หรือประสิทธิภาพลดลงหลังจากผ่านไปหนึ่งนาที?
wrk เป็นเครื่องมือสำหรับการทดสอบประสิทธิภาพ (benchmarking tool) ไม่ใช่ชุดทดสอบ (test suite) มันวัดความเร็ว ไม่ได้ตรวจสอบว่า Body ของ JSON ถูกต้องหรือไม่, Status Code เป็น 200 หรือไม่ หรือ API ทำงานตามสัญญาหรือไม่ โปรดจำความแตกต่างนี้ไว้ เราจะกลับมาพูดถึงเรื่องนี้อีกครั้งในช่วงท้าย เพราะมันจะเปลี่ยนวิธีการนำ wrk ไปใช้ใน Workflow การทดสอบจริง หากคุณต้องการภาพรวมก่อน คู่มือการทดสอบโหลด API นี้จะครอบคลุมแนวคิดที่ wrk นำมาใช้งานจริง
การติดตั้ง wrk
macOS
Homebrew มีไบนารีที่สร้างไว้ล่วงหน้า ซึ่งเป็นวิธีที่ง่ายที่สุด:
brew install wrk
บน Apple Silicon นี่เป็นสิ่งสำคัญ การสร้างจาก Source Code อาจพบปัญหา LuaJIT ARM64 ได้ ดังนั้นการใช้ไบนารีของ Homebrew จะช่วยลดความยุ่งยากไปได้Linux (สร้างจาก Source Code)
ไม่มีแพ็คเกจ apt อย่างเป็นทางการ คุณจึงต้องสร้างเอง ติดตั้ง Toolchain และ OpenSSL Headers ก่อน:
sudo apt-get install build-essential libssl-dev git -y
จากนั้นโคลนและคอมไพล์:
git clone https://github.com/wg/wrk.git
cd wrk
make
สิ่งนี้จะสร้างไบนารี wrk ในไดเรกทอรีปัจจุบัน ย้ายไปที่ PATH ของคุณเพื่อให้สามารถเรียกใช้ได้จากทุกที่:sudo cp wrk /usr/local/bin
ยืนยันว่าทำงานได้:
wrk --version
คำสั่งพื้นฐาน
นี่คือรูปแบบการรัน wrk ทุกครั้ง:
wrk -t12 -c400 -d30s http://127.0.0.1:8080/index.html
มีสี่สิ่งเกิดขึ้น มาดูกันที่ Flag ต่างๆ
-t, --threadsกำหนดจำนวนเธรดของ OS ที่ wrk สร้างขึ้น จุดเริ่มต้นที่ดีคือหนึ่งเธรดต่อหนึ่ง Core ของ CPU ในตัวอย่างนี้คือ 12-c, --connectionsกำหนดจำนวนการเชื่อมต่อ HTTP ทั้งหมดที่เปิดค้างไว้ในทุกเธรด ในตัวอย่างนี้คือ 400 การเชื่อมต่อกระจายไปใน 12 เธรด การเชื่อมต่อคือวิธีที่คุณจำลองไคลเอนต์ที่เชื่อมต่อพร้อมกัน-d, --durationกำหนดระยะเวลาที่การทดสอบจะรัน ยอมรับค่าเช่น30s,2m, หรือ2hในตัวอย่างนี้คือ 30 วินาที
อีกสอง Flag ที่คุณจะใช้บ่อยๆ:
--latencyพิมพ์รายละเอียดการกระจาย Latency เป็นเปอร์เซ็นไทล์ เปิดใช้งานเกือบทุกครั้ง ค่าเฉลี่ยจะซ่อนค่า Tail Latency และ Tail มักเป็นสิ่งที่ผู้ใช้ได้รับผลกระทบมากที่สุด--timeoutบันทึก Request ว่าหมดเวลาหากไม่มีการตอบกลับภายในระยะเวลาที่คุณกำหนด เช่น--timeout 2sหากไม่มีค่านี้ การตอบกลับที่ช้าอาจทำให้ตัวเลข Latency ของคุณคลาดเคลื่อนได้
การรันที่คุณจะใช้บ่อยๆ มีลักษณะดังนี้:
wrk -t8 -c200 -d30s --latency http://localhost:3000/api/users
แปดเธรด, 200 การเชื่อมต่อ, 30 วินาที, พร้อมกับการแสดงผลการกระจาย Latency เต็มรูปแบบในตอนท้าย
การอ่านผลลัพธ์
wrk จะพิมพ์รายงานสรุปออกมา นี่คือตัวอย่างการรันจริงกับบริการขนาดเล็ก:
Running 5s test @ http://10.135.232.163:3000
2 threads and 5 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 3.82ms 2.64ms 26.68ms 85.81%
Req/Sec 550.90 202.40 0.98k 68.00%
5494 requests in 5.01s, 1.05MB read
Requests/sec: 1096.54
Transfer/sec: 215.24KB
อ่านจากล่างขึ้นบน เพราะสองบรรทัดสุดท้ายคือหัวข้อหลัก
Requests/sec คือ Throughput: จำนวน Requests ที่เซิร์ฟเวอร์ทำเสร็จต่อวินาทีโดยเฉลี่ย ในที่นี้คือ 1,096 นี่คือตัวเลขที่คุณจะใช้เปรียบเทียบระหว่างการรันและระหว่างการเปลี่ยนแปลงโค้ด
Transfer/sec คือ Bandwidth: จำนวนข้อมูลที่ถ่ายโอนต่อวินาที มีประโยชน์เมื่อ Payload มีขนาดใหญ่ หรือเมื่อคุณสงสัยว่าติดขัดที่ Bandwidth ไม่ใช่ CPU
มาดูตาราง Thread Stats ซึ่งอธิบายการกระจายตัว ไม่ใช่แค่ค่าเฉลี่ย:
- แถว Latency: ค่าเฉลี่ย Latency คือ 3.82ms แต่ค่า Standard Deviation คือ 2.64ms และค่าสูงสุดพุ่งไปที่ 26.68ms ค่าสูงสุดที่สูงเมื่อเทียบกับค่าเฉลี่ยเป็นสัญญาณบางอย่าง Requests บางรายการช้าแม้ว่าส่วนใหญ่จะเร็วก็ตาม
- แถว Req/Sec: อัตรา Request ต่อเธรด พร้อมกับการกระจายตัวของมัน
คอลัมน์ +/- Stdev บอกคุณว่าเปอร์เซ็นต์ของตัวอย่างที่อยู่ในช่วง Standard Deviation หนึ่งๆ เป็นเท่าใด เปอร์เซ็นต์ที่ต่ำลงหมายถึงการกระจายตัวที่กว้างขึ้นและคาดเดาได้น้อยลง
บรรทัด 5494 requests in 5.01s ยืนยันปริมาณรวมที่การรันได้ส่งไปจริง
เมื่อคุณเพิ่ม --latency, wrk จะพิมพ์บล็อก Percentile เพื่อให้คุณเห็น Tail ได้โดยตรง:
Latency Distribution
50% 3.21ms
75% 4.86ms
90% 7.09ms
99% 14.13ms
เปอร์เซ็นไทล์ที่ 99 เป็นตัวเลขที่ต้องจับตาดู หาก 99% ของ Requests เสร็จสิ้นภายใน 14ms แต่ค่าเฉลี่ยของคุณคือ 3.82ms นั่นหมายความว่าผู้ใช้หนึ่งในร้อยกำลังรอนานกว่าที่ค่าเฉลี่ยแนะนำมาก ค่าเฉลี่ยอาจบิดเบือนข้อมูลเกี่ยวกับ Tail ได้ แต่เปอร์เซ็นไทล์ไม่บิดเบือน
การส่ง POST Requests และ Custom Headers ด้วย Lua Script
โดยค่าเริ่มต้น wrk จะส่ง GET Requests หากต้องการส่ง POST, เพิ่ม Body, หรือตั้งค่า Custom Headers คุณต้องส่ง Lua Script ด้วย -s
สร้างไฟล์ชื่อ post.lua:
wrk.method = "POST"
wrk.body = '{"name": "Ada", "role": "engineer"}'
wrk.headers["Content-Type"] = "application/json"
สามฟิลด์นี้ทำหน้าที่ wrk.method กำหนด HTTP Verb wrk.body กำหนด Request Body wrk.headers คือตารางที่ Key แต่ละตัวเป็นชื่อ Header
รันโดยชี้ -s ไปยัง Script:
wrk -t4 -c100 -d30s -s post.lua --latency http://localhost:3000/api/users
สำหรับการส่ง POST แบบ Form-Encoded แทน JSON, wrk repo มีตัวอย่างที่ตรงกันดังนี้:
wrk.method = "POST"
wrk.body = "foo=bar&baz=quux"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
คุณยังสามารถตั้งค่า Headers ด้วย Flag -H สำหรับกรณีที่ง่ายกว่า โดยไม่ต้องใช้ Script:
wrk -t4 -c100 -d30s -H "Authorization: Bearer TOKEN123" --latency http://localhost:3000/api/protected
ใช้ -H สำหรับหนึ่งหรือสอง Header ใช้ Lua Script เมื่อคุณต้องการ Body, Method ที่ไม่ใช่ GET, หรือ Logic ที่เฉพาะเจาะจงสำหรับแต่ละ Request
ข้อจำกัด: wrk ไม่ตรวจสอบความถูกต้อง
นี่คือส่วนที่คนมักพลาด wrk บอกคุณว่าเซิร์ฟเวอร์ตอบกลับเร็วแค่ไหน ไม่ได้บอกคุณว่าคำตอบนั้นถูกต้องหรือไม่
ลองชี้ wrk ไปยัง Endpoint ที่คืนค่า HTTP 500 สำหรับทุก Request คุณจะได้รายงานที่ดูดีพร้อมกับตัวเลข Requests-per-second ที่สูง wrk นับการแลกเปลี่ยน HTTP ที่เสร็จสมบูรณ์ ไม่ได้ยืนยัน Status Code, ตรวจสอบ Response Body กับ Schema, หรือยืนยันว่า API ทำงานตามที่ควรจะเป็น ข้อผิดพลาดก็อาจดูเหมือนเร็วได้ เพราะเซิร์ฟเวอร์ที่ปฏิเสธ Requests ตั้งแต่เนิ่นๆ จะทำงานน้อยลงต่อ Request
ดังนั้น wrk จึงตอบคำถามว่า "มันเร็วพอภายใต้โหลดหรือไม่?" แต่ไม่สามารถตอบคำถามว่า "มันถูกต้องหรือไม่?" ทั้งสองคำถามมีความสำคัญและต้องใช้เครื่องมือที่แตกต่างกัน ตัวเลขโหลดบน Endpoint ที่เสียคือตัวเลขที่คุณไม่ควรเชื่อ นี่คือเหตุผลว่าทำไมทีมจึงจับคู่เครื่องมือ Benchmarking เข้ากับ ชุดทดสอบ Functional อย่างแม่นยำ เครื่องมือหนึ่งพิสูจน์ความเร็ว อีกเครื่องมือหนึ่งพิสูจน์พฤติกรรม
Apidog และ Functional Testing เข้ากันได้ดีอย่างไร
Workflow ที่สะอาดคือสองชั้น ทำงานตามลำดับ
ขั้นแรก ตรวจสอบพฤติกรรม ก่อนที่คุณจะสนใจว่า Endpoint เร็วแค่ไหน ให้ยืนยันว่ามันถูกต้อง ใน Apidog คุณสร้างสถานการณ์ทดสอบที่ส่ง Request จริงและยืนยันสิ่งที่ส่งกลับมา: Status Code, ฟิลด์ JSON, Response Schema และ Business Logic คุณสามารถเชื่อมโยง Requests, ส่งข้อมูลระหว่างขั้นตอน และรันสถานการณ์เดียวกันในสภาพแวดล้อมต่างๆ นี่คือชั้นที่ตรวจจับข้อผิดพลาด 500 ที่ wrk ตรวจสอบประสิทธิภาพอย่างมีความสุข
จากนั้น ทดสอบประสิทธิภาพ (Benchmark Throughput) เมื่อพฤติกรรมได้รับการยืนยันแล้ว ให้รัน wrk กับ Endpoint เดียวกันเพื่อดูว่ามันรองรับ Concurrency และโหลดที่ต่อเนื่องได้อย่างไร Apidog ยังมี เครื่องมือทดสอบประสิทธิภาพในตัว หากคุณต้องการรวมงาน Functional และ Load ไว้ในที่เดียว แต่ wrk เป็นเครื่องมือเฉพาะที่ดีสำหรับการทดสอบประสิทธิภาพจาก Command-line โดยตรง
ชั้น Functional ทำงานใน CI ไม่ใช่แค่บนแล็ปท็อปของคุณ Apidog CLI เป็นแบบ Headless ดังนั้นจึงสามารถใส่ลงในขั้นตอน Pipeline ใดๆ ที่สามารถรัน Node ได้ ติดตั้งได้เลย:
npm install -g apidog-cli
จากนั้นรันสถานการณ์ทดสอบหรือ Suite ที่บันทึกไว้โดยใช้ ID:
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t <scenarioOrSuiteId> \
-e <environmentId> \
-r cli,html,junit
-t คือ Scenario, Folder, หรือ Suite ID ที่จะรัน -e คือ Environment ID -r เลือกรูปแบบรายงาน หนึ่งหรือหลายรูปแบบจาก cli, html, json, และ junit เอาต์พุต JUnit สามารถเชื่อมต่อกับระบบ CI ส่วนใหญ่เพื่อกำหนดเกณฑ์ Pass/Fail สำหรับการรันที่ขับเคลื่อนด้วยข้อมูล ให้เพิ่ม -d (หรือ --iteration-data) พร้อมกับ File Path หรือ Test-Data ID เพื่อทำซ้ำ Scenario เดิมกับ Input หลายรายการ
CLI รัน Apidog Scenarios และ Suites ที่บันทึกไว้ มันเป็นแบบ Headless ไม่ใช่ Interactive Request Sender และไม่ใช่ Load Generator มันคือประตูการตรวจสอบความถูกต้อง wrk คือมาตรวัดความเร็ว รันประตูการตรวจสอบความถูกต้องใน Pipeline ของคุณ (ดู คำแนะนำ CLI CI/CD นี้ หรือ คู่มือ GitHub Actions สำหรับการคัดลอกและวางการตั้งค่า) จากนั้นทดสอบประสิทธิภาพด้วย wrk เมื่อคุณต้องการตัวเลข Throughput การอ้างอิง CLI ฉบับเต็ม ครอบคลุม Flag ที่เหลือ
คำถามที่พบบ่อย
wrk กับ ab (ApacheBench) ต่างกันอย่างไร? ทั้งคู่ส่งโหลด HTTP และรายงาน Requests ต่อวินาที wrk เป็นแบบ Multithreaded และใช้ Event Loop ดังนั้นจึงสามารถสร้างโหลดได้มากขึ้นจากเครื่องเดียวและจัดการ Concurrency สูงได้ดีกว่า ab เป็นแบบ Single-threaded สำหรับโหลดที่หนักจากเครื่องเดียว wrk มักจะรองรับได้ดีกว่า ไม่มีตัวใดตัวหนึ่งที่ตรวจสอบความถูกต้องของ Response
ฉันควรใช้จำนวนเธรดและการเชื่อมต่อเท่าไหร่? เริ่มต้นด้วยหนึ่งเธรดต่อหนึ่ง Core ของ CPU และตั้งค่าการเชื่อมต่อให้เท่ากับระดับ Concurrency ที่คุณต้องการจำลอง หากคุณมี 8 Core และต้องการจำลองไคลเอนต์พร้อมกัน 200 ราย ลองใช้ -t8 -c200 สังเกตเครื่องไคลเอนต์ หาก wrk เองติดขัดที่ CPU ตัวเลขของคุณจะสะท้อนถึงขีดจำกัดของ Load Generator ไม่ใช่ของเซิร์ฟเวอร์ เพิ่มการเชื่อมต่อขึ้นเรื่อยๆ จนกว่า Throughput จะหยุดเพิ่มขึ้น
wrk สามารถทดสอบ HTTPS Endpoints ได้หรือไม่? ได้ ชี้ไปที่ URL ที่เป็น https:// และ wrk จะจัดการ TLS นั่นคือเหตุผลที่ Linux Build ต้องการ libssl-dev การจับมือ TLS เพิ่มต้นทุน CPU ทั้งสองฝั่ง ดังนั้นคาดหวังว่า Throughput ดิบจะต่ำกว่าเมื่อเทียบกับ HTTP ธรรมดา
wrk ตรวจสอบ Response Body หรือ Status Code หรือไม่? ไม่ wrk นับการแลกเปลี่ยน HTTP ที่เสร็จสมบูรณ์และวัดเวลา ไม่ได้ยืนยัน Status Code หรือ Body ดังนั้น Endpoint ที่คืนค่าข้อผิดพลาดก็ยังสามารถแสดงตัวเลข Requests-per-second ที่สูงได้ ใช้ชุดทดสอบ Functional เช่นที่รันผ่าน Apidog CLI เพื่อตรวจสอบความถูกต้อง จากนั้นใช้ wrk สำหรับ Throughput
ควรใช้เวลานานเท่าไรในการทดสอบโหลด? นานพอที่จะผ่านผลกระทบจากการ Warm-up เช่น Cache ที่ยังไม่ทำงานเต็มที่และการ Compile แบบ JIT ไม่กี่วินาทีก็เพียงพอสำหรับการตรวจสอบอย่างรวดเร็ว แต่ 30 วินาทีถึงสองสามนาทีจะให้ตัวเลขที่เสถียรมากขึ้นและแสดงการลดลงของประสิทธิภาพที่เกิดขึ้นภายใต้โหลดที่ต่อเนื่องเท่านั้น ใช้ -d30s เป็นค่าเริ่มต้นที่ดี และยืดเวลาออกไปเมื่อคุณกำลังไล่ล่าหาข้อผิดพลาดที่เกิดขึ้นช้าๆ
