производительность системы

Показатели, которые следует мерить для нагрузочного тестирования:

  1. Скорость ответа(далее Latency) — какая средняя скорость ответа? Какая максимальная и минимальная?
  2. Пропускная способность(далее Bandwitch) — сколько запросов в минуту выполнено?
  3. Количество ошибок(далее Error rate) — сколько запросов приложение не смогло выполнить?

Подробнее о третьем показателе. Когда система подвергается большей нагрузке, чем способна обработать(т.е., когда мы пытаемся закачать в “трубу” больше задач, чем она может через себя пропустить), у нее есть 3 варианта поведения:

  1. Правильный. Поставить входящие запросы в очередь и обработать их когда освободятся ресурсы
    • Пример: Наш электронный магазин предоставляет HTTP REST API партнерам, позволяя им закупать у нас товары оптом. Наш сервер API способен обслуживать 40 одновременных запросов. Что произойдет, когда мы получим одновременно 41 запрос? Нам придется поставить один запрос в очередь, и заставить его ждать, пока не освободится какой то из потоков, которые обслуживают сейчас предыдущих 40 пользователей. А если мы получим 60 одновременных запросов? Поставим в очередь 20 из них.
  2. Правильный. Если количество запросов настолько велико, что их невозможно обработать за требуемое время — отбрасывать запросы
    • Пример: Продолжая пример выше, что если мы получим 240 запросов? Мы конечно можем поставить 200 запросов в очередь- но чем больше у нас ожидающих в очереди, тем хуже средняя скорость ответа. Представьте, что вы стоите в огромной очереди за билетами на спектакль, который показывают сегодня последний раз и вы явно не успеваете до закрытия кассы. Нет смысла стоять в такой очереди. С точки зрения пользователя, система которая “зависла”, т.е.,отвечает слишком медленно, ничем не лучше системы которая “сломана”, т.е., не отвечает вообще.  (Да, с точки зрения инженера, который обслуживает системы и знает их изнутри, разница большая — но заказчика это не интересует). Система, которая сразу сигнализирует о том, что перегружена/получила больше работы, чем может выполнить в срок, намного лучше, чем та которая молча заставляет клиентов ждать слишком долго/вечно. Нужно поставить лимит на длину очереди и отбрасывать (например через HTTP 500) запросы которые не уместились в нее.Вопрос на проверку: если наш SLA(Service Level Agreement) “запрос должен быть обработан не больше чем за 6 секунды”  и скорость выполнения запроса у нас 3 секунды — какая максимальная длина очереди имеет смысл? Ответ — 40. Более длинная очередь не позволит выполнить SLA.
  3. Неправильный. Пытаться сделать больше работы, чем система способна выполнить. Это приведет лишь к неконтролируемому снижению производительности(из-за swopping’а памяти, перегрузки CPU/HDD/IO, и т.д.)  
  4. Неправильный. Все остальное. Повредить данные, выдавать пользователю мусор вместо требуемого HTML/XML, не выдавать никакого ответа и так далее.

Типичный график нагрузочного тестирования. Система способна обработать не больше  100 запросов в секунду. Запихивание в систему большего объема работы (300 запросов) только создает длинную очередь из 200 ждущих запросов.

Тестирование, естественно, должно быть автоматизированным (как иначе имитировать работу сотен пользователей). Есть хороший выбор инструментов тестирования (Jmeter, Grinder, Load  Runner).

Тестирование должно быть повторяемым ( любой тест можно воспроизвести и сравнить производительность приложения до оптимизаций и после).

Любой участник команды(разработчик или тестировщик) должен иметь возможность  провести тест на выбранном стенде в DEV или QA или на собственном компьютере — чтобы провести эксперимент или ревью.

Следовательно:

  • Тестовые скрипты, их настройки, тестовые данные, скрипты генерации тестовых данных — в Version Control System
  • конфигурационные файлы приложения, OC и middleware (MongoDB, Elastic Search/ и т.д.) в Version Control System.
  • Инструмент тестирования — любой разработчик может легко скачать и установить его на свой компьютер (нужны лицензии или инструмент должен быть open source).
  • Отчет о тестировании должен показывать:
  • Производительность по каждой тестируемой задаче (“полнотекстовый поиск”, “просмотр накладной” и т.д.) по результатам последнего теста.
  • Тренд в разрезе задача/производительность/количество пользователей за период, чтобы отследить, как недавние изменения в продукте повлияли на производительность
  • Необходимо отдельно тестировать производительность в режиме “кеширование включено” и “кеширование выключено”. Иначе мы можем получить завышенные результаты. Кеширование — может сильно повысить производительность и ,поэтому, используется, и в нашем продукте и во всех лежащих в его фундаменте Middleware(Веб сервер, СУБД,и т.д.). Но, в зависимости от того, как пользователи используют наш продукт, кеширование может дать как значительный выигрыш, так и нулевой и даже отрицательный (т.е., снизить производительность). Например, функция веб сайта “пользователь просматривает свой приватный профиль” не выигрывает от использования кеширования:
  • пользователь не будет просматривать свой профиль несколько раз подряд,
  • другим пользователям доступ к этим данным запрещен,
  • в целом этой функцией редко пользуются.

