Linkerd, наша сервисная сетка (service mesh) для облачных приложений, по долгу службы обязана на протяжении длительного времени справляться с большими объемами сетевого трафика. Перед выпуском очередного релиза соответствие этому требованию необходимо тщательно проверять. В этой статье мы опишем стратегии нагрузочного тестирования и использованные нами инструменты, а также рассмотрим несколько обнаруженных проблем. В итоге будет представлен slow_cooker — написанный на Go инструмент нагрузочного тестирования с открытым исходным кодом, который был создан для выполнения длительных нагрузочных тестов и выявления проблем жизненного цикла (lifecycle issue identification).


linkerd действует как прозрачный прокси. Он добавляет к предназначенным для определенных сервисов запросам использование пулов соединений, отработку отказов, повторные попытки, балансировку нагрузки с учетом задержек и многое другое. Чтобы быть жизнеспособной и пригодной для промышленной эксплуатации системой, linkerd должен уметь справляться с очень большим количеством запросов на протяжении длительных периодов времени в условиях меняющейся обстановки. К счастью, linkerd построен на основе netty и Finagle. Среди всех сетевых программ их код является одним из самых широко протестированных и проверенных в процессе промышленной эксплуатации. Но код — это одно, а реальная производительность — совсем другое.


Чтобы оценить поведение системы в условиях промышленной эксплуатации, linkerd должен быть подвергнут самому тщательному и всестороннему нагрузочному тестированию. Более того, поскольку linkerd является частью базовой инфраструктуры, его экземпляры редко останавливаются или перезапускаются, и каждый из них может пропустить через себя миллиарды запросов в условиях меняющегося поведения сервисов и их клиентов. Это значит, что мы также должны тестировать проблемы жизненного цикла (lifecycle issues). Для сетевых серверов с высокой пропускной способностью, каковым является и linkerd, проблемы жизненного цикла включают утечки памяти и сокетов, плохие GC-паузы, а также перегрузки сети и дисковой подсистемы. Такие вещи происходят нечасто, но если не научиться отрабатывать их должным образом, последствия могут быть катастрофическими.


Кто тестирует программы для тестирования?


На начальном этапе разработки linkerd мы использовали популярные инструменты нагрузочного тестирования ApacheBench и hey. (Конечно, они работают только с HTTP, а linkerd проксирует различные протоколы, включая Thrift, gRPC и Mux—but. Но нам нужно было с чего-то начинать.)


К сожалению, мы быстро осознали, что, несмотря на несомненную полезность этих инструментов для быстрого получения данных о производительности, они не очень хороши в определении проблем жизненного цикла, которые мы хотели научиться выявлять. Эти инструменты выдают общий итог по факту завершения теста, а при таком подходе проблемы можно не заметить. Более того, они полагаются на средние значения и стандартные отклонения, что является не самым лучшим, по нашему мнению, способом оценки производительности системы.


Чтобы выявить проблемы жизненного цикла, нам были нужны более качественные метрики и возможность видеть, как linkerd ведет себя во время длительных тестов, выполняющихся часы и дни, а не минуты.


Для получения нежного кода готовим не торопясь


Поскольку мы не смогли найти подходящий инструмент, пришлось сделать свой: slow_cooker. slow_cooker — программа для нагрузочного тестирования, разработанная специально для выполнения длительных нагрузочных тестов и выявления проблем жизненного цикла. Мы широко используем slow_cooker для поиска проблем с производительностью и тестирования изменений в наших продуктах. В slow_cooker есть пошаговые отчеты (incremental reports) о ходе процесса тестирования, обнаружение изменений (change detection) и все необходимые метрики.


Чтобы другие люди могли пользоваться slow_cooker и участвовать в разработке, мы сегодня открываем его исходный код. См. slow_cooker source на GitHub и недавно выпущенный релиз 1.0.


Давайте поговорим о возможностях, которые предоставляет slow_cooker.


(Для упрощения мы протестируем его непосредственно на самих веб-сервисах. На практике мы, конечно, используем slow_cooker в первую очередь для поиска проблем с linkerd, а не с сервисами, которые он обслуживает.)


Пошаговый отчет о задержках сети


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


В примере приведен вывод slow_cooker, полученный при нагрузочном тестировании linkerd. В нашем тестовом сценарии linkerd балансирует нагрузку между тремя серверами nginx, каждый из которых раздает статический контент. Задержки даны в миллисекундах, и мы выводим min, p50, p95, p99, p999 и max-задержки, зафиксированные на десятисекундных интервалах.


