Пользователь
12,0
рейтинг
20 августа 2015 в 17:35

Разработка → Миллиарды запросов в день встречают Go 1.5 перевод

Перевод статьи о том, как компания перевела свою инфраструктуру на Go 1.5 и уменьшила паузы сборщика мусора с ~279мс до ~10мс.

Маркетинг системы, ориентированные на клиентов, зависят от сбора и анализа как можно большего количества связанных событий. Клиенты буквально везде, и количество данных растет экспоненциально. Язык Go играет важную роль в нашей системе сбора данных. Сегодня FLXone обрабатывает 3+ миллиарда запросов в день написанным с нуля нашим приложением.

Наш путь для достижения такой производительности начался с определения ключевых задач стыка маркетинга и рекламы с технологиями:
  • должно собираться и обрабатываться огромное количество данных
  • клиенты могут генерировать миллионы событий, увеличивая нашу нагрузку за секунды
  • отзывчивость (latency) это КЛЮЧ к анализу данных в реальном времени

В 2013 году мы решили, что Go (на тот момент ещё 1.1) выглядит многообещающе, и мы написали первую версию нашего приложения меньше чем за 5 дней, и над ней работало всего 2 программиста. Фишки языка, такие, как горутины и каналы, сильно упростили задачу для написания кода, с обильной конкурентностью (concurrency). Достижение тысяч запросов в секунду на Macbook Pro с минимальными оптимизациями выглядело очень многообещающе.

Приложение, по сути, делает следующее: принимает запросы, с большим количеством URL параметров, в среднем по 1КБ каждый. Сервер парсит запросы, и отправляет сообщение в распределенную очередь. По окончании этого, он возвращает пустой ответ клиенту.

Растем дальше


Как только наш бизнес начал расти, мы увидели, что время отклика стало нарастать. У нас было SLA о 100мс на запрос. И когда мы выросли ещё больше, это становилось проблемой всё больше. Сначала мы решили, что это как-то связано с сетевыми соединениями к серверам, но даже при том, что мы генерировали терабайты данных ежедневно, проблема была в чём-то другом.

Тогда мы принялись анализировать поведение нашей Go программы. В среднем приложение тратило ~2мс на запрос, и это было отлично! У нас оставалось 98мс на сетевой оверхед, SSL рукопожатие, DNS запросы и всё остальное, что держит интернет на плаву.

К сожалению, стандартное отклонение времени отклика была большая, около 100мс. Уложиться в наше требование по SLA стало азартной игрой. С помощью Go пакета «runtime» мы сделали профайлинг нашего приложения и поняли, что нашей проблемой была сборка мусора, которая приводила к тому, что 95-перцентиль времени отклика составлял 279 миллисекунд…

Мы решили переписать большие куски нашего приложения так, чтобы они не генерировали мусора вообще. Это очень уменьшило интервал, на который сборщик мусора останавливал всё приложение, чтобы сделать свои магические действия. Но проблемы со временем отклика оставались всё равно, поэтому мы решили добавить больше нод, чтобы укладываться в наш SLA. При пиковых нагрузках в 80K запросов в секунду, даже минимальный мусор может быть серьёзной проблемой.

И этот день настал


Последние месяцы было много разговоров о Go 1.5. Компилятор полностью был переписан с C на Go, что напоминало мне фильм «Начало» («Inception»). Но более того, был полностью переделан сборщик мусора.

Вчера вечером (19 августа), этот момент наконец-то наступил. Стабильная версия Go 1.5 вышла, с утверждением:
Пауза «остановки мира» сборщика почти всегда будет меньше 10мс, и в большинстве случаев, намного меньше.

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

Мы дали поработать ей 12 часов и проанализировали новые значения времени отклика: всего запроса, отдельно приложения, и, не менее важный параметр, время пауз сборщика мусора. На графике ниже вы можете увидеть как уменьшился разброс по значениям и среднее значение времени отклика:


Две гистограммы времени отклика приложения (единственно важная для нас вещь). Ось X: время отклика, Ось Y: количество запросов. Слева: сервер бегущий на Go 1.4, справа: сервер бегущий на Go 1.5, разница видна невооруженным взглядом.

Новая версия Go уменьшила наше значение 95-перцентиля сборщика мусора с 279 мс до всего лишь 10 мс. Это фантастическое уменьшение паузы на 96% и это ровно то, что было указано в релиз нотах.

