В своей карьере настройки производительности я давал советы измерять и не угадывать чаще, чем я могу вспомнить. И во многих случаях цель этого совета перестала смотреть на монолитное унаследованное приложение на 500 000 LOC, над которым они работают.
В этом посте мы собираемся поделиться некоторыми простыми инструментами и концепциями по настройке начальных измерений, чтобы у вас была возможность начать с более тонкой настройки производительности.
Постановка цели
Практически любое нетривиальное приложение можно оптимизировать навсегда. И в плохом, и в хорошем смысле. Плохие примеры включают в себя настройку случайных частей приложений, а затем молиться за лучшее. Какова бы ни была причина — я видел десятки и десятки разработчиков, «уверенных, что именно эта строка кода требует внимания», не основывая свое интуитивное чувство на каких-либо измерениях.
Вторая категория почти так же опасна, поскольку вы можете потратить сотни человеко-лет на настройку своего приложения, «чтобы быть готовым к прайм-тайму». Определение прайм-тайма может отличаться, но вы действительно уверены, что собираетесь разместить терабайты мультимедиа, десятки миллионов записей в вашей базе данных и обслуживать сотни тысяч одновременно работающих пользователей с задержкой менее 100 мс? Если вы не намерены разорить Google, вы, скорее всего, этого не сделаете. Хуже того, тратить так много времени на подготовку к прайм-тайму — это чертовски хороший способ убедиться, что прайм-тайм никогда не наступит. Вместо того, чтобы тратить это время на то, чтобы выжать лишние 1 мс задержки, никто, вероятно, не заметит, может быть, то же самое время должно было потратить на устранение этой ошибки макета, раздражающей конечных пользователей в Safari?
Итак, как установить значимую цель? Для этого вам необходимо понять, в каком бизнесе вы находитесь. Стратегические игры в реальном времени и шутеры от первого лица, как правило, предъявляют иные требования, чем тележки для покупок в Интернете. Как я проверял в прошлый раз, Java не была сильной в игровой индустрии, поэтому предположим, что вы имеете дело с типичным приложением Java EE с веб-интерфейсом.
В этом случае вы должны начать сегментировать ваше приложение по различным категориям, которые, основываясь на исследовании , похожи на следующее:
- 0,1 секунды дает ощущение мгновенного ответа — то есть результат ощущается так, как будто он был вызван пользователем, а не компьютером.
- 1 секунда поддерживает непрерывность мысли пользователя. Пользователи могут почувствовать задержку и, таким образом, знать, что компьютер генерирует результат, но они по-прежнему чувствуют, что контролируют весь процесс и что они двигаются свободно, а не ждут на компьютере. Эта степень отзывчивости необходима для хорошей навигации.
- 10 секунд удерживает внимание пользователя. В течение 1–10 секунд пользователи определенно чувствуют зависимость от компьютера и хотят, чтобы он работал быстрее, но они могут справиться с этим. Через 10 секунд они начинают думать о других вещах, что усложняет восстановление мозгов, как только компьютер, наконец, отвечает.
Таким образом, у вас может быть такая функциональность, как добавление продуктов в корзины покупок или просмотр рекомендуемых товаров, которые нужно втиснуть в «мгновенное ведро», чтобы обеспечить лучший общий пользовательский опыт, что приведет к лучшим показателям конверсии. Изначально я уверен, что ваше приложение далеко не соответствует этим критериям, поэтому смело заменяйте порог в 100 мс и 1000 мс на то, чего вы действительно можете достичь, например, на 300 и 1500 мс.
С другой стороны, у вас, скорее всего, есть некоторые операции, которые достаточно дороги, такие как поиск товаров или регистрация учетной записи пользователя, которые могут попасть во второе ведро.
И, наконец, у вас есть функциональность, которую вы можете добавить в последнюю корзину, например, создание PDF из счета. Обратите внимание, что вы также можете получить четвертую категорию — например, если вы генерируете накладные для вашего склада, ваш бизнес может быть полностью в порядке, если пакетная обработка ежедневных накладных занимает 20 минут.
Обратите внимание, что деловые люди имеют тенденцию бросать все в «мгновенное» ведро. Теперь ваша задача объяснить требуемые усилия и спросить: «Если у вас было только три операции, которые выполняются под нашим порогом, что это должно быть».
Теперь вы должны разбить функциональность вашего приложения на разные категории, такие как следующие:
требование | категория |
Просмотр продуктов | мгновенное |
Добавить товар в корзину | мгновенное |
Поиск товара | бесшовный |
Зарегистрировать новый аккаунт | внимание |
Генерация счета в формате PDF | внимание |
Генерация ежедневных счетов доставки | медленный |
Понимание текущей ситуации
Ваша следующая цель — понять, где вы будете проводить измерения. Для конечных пользователей работа завершается, когда страница отображает результат операции в их браузере, который учитывает, например, сетевую задержку и операции браузера DOM.
Поскольку это будет более сложно измерить, давайте предположим, что ваш сайт был хорошо оптимизирован в соответствии с рекомендациями Google Page Speed или YSlow, а для простоты давайте сосредоточимся на элементах, находящихся непосредственно под вашим контролем.
В большинстве случаев последним элементом инфраструктуры, который остается под вашим контролем, будет ваш веб-сервер, такой как Apache HTTPD или nginx . Если у вас включено ведение журнала, у вас будет доступ к чему-то похожему на следующее в вашем nginx access.log:
1
2
3
4
5
6
7
8
9
|
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.
Например, вы можете добавить что-то вроде следующего в код вашего приложения, чтобы отслеживать время, проведенное на уровне сервиса.
01
02
03
04
05
06
07
08
09
10
11
12
|
@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; } } |
Кроме того, разложение времени запроса дает вам необходимую информацию, чтобы понять, где скрываются узкие места. Чаще всего вы быстро обнаружите нарушающий запрос, неправильную конфигурацию кэша или плохо разложенную функциональность, что позволит вам получить свои первые быстрые выигрыши за считанные дни или даже часы.