Статьи

Настройка производительности устаревших приложений

В  своей  карьере настройки производительности я давал советы измерять и не угадывать чаще, чем я могу вспомнить. И во многих случаях цель этого совета перестала смотреть на монолитное унаследованное приложение на 500 000 LOC, над которым они работают.

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

Постановка цели

Практически любое нетривиальное приложение можно оптимизировать навсегда. И в плохом, и в хорошем смысле. Плохие примеры включают в себя настройку случайных частей приложений, а затем молиться за лучшее. Какова бы ни была причина — я видел десятки и десятки разработчиков, «уверенных, что именно эта строка кода требует внимания», не основывая свое интуитивное чувство на каких-либо измерениях.

Вторая категория почти так же опасна, поскольку вы можете потратить сотни человеко-лет на настройку своего приложения, «чтобы быть готовым к прайм-тайму». Определение прайм-тайма может отличаться, но вы действительно уверены, что собираетесь разместить терабайты мультимедиа, десятки миллионов записей в вашей базе данных и обслуживать сотни тысяч одновременно работающих пользователей с задержкой менее 100 мс? Если вы не намерены разорить Google, вы, скорее всего, этого не сделаете. Хуже того, тратить так много времени на подготовку к прайм-тайму — это чертовски хороший способ убедиться, что прайм-тайм никогда не наступит. Вместо того, чтобы тратить это время на то, чтобы выжать лишние 1 мс задержки, никто, вероятно, не заметит, может быть, то же самое время должно было потратить на устранение этой ошибки макета, раздражающей конечных пользователей в Safari?

Итак, как установить значимую цель? Для этого вам необходимо понять, в каком бизнесе вы находитесь. Стратегические игры в реальном времени и шутеры от первого лица, как правило, предъявляют иные требования, чем тележки для покупок в Интернете. Как я проверял в прошлый раз, Java не была сильной в игровой индустрии, поэтому предположим, что вы имеете дело с типичным приложением Java EE с веб-интерфейсом.

В этом случае вы должны начать сегментировать ваше приложение по различным категориям, которые,  основываясь на исследовании  , похожи на следующее:

  • 0,1 секунды дает ощущение мгновенного ответа — то есть результат ощущается так, как будто он был вызван пользователем, а не компьютером.
  • 1 секунда поддерживает непрерывность мысли пользователя. Пользователи могут почувствовать задержку и, таким образом, знать, что компьютер генерирует результат, но они по-прежнему чувствуют, что контролируют весь процесс и что они двигаются свободно, а не ждут на компьютере. Эта степень отзывчивости необходима для хорошей навигации.
  • 10 секунд удерживает внимание пользователя. В течение 1–10 секунд пользователи определенно чувствуют зависимость от компьютера и хотят, чтобы он работал быстрее, но они могут справиться с этим. Через 10 секунд они начинают думать о других вещах, что усложняет восстановление мозгов, как только компьютер, наконец, отвечает.

Таким образом, у вас может быть такая функциональность, как добавление продуктов в корзины покупок или просмотр рекомендуемых товаров, которые нужно втиснуть в «мгновенное ведро», чтобы обеспечить наилучшее общее взаимодействие с пользователем и повысить показатели конверсии. Изначально я уверен, что ваше приложение не соответствует этим критериям, поэтому смело заменяйте порог в 100 мсек на то, чего вы действительно можете достичь, например, 300 мсек.

С другой стороны, у вас, скорее всего, есть некоторые операции, которые достаточно дороги, такие как поиск товаров или регистрация учетной записи пользователя, которые могут попасть во второе ведро.

И, наконец, у вас есть функциональность, которую вы можете добавить в последнюю корзину, например, создание PDF из счета. Обратите внимание, что вы также можете получить четвертую категорию — например, если вы генерируете накладные для вашего склада, ваш бизнес может быть полностью в порядке, если пакетная обработка ежедневных накладных занимает 20 минут.

Обратите внимание, что деловые люди имеют тенденцию бросать все в «мгновенное» ведро. Теперь ваша задача объяснить требуемые усилия и спросить: «Если у вас было только три операции, которые выполняются под нашим порогом, что это должно быть».

Теперь вы должны разбить функциональность вашего приложения на разные категории, такие как следующие:

требование

категория

Просмотр продуктов

мгновенное

Добавить товар в корзину

мгновенное

Поиск товара

бесшовный

Зарегистрировать новый аккаунт

внимание

Генерация счета в формате PDF

внимание

Генерация ежедневных счетов доставки

медленный

Понимание текущей ситуации

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

Поскольку это будет более сложно измерить, давайте предположим, что ваш сайт был хорошо оптимизирован в соответствии с   рекомендациями Google Page Speed  или  YSlow, а для простоты давайте сосредоточимся на элементах, находящихся непосредственно под вашим контролем.

