SuperOleg39 / ssr-perf-test

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Тестирование производительности SSR

Концепция

Репозиторий содержит код для сравнения производительности серверного рендеринга при наличии одной тяжелой синхронной задачи, или выполнения той же работы с помощью множества небольших синхронных задач.

В качестве эталона синхронной задачи используется метод ReactDOMServer.renderToString.

Тяжелая задача - множество последовательных вызовов renderToString, декомпозиция этой задачи на маленькие таски реализована с помощью вложенных вызовов setImmediate.

Цель сравнения - проверка теории, что NodeJS сервер лучше справляется с нагрузкой, если не выполняет тяжелых синхронных задач.

Метод исследования

Производительность проверяется с помощью autocannon. autocannon позволяет увидеть RPS и время ответа от сервера на разных перцентилях.

Время выполнения одной тяжелой задачи === времени выполнения пачки мелких задач.

Проверка происходит и с небольшими нагрузками, и с огромным количеством запросов.

Первое исследование - исключительно сравнение одной тяжелой и множества декомпозированных задач.

Второе исследование - добавляется эмуляция запросов к API до вызова renderToString.

Ход исследования

Одна длинная таска (long task) или множество коротких задач (small tasks) - практически не влияет на RPS, и соответственно не значительно влияет на среднее (average) время ответа от сервера (latency). При этом, значительно отличаются latency на разных перцентилях.

Первое исследование

Исследование без эмуляции запросов к API, исключительно синхронные задачи.

Long task

Long task сервер отдает быстро ответы на первые запросы, при этом все последующие буквально встают в очередь, и максимальное latency будет у клиента, который отправил последний запрос. Это интуитивно понятное поведение.

Бенчмарк

Команда для эмуляции большой нагрузки:

autocannon -c 50 -d 10 http://localhost:3000/ -l -t 1000 --amount 200

Running 200 requests test @ http://localhost:3000/
50 connections

┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬────────────┬─────────┐
│ Stat    │ 2.5%   │ 50%     │ 97.5%   │ 99%     │ Avg        │ Stdev      │ Max     │
├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼────────────┼─────────┤
│ Latency │ 459 ms │ 4821 ms │ 4933 ms │ 4982 ms │ 4304.48 ms │ 1356.04 ms │ 9741 ms │
└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴────────────┴─────────┘
┌───────────┬───────┬───────┬─────────┬─────────┬─────────┬────────┬───────┐
│ Stat      │ 1%    │ 2.5%  │ 50%     │ 97.5%   │ Avg     │ Stdev  │ Min   │
├───────────┼───────┼───────┼─────────┼─────────┼─────────┼────────┼───────┤
│ Req/Sec   │ 8     │ 8     │ 10      │ 11      │ 10      │ 0.64   │ 8     │
├───────────┼───────┼───────┼─────────┼─────────┼─────────┼────────┼───────┤
│ Bytes/Sec │ 976 B │ 976 B │ 1.22 kB │ 1.34 kB │ 1.22 kB │ 77.2 B │ 976 B │
└───────────┴───────┴───────┴─────────┴─────────┴─────────┴────────┴───────┘

Req/Bytes counts sampled once per second.

┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001      │ 147          │
├────────────┼──────────────┤
│ 0.01       │ 147          │
├────────────┼──────────────┤
│ 0.1        │ 147          │
├────────────┼──────────────┤
│ 1          │ 199          │
├────────────┼──────────────┤
│ 2.5        │ 459          │
├────────────┼──────────────┤
│ 10         │ 1946         │
├────────────┼──────────────┤
│ 25         │ 4789         │
├────────────┼──────────────┤
│ 50         │ 4821         │
├────────────┼──────────────┤
│ 75         │ 4889         │
├────────────┼──────────────┤
│ 90         │ 4901         │
├────────────┼──────────────┤
│ 97.5       │ 4933         │
├────────────┼──────────────┤
│ 99         │ 4982         │
├────────────┼──────────────┤
│ 99.9       │ 9741         │
├────────────┼──────────────┤
│ 99.99      │ 9741         │
├────────────┼──────────────┤
│ 99.999     │ 9741         │
└────────────┴──────────────┘

200 requests in 20.04s, 24.4 kB read

Small tasks

Small tasks сервер выполняет все полученные одновременно запросы параллельно. Это приводит к такому контринтуитивному поведению, что все пользователи получают ответы с большой задержкой, примерно равной average latency, даже если первые из этих пользователей сделали запрос на сервер в самом начале всплеска нагрузки.

Бенчмарк

Команда для эмуляции большой нагрузки:

autocannon -c 50 -d 10 http://localhost:4000/ -l -t 1000 --amount 200

Running 200 requests test @ http://localhost:4000/
50 connections

┌─────────┬─────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐
│ Stat    │ 2.5%    │ 50%     │ 97.5%   │ 99%     │ Avg        │ Stdev     │ Max     │
├─────────┼─────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤
│ Latency │ 4848 ms │ 4854 ms │ 5193 ms │ 5205 ms │ 4916.98 ms │ 114.35 ms │ 5213 ms │
└─────────┴─────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘
┌───────────┬─────┬──────┬─────┬────────┬─────────┬─────────┬───────┐
│ Stat      │ 1%  │ 2.5% │ 50% │ 97.5%  │ Avg     │ Stdev   │ Min   │
├───────────┼─────┼──────┼─────┼────────┼─────────┼─────────┼───────┤
│ Req/Sec   │ 0   │ 0    │ 0   │ 50     │ 10      │ 18.51   │ 2     │
├───────────┼─────┼──────┼─────┼────────┼─────────┼─────────┼───────┤
│ Bytes/Sec │ 0 B │ 0 B  │ 0 B │ 6.1 kB │ 1.22 kB │ 2.26 kB │ 244 B │
└───────────┴─────┴──────┴─────┴────────┴─────────┴─────────┴───────┘

Req/Bytes counts sampled once per second.

┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001      │ 4813         │
├────────────┼──────────────┤
│ 0.01       │ 4813         │
├────────────┼──────────────┤
│ 0.1        │ 4813         │
├────────────┼──────────────┤
│ 1          │ 4848         │
├────────────┼──────────────┤
│ 2.5        │ 4848         │
├────────────┼──────────────┤
│ 10         │ 4849         │
├────────────┼──────────────┤
│ 25         │ 4851         │
├────────────┼──────────────┤
│ 50         │ 4854         │
├────────────┼──────────────┤
│ 75         │ 4865         │
├────────────┼──────────────┤
│ 90         │ 5127         │
├────────────┼──────────────┤
│ 97.5       │ 5193         │
├────────────┼──────────────┤
│ 99         │ 5205         │
├────────────┼──────────────┤
│ 99.9       │ 5213         │
├────────────┼──────────────┤
│ 99.99      │ 5213         │
├────────────┼──────────────┤
│ 99.999     │ 5213         │
└────────────┴──────────────┘

200 requests in 20.04s, 24.4 kB read

Второе исследование

В этом исследовании добавлена эмуляция запросов к API. API отвечает медленно на каждый второй запрос.

Long task

Запросы так же встают в очередь, в подробной latency по перцентилям ярко видно, что чем позже пришел запрос, тем дольше будет ждать пользователь.

Бенчмарк

Команда для эмуляции большой нагрузки:

autocannon -c 50 -d 10 http://localhost:3000/ -l -t 1000 --amount 200

Running 200 requests test @ http://localhost:3000/
50 connections

┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬────────────┬─────────┐
│ Stat    │ 2.5%   │ 50%     │ 97.5%   │ 99%     │ Avg        │ Stdev      │ Max     │
├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼────────────┼─────────┤
│ Latency │ 797 ms │ 4882 ms │ 7923 ms │ 8026 ms │ 4856.98 ms │ 1850.24 ms │ 8096 ms │
└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴────────────┴─────────┘
┌───────────┬───────┬───────┬────────┬─────────┬─────────┬───────┬───────┐
│ Stat      │ 1%    │ 2.5%  │ 50%    │ 97.5%   │ Avg     │ Stdev │ Min   │
├───────────┼───────┼───────┼────────┼─────────┼─────────┼───────┼───────┤
│ Req/Sec   │ 1     │ 1     │ 9      │ 10      │ 8.7     │ 1.88  │ 1     │
├───────────┼───────┼───────┼────────┼─────────┼─────────┼───────┼───────┤
│ Bytes/Sec │ 122 B │ 122 B │ 1.1 kB │ 1.22 kB │ 1.06 kB │ 229 B │ 122 B │
└───────────┴───────┴───────┴────────┴─────────┴─────────┴───────┴───────┘

Req/Bytes counts sampled once per second.

┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001      │ 356          │
├────────────┼──────────────┤
│ 0.01       │ 356          │
├────────────┼──────────────┤
│ 0.1        │ 356          │
├────────────┼──────────────┤
│ 1          │ 466          │
├────────────┼──────────────┤
│ 2.5        │ 797          │
├────────────┼──────────────┤
│ 10         │ 2406         │
├────────────┼──────────────┤
│ 25         │ 3558         │
├────────────┼──────────────┤
│ 50         │ 4882         │
├────────────┼──────────────┤
│ 75         │ 6308         │
├────────────┼──────────────┤
│ 90         │ 7382         │
├────────────┼──────────────┤
│ 97.5       │ 7923         │
├────────────┼──────────────┤
│ 99         │ 8026         │
├────────────┼──────────────┤
│ 99.9       │ 8096         │
├────────────┼──────────────┤
│ 99.99      │ 8096         │
├────────────┼──────────────┤
│ 99.999     │ 8096         │
└────────────┴──────────────┘

200 requests in 23.04s, 24.4 kB read

Small tasks

Похожая картина как и без эмуляции API, но на подробной таблице latency по перцентилям видно, что первые пользователи начали получать ответы все-таки быстрее, чем последующие. В первом бенчмарке на каждом перцентиле были близкие latency.

В этом бенчмарке (и в других, если ставить небольшую нагрузку), заметно что на 75 и 90 перцентилях small tasks сервер отвечает пользователям заметно быстрее.

Бенчмарк

Команда для эмуляции большой нагрузки:

autocannon -c 50 -d 10 http://localhost:4000/ -l -t 1000 --amount 200

Running 200 requests test @ http://localhost:4000/
50 connections

┌─────────┬─────────┬─────────┬─────────┬─────────┬───────────┬───────────┬─────────┐
│ Stat    │ 2.5%    │ 50%     │ 97.5%   │ 99%     │ Avg       │ Stdev     │ Max     │
├─────────┼─────────┼─────────┼─────────┼─────────┼───────────┼───────────┼─────────┤
│ Latency │ 4016 ms │ 4641 ms │ 5561 ms │ 5575 ms │ 4847.4 ms │ 553.43 ms │ 5579 ms │
└─────────┴─────────┴─────────┴─────────┴─────────┴───────────┴───────────┴─────────┘
┌───────────┬─────┬──────┬───────┬─────────┬─────────┬─────────┬───────┐
│ Stat      │ 1%  │ 2.5% │ 50%   │ 97.5%   │ Avg     │ Stdev   │ Min   │
├───────────┼─────┼──────┼───────┼─────────┼─────────┼─────────┼───────┤
│ Req/Sec   │ 0   │ 0    │ 7     │ 25      │ 9.53    │ 8.8     │ 4     │
├───────────┼─────┼──────┼───────┼─────────┼─────────┼─────────┼───────┤
│ Bytes/Sec │ 0 B │ 0 B  │ 854 B │ 3.05 kB │ 1.16 kB │ 1.07 kB │ 488 B │
└───────────┴─────┴──────┴───────┴─────────┴─────────┴─────────┴───────┘

Req/Bytes counts sampled once per second.

┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001      │ 3828         │
├────────────┼──────────────┤
│ 0.01       │ 3828         │
├────────────┼──────────────┤
│ 0.1        │ 3828         │
├────────────┼──────────────┤
│ 1          │ 3832         │
├────────────┼──────────────┤
│ 2.5        │ 4016         │
├────────────┼──────────────┤
│ 10         │ 4160         │
├────────────┼──────────────┤
│ 25         │ 4386         │
├────────────┼──────────────┤
│ 50         │ 4641         │
├────────────┼──────────────┤
│ 75         │ 5460         │
├────────────┼──────────────┤
│ 90         │ 5533         │
├────────────┼──────────────┤
│ 97.5       │ 5561         │
├────────────┼──────────────┤
│ 99         │ 5575         │
├────────────┼──────────────┤
│ 99.9       │ 5579         │
├────────────┼──────────────┤
│ 99.99      │ 5579         │
├────────────┼──────────────┤
│ 99.999     │ 5579         │
└────────────┴──────────────┘

200 requests in 21.04s, 24.4 kB read

Итоги

Разделение одной тяжелой задачи на много мелких начинает хорошо себя показывать в тех случаях, когда на каждый запрос сервер делает много различных не детерменированных по времени синхронных и асинхронных действий.

У сервера появляется возможность намного быстрее ответить на те запросы, которые делают минимум запросов к API, или вообще сразу отдают 404 страницу или редирект.

Но если большая часть запросов вызывает одинаковые синхронные действия, и одинаковые по длительности запросы к API, при больших нагрузках все пользователи получат одинаково плохое время ответа, что вряд ли является ожидаемым поведением - иногда таймауты ответа к серверу у небольшого количества пользователей могут быть более удачным вариантом.

About


Languages

Language:JavaScript 100.0%