Паузы сборки мусора уменьшились на 96%

Мы решили задеплоить новую версию на остальную часть нашей инфраструктуры (12 дата центров в 7 географических зонах) и увидели, что наше среднее время отклика на запросы уменьшилось на 53%. Это означало, что мы можем без каких либо усилий уложиться в наши 100мс, плюс каждая нода теперь может держать большую нагрузку.

Благодаря отдаче и гибкости нашей команды, релиз Go 1.5 очень сильно улучшил нашу производительность и это произошло за 24 часа.
Перевод: Robin Verlangen
divan0 @divan0
карма
132,0
рейтинг 12,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

  • –6
    Раст даже на одном сервере тянет больше 100к запросов в секунду.
    Совсем грустно стало за Go после этой статьи.
    • +8
      На одном ядре.
      • +8
        Raspberry Pi…
        а если серьезно пруфы, код в студию можно будет посмотреть. у меня даже у nginx который на C не получается на ядре делать 100к
        • +4
          Да это шутки же всё :)
        • +26
          Не, Разбери — это сильно крутая железка.

          У меня sim-карта с Java Card Connected на сервлетах 100к запросв в секунду держит. Правда, мы ее деплоим на Nokia 3310

          image
    • +10
      Ну да, а у моего брата до сотни за 2 секунды… Вы что и с чем сравниваете, интересно?
    • +5
      Вы опять тут? =)
    • –3
      А akka хвалится своими 50 млрд сообщений в секунду, а ведь это JVM.
      • +3
        Во первых, не млрд а млн.
        Во вторых, это сообщения между акторами внутри JVM, а не запросы по сети.
        • +1
          Да, ошибочка вышла.
          Насчет второго хочу сказать, что вам нужно учиться распознавать сарказм)
      • 0
        А в третьих — пони порвал всех в мире акторов.
        Link: http://www.ponylang.org/benchmarks_all.pdf
  • +15
    Слева: сервер бегущий на Go 1.4, справа: сервер бегущий на Go 1.5, разница видна невооруженным взглядом.

    image
    • +3
      «Сам дурак»
      © Magic Gooddy '98
  • +2
    Cледующая заметка в блоге этого автора:

    Для HTTPS запросов у нас следующая структура:
    [nginx] => [varnish] => [golang-app]

    а для HTTP
    [varnish] => [golang-app]

    Сейчас мы используем чистый net/http для веб сервера, без каких-либо фреймворков с маршрутизацией. Тем не менее, приглядываемся к легковесным реализациям, которые генерируют меньше мусора (или вообще без мусора).
    • 0
      Здорово, спасибо за наводку.
  • 0
    Я правильно понял логику:
    нам нужна лэтенси ---> берем язык с gc ---> оказывается там фризы ---> о чудо, в версии 1.5 их немного меньше
    А если в 1.6 оно снова будет 100мс?
    А если бы в 1.5 ничего не изменилось бы?
    Я понимаю, это пеар ГОши, но если с другой стороны посмотреть — какая то идиотия, прости господи.
  • +6
    Краткое содержание статьи
    — Раньше я ездил на велосипеде с подспущенными шинами.
    Получалось ездить, но не очень быстро.
    Теперь мне накачали шины.
    Попробовал объехать вокруг дома — езжу быстрее!!!
    Буду теперь ездить быстрее, и все это произошло за пять минут!
    • +3
      Просто автор ещё не отошел от эйфории и не дошел до пункта «сделать выводы».
      А выводы могут быть такими «сегодня нас спасли разрабы go, но впредь нужно следить за сборкой мусора».
  • +2
    Standard deviation обычно на русский переводится как среднеквадратическое отклонение.
    • 0
      Спасибо, это верно. Но звучит слишком «научно» — поправил на «стандартное отклонение», что тоже, вроде как, корректно.
      • +2
        Научно не научно, но это общепринятая терминология которой лучше придерживаться.
        • +1
          Да что спорить, в любом случае в статье путаница. Автор называет это standard deviation, но очевидно имеет ввиду просто average request latency.
  • –4
    На что люди не идут, лишь бы сразу на эрланге всё нормально не сделать
  • –2
    Нет ли в статье скрытой рекламы таблеток персен?
  • +1
    Что еще за «персентиль»?
    • 0
      • 0
        Я-то догадался, что речь про процентиль, она же перцентиль.
        • 0
          Ну я и так, и так встречал. Хотя окей, поправлю.
  • 0
    У меня наоборот с 1.5.1 работа многопоточности ухудшилась — вырасли накладные расходы go на внутреннюю синхронизацию.

    Задача: tcp-прокси со сжатием трафика, активно работают 50 горутин — перекачивают и сжимают трафик, суммарный трафик относительно небольшой около 20-30Мбит/сек.
    При сборке программы с Go 1.5.1 нагрузка колеблется в районе 50-150% (т.е. от 0.5 до 1.5 ядер), профайлер показывает что очень заметная часть времени уходит на синхронизацию потоков:
    Заголовок спойлера
    34160ms of 57820ms total (59.08%)
    Dropped 144 nodes (cum <= 289.10ms)
    Showing top 10 nodes out of 114 (cum >= 1820ms)
          flat  flat%   sum%        cum   cum%
        9020ms 15.60% 15.60%     9020ms 15.60%  compress/flate.(*compressor).findMatch
        7690ms 13.30% 28.90%     7690ms 13.30%  runtime.futex
        5860ms 10.13% 39.03%     6880ms 11.90%  syscall.Syscall
        4090ms  7.07% 46.11%    16310ms 28.21%  compress/flate.(*compressor).deflate
        1520ms  2.63% 48.74%     1520ms  2.63%  runtime.xchg
        1440ms  2.49% 51.23%     9760ms 16.88%  runtime.findrunnable
        1340ms  2.32% 53.55%     1510ms  2.61%  runtime.atomicload
        1180ms  2.04% 55.59%     1180ms  2.04%  runtime._ExternalCode
        1120ms  1.94% 57.52%     1120ms  1.94%  runtime.usleep
         900ms  1.56% 59.08%     1820ms  3.15%  runtime.runqgrab
    



    При сборке с 1.4.3 нагрузка колеблется в районе 20-50%
    Заголовок спойлера
    48390ms of 60030ms total (80.61%)
    Dropped 161 nodes (cum <= 300.15ms)
    Showing top 10 nodes out of 64 (cum >= 500ms)
          flat  flat%   sum%        cum   cum%
       25510ms 42.50% 42.50%    25510ms 42.50%  compress/flate.(*compressor).findMatch
       10790ms 17.97% 60.47%    44660ms 74.40%  compress/flate.(*compressor).deflate
        3990ms  6.65% 67.12%     4400ms  7.33%  syscall.Syscall
        1940ms  3.23% 70.35%     7740ms 12.89%  compress/flate.(*huffmanBitWriter).writeBlock
        1630ms  2.72% 73.06%     1630ms  2.72%  runtime.futex
        1460ms  2.43% 75.50%     1970ms  3.28%  compress/flate.(*huffmanBitWriter).writeBits
        1210ms  2.02% 77.51%     2790ms  4.65%  compress/flate.(*huffmanBitWriter).writeCode
         730ms  1.22% 78.73%      890ms  1.48%  compress/flate.(*huffmanEncoder).bitCounts
         630ms  1.05% 79.78%      630ms  1.05%  runtime.writebarrierslice
         500ms  0.83% 80.61%      500ms  0.83%  unrollgcprog1
    



    Аналогично с go 1.3.3
    Заголовок спойлера
    Total: 2720 samples
         703  25.8%  25.8%      703  25.8% compress/flate.(*compressor).findMatch
         342  12.6%  38.4%     1258  46.2% compress/flate.(*compressor).deflate
         333  12.2%  50.7%      373  13.7% syscall.Syscall
         235   8.6%  59.3%      235   8.6% runtime.futex
          73   2.7%  62.0%       73   2.7% ExternalCode
          67   2.5%  64.4%       67   2.5% runtime.xchg
          45   1.7%  66.1%       45   1.7% runtime.usleep
          43   1.6%  67.7%      213   7.8% compress/flate.(*huffmanBitWriter).writeBlock
          38   1.4%  69.1%       61   2.2% compress/flate.(*huffmanBitWriter).writeBits
          34   1.2%  70.3%       84   3.1% compress/flate.(*huffmanBitWriter).writeCode
    

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.