В большинстве случаев последним элементом инфраструктуры, который остается под вашим контролем, будет ваш веб-сервер, такой как  Apache HTTPD  или  nginx . Если у вас включено ведение журнала, у вас будет доступ к чему-то похожему на следующее в вашем nginx access.log:

82.192.41.11 - - [04/Dec/2013:12:16:11 +0200]  "POST /register HTTP/1.1" 301 184 "https://myshop.com/home" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0" 0.428
82.192.41.11 - - [04/Dec/2013:12:16:12 +0200]  "POST /searchProduct HTTP/1.1" 200 35 "https://myshop.com/accountCreated" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0" 3.008 
82.192.41.11 - - [04/Dec/2013:12:16:12 +0200]  "GET /product HTTP/1.1" 302 0 "https://myshop.com/products/searchProducts" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0" 0.623
82.192.41.11 - - [04/Dec/2013:12:16:13 +0200]  "GET /product HTTP/1.1" 200 35 "https://myshop.com/product/123221" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0" 0.828
82.192.41.11 - - [04/Dec/2013:12:16:13 +0200]  "GET /product HTTP/1.1" 200 35 "https://myshop.com/product/128759" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0" 1.038
82.192.41.11 - - [04/Dec/2013:12:16:13 +0200]  "GET /product HTTP/1.1" 200 35 "https://myshop.com/product/128773" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0" 0.627
82.192.41.11 - - [04/Dec/2013:12:16:14 +0200]  "GET /addToShoppingCart HTTP/1.1" 200 35 "https://myshop.com/product/128773" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0" 2.808
82.192.41.11 - - [04/Dec/2013:12:16:14 +0200]  "GET /purchase HTTP/1.1" 302 0 "https://myshop.com/addToShoppingCart" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0" 3.204
82.192.41.11 - - [04/Dec/2013:12:16:16 +0200]  "GET /viewPDFInvoice HTTP/1.1" 200 11562 "https://myshop.com/purchase" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0" 3.018

Теперь давайте посмотрим, что мы нашли в журнале. Извлеченный нами фрагмент содержит действия одного пользователя, завершившего полную транзакцию на нашем веб-сайте. Пользователь создал учетную запись, выполнил поиск продуктов, просмотрел результаты, нашел понравившийся продукт, добавил его в корзину, совершил покупку и сгенерировал PDF счета-фактуры. Эти действия можно обнаружить в столбце «POST / searchProduct HTTP / 1.1». Следующая важная часть — это последний столбец, содержащий общее время запроса, которое потребовалось для выполнения конкретного запроса. В случае / searchProduct потребовалось 3,008 секунды для завершения поиска.

Обратите внимание, что по умолчанию в  nginx  не включено ведение журнала времени запроса, поэтому вам может потребоваться изменить ваш log_format, добавив $ request_time в шаблон.

Теперь с небольшим количеством волшебства grep / sed / excel у вас будет что-то похожее на следующее:

требование

категория

Жадный

90%

Просмотр продуктов

мгновенное

0,734

0,902

Добавить товар в корзину

мгновенное

2,422

3,490

Поиск товара

бесшовный

2,800

3,211

Зарегистрировать новый аккаунт

внимание

0,428

0,480

Генерация счета в формате PDF

внимание

3,441

4,595

Генерация ежедневных счетов доставки

медленный

Выбор цели для оптимизации

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

Эта статья не фокусируется на реальных задачах оптимизации, но чтобы дать пищу для размышлений — что вам следует делать дальше, зная, что добавление продуктов в корзину занимает слишком много времени?

Следующие шаги определенно более специфичны для приложения, чем предыдущие задачи. Кроме того, используемые инструменты могут различаться — теперь вы можете решить использовать APM или продукты для мониторинга. Но если у вас нет бюджета или вы просто не хотите бороться с отделом закупок, простое решение будет включать добавление журнала регистрации в границах компонента с использованием средств AOP.

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

@Aspect
@Component
public class TimingAspect {

  @Around("execution(* com.myshop..*Service+.*(..))")
  public Object time(ProceedingJoinPoint joinPoint) throws Throwable {
    StopWatch.WatchStatus watch = StopWatch.getInstance().start(joinPoint.getSignature().getName());
    final Object result = joinPoint.proceed();
    watch.stop();
    return result;
  }

}

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

Этот пост был первоначально опубликован в  Java Advent Calendar . Этот конкретный пост лицензируется под  лицензией Creative Commons 3.0 Attribution  . Если вам это нравится, пожалуйста, распространите информацию, поделившись, чирикать, FB, G + и так далее!