Десь рік тому ми почали працювати над новою версією наших продуктів. Саме тоді ми почали випробовувати різні технології, архітектури, підходи, а головне це — міряти performance, бо без цього в highload проектах взагалі не вижити.
При проектуванні “любої” системи нам потрібно знати її ліміти:
- скільки паралельних запитів може обробити мікросервіс за допустиму latency?
- як багато запитів може витримати база даних, яку ми використовуємо?
- як довго потрібно чекати на Push повідомлення?
- як довго триває розподілена транзакція та між якими сервісами відбувається найбільша затримка?
І таких питань у нас було безліч. В процесі тестування ми використовували різний tooling: JMeter, ab, Gatling, але всі вони надавали дуже лімітовані можливості. Нам не вдавалося нормально покрити push flow (WebSockets/SSE), різні бази даних, було складно імітувати різний workloads (update/read).
На цій зустрічі я розповім про наш досвід застосування load testing:
- що використовуємо для тестування баз даних, мікросервісів;
- як готуємо Pull/Push тести та як адаптуємо тести під різні протоколи (HTTP/WebSockets/SSE);
- які виникають проблеми з замірами latency.
Моя доповідь дуже практична, тому після неї ви зможете з легкістю почати застосовувати load testing у себе на проекті.
8. • Document size ~15Kb
• 5K sport games available for betting
• 73K markets (categories of bets)
• 500K selections (positions to win an event)
• Up to 1M incoming events per minute consumed by one
process
• 10K outgoing events per second
• 10TB of streaming data per day
9. { update }
{ push }
{ pull }
Data Consistency Problem
10. { update }
{ push }
{ pull }
Data Propagation Problem
DB
11. Agenda
- The real need of load testing
- Load testing basics
- Current state of load testing on 2019
- NBomber – your drug to load testing
12. Do you know
how many
concurrent requests
your service
can handle?
13. The real need of Load Testing
- Choose technology stack
- Prove architecture design (POC)
- System regression (CI/CD pipeline)
- Explore system limits
14. Load Testing basics
- in many cases, you don’t need a cluster
- make sure that targeting node is fully loaded (100% CPU) to
get max RPS
- make sure that you have long running tests
- make sure that you have controllability (your test
input/output should be consistent)
17. Current state of Load Testing
Gatling (open-source, Scala, Akka):
- free + commercial version
- test as code
- very good for HTTP (has API for WebSockets, SSE)
- very nice reports
- has integration with Grafana, Jenkins
- has UI recorder to playback actions
- no back pressure
- not easy to customize
- no cluster in free version
18. class BasicSimulation extends Simulation {
val httpConf = http
.baseURL("http://computer-database.gatling.io")
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
.doNotTrackHeader("1")
.acceptLanguageHeader("en-US,en;q=0.5")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/
val scn = scenario("BasicSimulation")
.exec(
http("request_1").get("/"))
.pause(5)
setUp(
scn.inject(atOnceUsers(1))
).protocols(httpConf)
}
19. Current state of Load Testing
- Easy to cover HTTP only systems
- Hard to cover any other Pull/Push systems
- Complicated and not flexible API
- Limited cluster support (different
workloads)
20. Current state of Load Testing
- Easy to cover HTTP only systems
- Hard to cover any other Pull/Push systems
- Complicated and not flexible API
- Limited cluster support
21. Batch Update Process 1
Batch Update Process 3
MongoDB
Batch Update Process 2
insert
read
read
27. let start = getCurrentTimer()
execFunc()
let end = getCurrentTimer()
let latency = end - start
28. let start = getCurrentTimer()
sendHttpReqeust()
let end = getCurrentTimer()
let latency = end - start
29. let start = getCurrentTimer()
queryMongoDb()
let end = getCurrentTimer()
let latency = end - start
30. let start = getCurrentTimer()
publishToKafka()
let end = getCurrentTimer()
let latency = end - start
31. let start = getCurrentTimer()
let! response = websockets.ReceivePush()
let end = getCurrentTimer()
let latency = end - start
32. Load test any system
https://nbomber.com
PM> Install-Package NBomber
33. Why another {x} framework for load testing?
The main reasons are:
- To be technology agnostic as much as possible (no dependency on any protocol:
HTTP, WebSockets, SSE etc).
- To be able to test .NET implementation of specific driver. During testing, it was
identified many times that the performance could be slightly different because of
the virtual machine(.NET, Java, PHP, Js, Erlang, different settings for GC) or just
quality of drivers. For example there were cases that drivers written in C++ and
invoked from NodeJs app worked faster than drivers written in C#/.NET. Therefore,
it does make sense to load test your app using your concrete driver and runtime.
34. - Technology agnostic
(no dependency on HTTP, WebSockets, SSE)
- Very simple API
(to test Pull and Push scenarios)
- CI/CD integration
41. var scenario = ScenarioBuilder
.CreateScenario("Hello World from NBomber!", step)
.WithConcurrentCopies(10)
.WithDuration(TimeSpan.FromSeconds(10));
var step = Step.Create("simple step", async context => {
// you can do any logic here: go to http, websocket etc
await Task.Delay(TimeSpan.FromSeconds(0.1));
return Response.Ok();
});
NBomberRunner.RegisterScenarios(scenario)
.RunInConsole();
42. let step = Step.create("simple step", fun context -> task {
// you can do any logic here: go to http, websocket etc
do! Task.Delay(TimeSpan.FromSeconds(0.1))
return Response.Ok()
})
Scenario.create("Hello World from NBomber!", [step])
|> Scenario.withConcurrentCopies(10)
|> Scenario.withDuration(TimeSpan.FromSeconds(10.0))
|> NBomberRunner.registerScenario
|> NBomberRunner.runInConsole