Нагрузочное тестирование для такой функции покажет ЗАВЫШЕННУЮ производительность. Потому что тест выполнит много запросов подряд, и , естественно, кеширование увеличит производительность — в тесте, но не в реальной жизни! Решение — выключить кеширование, прежде чем тестировать такую функцию ( в настройках нашего приложения, веб сервера, базы данных, и т.д.).

— В ходе тестировании желательно фиксировать показатели нагрузки на железо/middleware. Это может помочь найти узкие места (далее bottleneck) в системе.

Типовой набор метрик:

  • CPU User time
  • CPU System time (Может  показывать на неэффективный ввод-вывод, т.е. слишком большое количество мелких операций)
  • HDD Number of reads
  • HDD Number of writes
  • HDD bytes readed
  • HDD bytes written
  • Network Number of reads
  • Network number of writes
  • Network bytes readed
  • Network bytes written
  • RAM Private space used by application
  • RAM Shared space used by application
  • RAM Number of page faults (насколько локализованы данные приложения в памяти?)
  • Когда начинать нагрузочное тестирование: как только получена первая сборка приложения.
  • Мощность и конфигурация тестового стенда должны быть идентичны Production. (Для сокращения издержек, в случае использования Cloud можно выключать тестовые сервера либо снижать их мощность между нагрузочными тестами )
  • Типичная ошибка — запуск теста несколько раз без “холодного запуска” тестируемого ПО(завышает результаты из-за кеширования).

Чтобы получить высокую производительность:

  • Уменьшайте длину задач(т.е. Latency)
  • Но не за счет излишнего увеличения их “ширины”. Например, слишком агрессивное кеширование памяти в задаче “сгенерировать веб страницу” ускорит ее выполнение, но заодно увеличит расход памяти на каждого пользователя — и ,в результате, сократит Bandwitch
  • Уменьшайте ширину задач в конвейере
  • Уменьшайте количество задач в конвейере

Есть 3 способа достичь этого:

  • Ускорить задачу, выполнять ее быстрее
  • Распараллелить задачи
  • Исключить задачу, совсем обойтись без нее

Кеширование

Идея — мы вычислили/добыли данные один раз и кладем их поближе, чтобы в следующий раз далеко не тянуться.

Пример 1:

Прочитали данные с диска — запомнили в оперативной памяти. Сэкономим в следующий раз на ожидании дискового ввода-вывода.

Пример 2: У нас сервера в Европе, а клиент — в США. Проблема — network latency. Мы устанавливаем кеширующий сервер в CША, поближе к клиенту.

Как мы выбираем — что кешировать, а что — нет? На какой срок?

Не имеет смысла кешировать данные, которые не будут повторно востребованы. Такое кеширование только понизит производительность — из за накладных расходов на помещение данных в кеш/поиск в кеше.

Пример: Кеширует ли  Google результаты поиска (вот этот набор из 100 000 ссылок по поисковой фразе “database performance”)? Предположу, что если и кеширует, то на короткий промежуток времени. Вероятность того, что много разных людей будут искать одно и то же — невелика. Вероятность, что они сформулируют свой поисковый запрос одинаково, слово в слово — еще меньше. В каком-нибудь поисковом сервисе для интранет — я бы не кешировал совсем.

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

Как выбираем размер кеша

Отмерим для кеша слишком мало памяти — нам не хватит места для данных. Отмерим слишком много — будем тратить больше времени на поиск в нем. Ищем баланс, используем статистику использования кеша.

Как очищаем кеш

Предположим, прочитали мы из медленного HDD/СУБД, что Иванов — начальник транспортного цеха. И закешировали в RAM на 15 минут. А через минуту пришел сотрудник HR и перевел Иванова на должность начальника цеха готовой продукции.

Пока все тривиально, наше приложение должно:

  • Обновить данные об Иванове в СУБД
  • Удалить устаревшие данные  об Иванове из кеша