$ ./slow_cooker_linux_amd64 -url http://target:4140 -qps 50 -concurrency 10 http://perf-target-2:8080
# sending 500 req/s with concurrency=10 to http://perf-target-2:8080 ...
#                      good/b/f t     good%   min [p50 p95 p99  p999]  max change
2016-10-12T20:34:20Z   4990/0/0 5000  99% 10s   0 [  1   3   4    9 ]    9
2016-10-12T20:34:30Z   5020/0/0 5000 100% 10s   0 [  1   3   6   11 ]   11
2016-10-12T20:34:40Z   5020/0/0 5000 100% 10s   0 [  1   3   7   10 ]   10
2016-10-12T20:34:50Z   5020/0/0 5000 100% 10s   0 [  1   3   5    8 ]    8
2016-10-12T20:35:00Z   5020/0/0 5000 100% 10s   0 [  1   3   5    9 ]    9
2016-10-12T20:35:11Z   5020/0/0 5000 100% 10s   0 [  1   3   5   11 ]   11
2016-10-12T20:35:21Z   5020/0/0 5000 100% 10s   0 [  1   3   5    9 ]    9
2016-10-12T20:36:11Z   5020/0/0 5000 100% 10s   0 [  1   3   5    9 ]    9
2016-10-12T20:36:21Z   5020/0/0 5000 100% 10s   0 [  1   3   6    9 ]    9
2016-10-12T20:35:31Z   5019/0/0 5000 100% 10s   0 [  1   3   5    9 ]    9
2016-10-12T20:35:41Z   5020/0/0 5000 100% 10s   0 [  1   3   6   10 ]   10
2016-10-12T20:35:51Z   5020/0/0 5000 100% 10s   0 [  1   3   5    9 ]    9
2016-10-12T20:36:01Z   5020/0/0 5000 100% 10s   0 [  1   3   5   10 ]   10

В этом отчете в колонке good% показана пропускная способность: как близко мы подошли к требуемому количеству запросов в секунду (RPS, requests per second).


Этот отчет выглядит неплохо: система работает быстро и время отклика стабильное. При этом мы должны иметь возможность четко видеть, где и когда начались неприятности. Вывод slow_cooker был настроен таким образом, чтобы облегчить визуальный поиск проблем и выбросов при помощи вертикального выравнивания, а также индикатора произошедшего изменения. Давайте посмотрим пример, где у нас появился сильно тормозящий сервер:


$ ./slow_cooker_linux_amd64 -totalRequests 100000 -qps 5 -concurrency 100 http://perf-target-1:8080
# sending 500 req/s with concurrency=10 to http://perf-target-2:8080 ...
#                      good/b/f t     good%   min [p50 p95 p99  p999]  max change
2016-11-14T20:58:13Z   4900/0/0 5000  98% 10s   0 [  1   2   6    8 ]    8 +
2016-11-14T20:58:23Z   5026/0/0 5000 100% 10s   0 [  1   2   3    4 ]    4
2016-11-14T20:58:33Z   5017/0/0 5000 100% 10s   0 [  1   2   3    4 ]    4
2016-11-14T20:58:43Z   1709/0/0 5000  34% 10s   0 [  1 6987 6987 6987 ] 6985 +++
2016-11-14T20:58:53Z   5020/0/0 5000 100% 10s   0 [  1   2   2    3 ]    3 --
2016-11-14T20:59:03Z   5018/0/0 5000 100% 10s   0 [  1   2   2    3 ]    3 --
2016-11-14T20:59:13Z   5010/0/0 5000 100% 10s   0 [  1   2   2    3 ]    3 --
2016-11-14T20:59:23Z   4985/0/0 5000  99% 10s   0 [  1   2   2    3 ]    3 --
2016-11-14T20:59:33Z   5015/0/0 5000 100% 10s   0 [  1   2   3    4 ]    4 --
2016-11-14T20:59:43Z   5000/0/0 5000 100% 10s   0 [  1   2   3    5 ]    5
2016-11-14T20:59:53Z   5000/0/0 5000 100% 10s   0 [  1   2   2    3 ]    3
FROM    TO #REQUESTS
   0     2 49159
   2     8 4433
   8    32 8
  32    64 0
  64   128 0
 128   256 0
 256   512 0
 512  1024 0
1024  4096 0
4096 16384 100

Как видите, система работает быстро и отзывчиво, за исключением одной заминки в 2016-11-14T20:58:43Z, во время которой пропускная способность упала до 34%, а затем вернулась к норме. Будучи владельцем этого сервиса, вы наверняка захотите посмотреть логи или показатели производительности, чтобы выяснить причину инцидента.


Пример проблемы жизненного цикла: GC-пауза


Чтобы продемонстрировать преимущества пошаговых отчетов по сравнению с обычными, выводящими только итоговые данные, давайте смоделируем ситуацию, в которой на сервере запускается сборщик мусора. В этом примере мы будем напрямую тестировать единственный процесс nginx, раздающий статический контент. Для симуляции задержек, вызванных сборщиком мусора, мы будем приостанавливать и возобновлять работу nginx в цикле с пятисекундными интервалами (используя kill -STOP $PID и kill -CONT $pid).


Для сравнения давайте начнем с отчета от ApacheBench:


$ ab -n 100000 -c 10 http://perf-target-1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1604373 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking perf-target-1 (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests

Server Software:        nginx/1.9.12
Server Hostname:        perf-target-1
Server Port:            8080

Document Path:          /
Document Length:        612 bytes

Concurrency Level:      10
Time taken for tests:   15.776 seconds
Complete requests:      100000
Failed requests:        0
Total transferred:      84500000 bytes
HTML transferred:       61200000 bytes
Requests per second:    6338.89 [#/sec] (mean)
Time per request:       1.578 [ms] (mean)
Time per request:       0.158 [ms] (mean, across all concurrent requests)
Transfer rate:          5230.83 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       3
Processing:     0    1  64.3      0    5003
Waiting:        0    1  64.3      0    5003
Total:          0    2  64.3      1    5003

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      1
  95%      1
  98%      1
  99%      2
 100%   5003 (longest request)

Здесь мы видим задержку в 1,5 мс, при этом присутствует несколько выбросов с большими задержками. Такой отчет достаточно легко ошибочно посчитать нормальным даже несмотря на то, что тестируемый сервис не отвечал на запросы ровно половину затраченного на проверку времени. Если целевое значение SLA равно 1 секунде, то сервис превышал его в течение более чем половины прогона, но из отчета этого можно и не заметить!


С пошаговыми отчетами slow_cooker мы видим, что есть постоянно проявляющаяся проблема с пропускной способностью. Также здесь гораздо более очевидно, что P99,9 имеет стабильно высокие значения на всем протяжении теста:


$ ./slow_cooker_linux_amd64 -totalRequests 20000 -qps 50 -concurrency 10 http://perf-target-2:8080
# sending 500 req/s with concurrency=10 to http://perf-target-2:8080 ...
#                      good/b/f t    good%    min [p50 p95 p99  p999]  max change
2016-12-07T19:05:37Z   2510/0/0 5000  50% 10s   0 [  0   0   2 4995 ] 4994 +
2016-12-07T19:05:47Z   2520/0/0 5000  50% 10s   0 [  0   0   1 4999 ] 4997 +
2016-12-07T19:05:57Z   2519/0/0 5000  50% 10s   0 [  0   0   1 5003 ] 5000 +
2016-12-07T19:06:07Z   2521/0/0 5000  50% 10s   0 [  0   0   1 4983 ] 4983 +
2016-12-07T19:06:17Z   2520/0/0 5000  50% 10s   0 [  0   0   1 4987 ] 4986
2016-12-07T19:06:27Z   2520/0/0 5000  50% 10s   0 [  0   0   1 4991 ] 4988
2016-12-07T19:06:37Z   2520/0/0 5000  50% 10s   0 [  0   0   1 4995 ] 4992
2016-12-07T19:06:47Z   2520/0/0 5000  50% 10s   0 [  0   0   2 4995 ] 4994
FROM    TO #REQUESTS
   0     2 19996
   2     8 74
   8    32 0
  32    64 0
  64   128 0
 128   256 0
 256   512 0
 512  1024 0
1024  4096 0
4096 16384 80

Отчеты о задержках на основе перцентилей


Как видно из примера с ApacheBench, некоторые инструменты нагрузочного тестирования выводят лишь среднее значение и стандартное отклонение. Однако эти метрики обычно неуместны при оценке задержек, которые не подчиняются закону нормального распределения и часто имеют очень длинные хвосты.


В slow_cooker мы не используем среднее значение и стандартное отклонение, а вместо этого отображаем минимум, максимум и несколько перцентилей высокого порядка (P50, P95, P99 и P99,9). Этот подход все чаще применяется в современном программном обеспечении, где один запрос может породить десятки или даже сотни обращений к другим системам и сервисам. В таких ситуациях метрики, подобные 95-му и 99-му перцентилям, позволяют получить преобладающее значение задержки.


Заключение


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


В настоящий момент мы широко используем slow_cooker для тестирования linkerd и других сервисов (например, nginx). linkerd тестируется в режиме 24x7 в условиях взаимодействия с различными сервисами, и slow_cooker помог нам не только предотвратить развертывание кода с серьезными ошибками, но и найти проблемы с производительностью в уже работающих релизах. Использование slow_cooker в Buoyant стало настолько повсеместным, что мы начали называть нагрузочное тестирование программ «слоукукингом».


Работу со slow_cooker можно начать с посещения странички с релизами на Github. Загрузите инструмент и запустите тестирование своего любимого сервера, чтобы проверить, нет ли у него проблем с производительностью. При тестировании linkerd slow_cooker нам очень помог, и мы надеемся, что вы найдете его не менее полезным.


Материалы для дальнейшего чтения (на английском)


Поделиться с друзьями
-->

Комментарии (1)


  1. tolkkv
    05.01.2017 19:39

    Хм, на первый взгляд утилита vegeta работает похожим образом, но куда более приятна в обращении. Когда начинали делать slow_cooker она уже была? Интересно сравнение :)