Что если у вас есть внешняя система, которая тоже может менять данные?

Например, данные департамента HR импортируются из другого филиала?

Кеш должен позволять очистить данные о любой выбранной бизнес-сущности/все данные:

  • Посредством API (которое “будет вызвано” процессом импорта данных)
  • Посредством commandline tool или пользовательского интерфейса, доступного только администратору — для упрощения сопровождения системы

Распределенные системы с Мастер-Мастер архитектурой добавляют новую сложность. Представьте, что у вас два серверных центра, в Москве и в Питере, и оба одновременно читают/изменяют одни и те же данные(master-master database replication). Необходимо реплицировать между центрами не только измененные данные, но и извещения наподобие “уважаемый Другой Дата Центр, сбросьте Ваш кеш о пользователе 123, его данные поменялись”. В качестве транспорта для “сбрось кеш” извещений, вероятно, следует использовать тот же канал, что и для репликации самих данных.

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

Предположим, наши клиенты расположены далеко от дата центра. Network latency варьирует от 0.1 ms (миллисекунды) в LAN до 100 ms в интернет. Есть смысл установить кеширующий сервер, поближе к ним, и кешировать данные в нем. Что если данные, к которым есть доступ у клиента X, не должны быть доступны клиенту Y? Типичные решения:

  • Кеширующий сервер делает запрос “разрешено ли Y смотреть данные с ID 17?” в центр данных (что, к сожалению, добавляет Network latency)
  • Клиент Y предварительно получает в центре данных тикет, в котором, в зашифрованном виде, содержится описание его прав доступа(и у кеширующего сервера есть ключ дешифрования)
Как проверяем “свежесть” данных

Продолжая пример с network latency, как кеширующий сервер проверит, доступны  ли,обновились ли данные в дата центре?

Прямолинейный способ — загрузить их заново, более эффективный — загрузить их только если контрольная сумма данных изменилась. Пример из спецификации HTTP — http:///wiki/HTTP_ETag.

Целостность данных в кеше

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

Зависимости между кешированным данными — когда мы удаляем из кеша устаревшие данные, нужно удалить и те, которые от них зависят.

Говоря об имплементации, потребуется

  • спецификация для имен ключей в кеше
  • спецификация формата данных в кеше
  • спецификация зависимостей (“когда чистите в кеше Пользователя, вычистите и его Профиль”)

Желательно иметь библиотеку- wrapper, которая одновременно и  документирует спецификации в коде и вынуждает, в хорошем смысле, программистов выполнять их.

Предварительный прогрев кеша

Может потребоваться при большой зависимости производительности приложения от кеширования/большом объеме данных. Чтобы избежать резкого “проседания” производительности после полного сброса кеша/перезапуска приложения.  

Специфика кеширования запросов в СУБД/ORM

Базы данных используют кеширование двояко:

  1. Низкоуровневый кеш блоков данных
  2. Кеш запросов (где ключом является “SELECT FistName,LastName from Users Where ID=123“, а значением — прочитанный набор записей)

Кеш запросов хорошо помогает при READ-ONLY доступе к небольшому объему данных(чтобы полностью уместился в кеше). Следует понимать, это не замена полноценному кешированию на уровне приложения.

Дело в том, что СУБД “не знает” бизнес-логики приложения и оперирует только текстами SQL запросов. Это создает проблемы c дубликацией/запихиванием слишком большого объема данных в кеш/излишне агрессивном сбросе кеша.

Например, база данных получила SQL два запроса подряд:

  1. SELECT * from user_profile where ID=123;
  2. UPDATE user_profile SET compensation_coeffcient=1.5 WHERE employee_grade=4;

Устарели ли результаты “SELECT …”? С точки зрения приложения — нет, потому что поле compensation_coeffcient им не используется, либо пользователь с ID==123 не имеет “employee_grade==4”. Но СУБД либо не может этого знать либо не может эффективно отследить.

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

Предварительные вычисления (Precompute)

Когда стоимость вычисления данных велика, вероятность повторного обращения к тем же данным значительна и частота изменений данных мала — имеет смысл предварительно вычислить их и сохранить в persistent storage.

Пример: Проблема — Чтение из СУБД неких редко изменяемых,но часто требуемых данных выполняется медленно из-за сложных SQL запросов со множеством JOIN’ов. Изменить структуру СУБД мы не хотим(другие бизнес-процессы требуют именно такой структуры).

Решение — Выполнять чтение и преобразование данных фоновым процессом, который сохраняет уже обработанные данные в “кеширующей таблице” в СУБД.

Пакетные операции

Объединение задач в “пакет” (batch) позволяет экономить на накладных расходах.

Типичная ошибка — не использовать SQL Batch при вставке большого количества записей в СУБД. Вставка данных требует от СУБД обработать входные данные/проанализировать SQL запрос/записать транзакцию в WAL(http:///wiki/Write-ahead_logging)/перестроить индекс таблицы. Вставка десяти записей означает — вы платите десять раз. При использовании Batch insert(пример для MySQL) — платите накладные расходы один раз.

Пример: Пул соединений

Типичное серверное приложение извлекает, по запросу пользователя, данные из СУБД(Сервера поиска/внешней системы/и т.д.)

Установка нового соединения съедает время(network latency/инициализация нового потока на стороне СУБД). Пул заранее открытых соединений к серверу СУБД решает эту проблему. С другой стороны, больше открытых соединений — больший расход ресурсов.

  • Когда применять: Небольшие объемы данных, частые запросы к ним.
  • Когда НЕ применять: Большие объемы данных(время на установление соединения пренебрежимо мало по сравнению с общей продолжительностью пересылки данных),  редкие запросы (т.е., открытое соединение будет простаивать, разбазаривая ресурсы )

Потенциальные проблемы:

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

  1. Приложение получило задачу на выполнение
  2. Открыло соединение (новое, “чистое”)
  3. Послало запрос
  4. Получило ответ
  5. Закрыло соединение (если в приложении до этого произошел сбой, СУБД сама закроет, когда сработает timeout )

Типовые проблемы при  использовании пула соединений:

  • Шаг №2 может надолго заблокировать поток. Например, приложение содержит ошибку и  не всегда возвращает соединение в пул. В результате все незанятые соединения в пуле оказались исчерпаны. И  реализация метода “openConnection” в пуле ждет, когда освободится одно из занятых сейчас соединений, пока не сработает таймаут(или вечно…)
  • Полученное на шаге №2 соединение уже использовалось ранее и могло “унаследовать” от предыдущего потока целую россыпь проблем (НЕзакрытая транзакция/НЕснятые блокировки/мусорные настройки Transaction Isoloation Level|Charset conversion/мусор в server side SQL variables).
  • Соединение может быть уже закрыто сервером СУБД из-за ошибок, либо длительной неактивности.
  • Полученный на шаге №4 ответ может быть вовсе не ответом на посланный в №3 запрос. Это может быть ответ на запрос, посланный предыдущим “пользователем” соединения.

Поэтому использование persistent соединений требуют поддержки на уровне реализации пула/протокола/драйвера БД/СУБД.

  • Сброс состояния соединение при возвращении в пул — СУБД должно предоставлять такую возможность и драйвер в приложении должен ею пользоваться.
  • Идентификация на уровне драйвера/протокола — к какому посланному ранее запросу относится полученный драйвером от СУБД ответ.
  • Отслеживание и четкое разграничение драйвером ошибок на сетевом уровне(timeout/connection closed) от ошибок уровня приложения (SQL query has error). Первые делают невозможным использование соединения/требуют выбросить его из пула. Вторые позволяют продолжить его использование.
  • Необходим лимит на количество в пуле (не больше, чем сервер приложений может обработать параллельно).
  • Лимит на начальное количество соединений в пуле.
  • Настройка — Сколько добавлять в пул за раз, когда не хватает.
  • Timeout на извлечение из пула. Добавление нового соединения может занять от доли секунды до часов(перегруженный сервер СУБД/сбой или неправильная настройка в файерволе или load balancer’е). Последнее, чего мы хотим — толпа потоков в сервере приложений, заблокированных навечно, потому что в пуле закончились соединения и он заблокировался при добавлении нового.
  • Timeout на соединение с СУБД  — см выше.
  • Timeout на получение данных из СУБД — см выше.
  • Heartbeat  — пул должен регулярно делать “ping” на каждом соединении, чтобы СУБД не закрыло его за неактивность .
  • Перезапуск пула без перезапуска приложения(см выше список проблем из-за которых перезапуск может понадобится). Перезапуск крупного server side приложения может потребовать несколько минут прежде чем работоспособность восстановиться полностью ( “прогрев” кешей/инициализация/установка network соединений).
  • Реконфигурация пула без перезапуска приложения(см выше).

Программные методы тестирования компьютера

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

Преимущество все-таки стоит отдавать тем методам, которые есть в самой операционной системе Windows. Они наиболее точны.

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

Мы рассмотрим основные образцы ПО, которые позволяют выполнить тест производительности.

Вернуться к меню ↑

Теги

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

Adblock
detector