
« Безумству храбрых поем мы песню»
Привет, Хабр! Меня зовут Евгений, я ведущий разработчик отдела SAP разработки.
Большинству из нас рано или поздно приходится сталкиваться с задачами оптимизации ПО. Последние пару лет мне пришлось плотно поработать в этом направлении, и в итоге решил поделиться некоторыми своими наблюдениями\выводами.
Статья описывает общий подход к увеличению производительности ПО. Для новичков в подобном "развлечении", она позволит понять что делать и куда смотреть в первую очередь. Опытные разработчики, надеюсь найдут для себя что то полезное, или смогут упорядочить свои знания.
Первоначально задумывалось, что целевая аудитория — SAP ABAP разработчики и ТехЛиды. Однако, после того как была написана основная часть статьи, пришло понимание, что рассказать все в одной публикации не получится, а специфика SAP систем фактически остается за кадром. Поэтому, к.м.к. получившийся вводный материал будет полезен разработчикам независимо от специализации, и возможно консультантам\системным аналитикам.
Оглавление
1. Постановка задачи
Что такое производительность?
Да, а что такое производительность? Термины же важны, и не важны одновременно.
Можно взять какое‑нибудь из определений производительности ПО, например:
Способность системы выполнять заложенные в нее функции для заданных ограничений по времени обработки и пропускной способности.
Способность приложения или системы эффективно выполнять поставленные задачи, обеспечивая высокую скорость работы и минимальное потребление системных ресурсов
А можно вспомнить какой‑нибудь случай, когда к вам приходят со словами:
«Ваша программа работает уже 12 часов, а должна была отработать за три! Что за... безобразие?».
«Почему я перехожу на страницу сайта и успеваю выспаться пока браузер ее отрисует?»
«Опять ваше... изделие съело всю доступную память сервера и упало в дамп!»
Все эти формулировки в свободной форме сводятся к двум основным претензиям:
Программа работает слишком медленно. Что бы это не означало в конкретном контексте.
Программа потребляет слишком много ресурсов. Память, диски, сеть... нужное подчеркнуть.
А это правда важно?
Скажем правду, в большинстве случаев производительность не имеет значения.
Есть много других проблем, более актуальных на момент создания ПО.
Например: Cможем ли мы закончить эту разработку в приемлемые сроки? А когда закончим, будет ли хоть кто-нибудь ей пользоваться? Нам заплатят за нашу работу...? Почему, почему они опять хотят все изменить? :)
Ну и прочие радости из жизни разработки софта.
Однако, для определенных случаев, и для некоторых классов систем производительность важна.
Когда повезет, на это обращают внимание уже на этапе постановки задачи, и производительность системы становится одним из важных архитектурных критериев.
Тогда уже на этапе разработки архитектуры и проектирования системы закладываются решения позволяющие достичь требуемых характеристик производительности, надежности и т.д.
Но мы же знаем, как это происходит в real life. О простите, в реальной жизни :)
В лучшем случае, проблемы производительности решаются в момент их возникновения на этапе разработки или тестирования. И в целом это неплохо, потому что во время разработки\приемки значительно проще изменить реализацию. Конечно это сильно дороже, чем если бы мы подумали заранее. Но все же, это сильно проще и дешевле чем делать доработку решения уже ушедшего в промышленную эксплуатацию.
Ну и наконец самый распространенный вариант.
Система\программа\комплекс ПО разработан, прошел приемочные испытания, был успешно сдан в опытную, а затем промышленную эксплуатацию.
После этого в течение N лет дорабатывался\изменялся\модернизировался, обрастал дополнительным функционалом и в итоге пришел к состоянию - оно как то работает. Работает, решает проблемы бизнеса, но скорость перестала удовлетворять пользователей, а потребление ресурсов - постоянная головная боль для службы ИТ.
В этом момент возникает задача оптимизации работы программы. Вспоминают что производительность это одно из архитектурных требований, оно важно, и .. надо что то делать.
А дальше приходят к разработке со словами "сделайте красиво." И нет, переписать все, слишком дорого, долго и вообще, уже никто не понимает полностью, как оно работает.
Тут мне всегда вспоминается очень старый анекдот:
"Одна американская фирма купила дорогое японское оборудование по производству ( чего-то ). Его установили, настроили и оно начало беспрерывно работать, принося солидную прибыль. Дела у фирмы пошли резко в гору и всё было хорошо. Но однажды станок сломался и остановился. Все попытки отремонтировать его были безуспешны.
Тогда директор фирмы объявил, что тот, кто запустит станок получит 1000 долларов.
Много лучших умов и рук трудились, но у них ничего не выходило.
И тут пришёл простой слесарь из цеха, походил, посмотрел, взял кувалду и ударил ей станок. Всё заработало как раньше.
Вызвал рабочего довольный директор к себе и сказал, чтобы тот написал отчёт о выполненной работе для получения обещанной премии.
Рабочий написал: 1 доллар за то, что ударил кувалдой и 999 долларов за то, что знал куда бить."
Ну и зачем все "эти много букв"? Что сказать то хотел?
В этом случае разработчик оказывается в ситуации слесаря из анекдота выше по тексту.
Есть сложная штука, которую нужно починить одним "ударом молотка"- ограниченным "точечным" воздействием, т.е. рефакторингом как можно меньшей части уже существующего кода\приложения.
Вот о том, как можно подходить к оптимизации в такой ситуации я и хотел поговорить.
Подход к оптимизации ПО одинаковый или очень похожий для многих распространенных приложений. Да и в целом для многих процессов, включая организационные.
В этот момент принято вспоминать о теории очередей, пропускной способности, утилизации ресурсов, времени обслуживания, управляемой деградации, моделировании высоконагруженных систем и прочих, мес��ами интересных а местами не очень, понятиях.
Все они конечно важны, и как мы знаем, "нет ничего практичнее хорошей теории", но в рамках статьи постараемся их по возможности не использовать.
Будем работать в любимом мной формате - шпаргалка краткая для практика, с ссылками на литературу для углубленного изучения.
И да, рассматривать всe будем на примере SAP S4HANA.
Общий подход к оптимизации ПО
Итак, на входе у нас незнакомая программа, и требование "сделать красиво" ускорив ее выполнение в N раз.
С чего начать?
Шутишь, начинать нужно всегда с начала.
2. Определить цель оптимизации производительности
Фундамент успеха закладывается на первом шаге. А именно, нужно понять в чем наша цель. Что именно мы оптимизируем? Не с точки зрения техники, а с точки зрения бизнеса.
Оптимизация ради оптимизации - кредо неудачника :) Ну или технического эксперта стремящегося к совершенству. К сожалению, а может и к счастью, чистое искусство в коммерческих проектах не оплачивается.
Требования к производительности:
Должны опираться на конкретные бизнес показатели Что конкретно не так? Система не может выполнить нужно количество операций в единицу времени? Например, фоновое задание должно обработать 1 млн. заказов ночью за 3 часа и обязательно успеть к 7 ч утра. Или отклик на нажатие кнопки не должен превышать 0.5 сек, иначе покупатели прерывают оформление покупки на кассе самообслуживания.
Находится в приемлемом диапазоне. Не слишком высокие (будут большие затраты на достижения требуемой производительности ) и не слишком низкие (будет просадка по бизнес показателям ). Часто требования необоснованно завышают, а потом удивляются, почему доработка обходится так дорого. Принцип Парето 80\20 и Баланс это слова которые вам придется прочувствовать на своей шкуре.
Крайне важно получить от бизнеса\постановщика задачи необходимую пропускную способность (количество одновременно работающих пользователей\кол-во транзакции в секунду\кол-во отчетов). Где высокие нагрузки, там и высокая параллельность. Было бы странно оптимизировать отчет под один запуск\одного пользователя, а потом удивляться, что запуск двух-трех одновременно убил систему. Такое тоже бывает.
Часто встречающиеся способы сформулировать требования к производительности:
▪ Время отклика (response time);
▪ Количество операций в единицу времени (throughput);
▪ Количество обрабатываемых данных в единицу времени (throughput);
▪ Ограничения на утилизацию ресурсов (utilization);
▪ Количество одновременно работающих пользователей системы.
Будем считать что мы поняли какова наша цель - определили критический параметр, четко\измеримо сформулировали задачу и что немаловажно, зафиксировали ее в письменном виде. Не проговорив ртом, а написав буквами. Например в виде ТЗ, ФС или просто в email с подтверждением от постановщика задачи.
3. Получить эталонные тестовые случаи
На этой стадии мы понимаем что мы хотим достичь, но не понимаем где находимся. Поэтому обязательный шаг перед началом каких либо изменений в уже существующем решении\программе - замер параметров производительности.
А для этого нужно определиться как мы будем проводить замер и на каких примерах.
В этой точке нужно вспомнить про Критичные сценарии использования и связь с ними параметров производительности.
Попытаемся сформулировать что нам нужно от тестового сценария:
▪ Для проверки выполнения требований по производительности тестовый сценарий
должен быть верифицирован\согласован.
▪ Тестовый сценарий должен описывать точки измерения выбранного параметра производительности ( например времени выполнения определенных операций )
▪ Для нагрузочного моделирования необходимо разработать типовые модели
нагрузки. ( Один пользователь выполнил одну операцию. Один пользователь массово выполнил N операций. X пользователей выполнили N операции, и т.д )
▪ Определить последовательность действий в рамках каждого сценария с указанием
временных характеристик этапов.
4. Замерить и зафиксировать текущую производительность решения
После получения воспроизводимых тестовых сценариев\тестовых случаев, вам вам необходимо выполнить замеры производительности и зафиксировать результаты.
С "выполнить замеры", сделать трассировку работы программы - все понятно, это мы умеем.
Применительно к SAP, решение по умолчанию, это снять трассировки выполнения программ с использованием транзакций SAT, ST12, ST05.
Но почему выделено зафиксировать?
А потому что оценку эффективности сделанных оптимизаций в дальнейшем будет вестись на основании этого значения.
Оптимизация это по своей сути процесс последовательного выдвижения гипотез о методах улучшения производительности системы, их реализации и оценки результата.
Поскольку результаты не всегда ожидаемые, вы можете получить ухудшение параметров и это потребует отката неудачных изменений.
Отследить влияние конкретных изменений во всех возможных случаях использования программы невозможно. В том числе потому, что сделать воспроизводимый тест локальной программы\модуля в рамках большой системы сложно, есть много неучтенных факторов.
Вы можете столкнутся с ситуацией когда заказчик оптимизации скажет - вы "все сломали" даже после отката всех сделанных изменений.
Вот в этом случае, корректно выполненный первоначальный замер производительности по согласованному тестовому сценарию поможет вам избежать лишних споров, и ... гхм, неприятных персональных последствий.
Поэтому, всегда фиксируйте результаты перед проведением оптимизации.
5. Определить проблемные места с точки зрения производительности
Итак, мы сняли трассировку выполнения программы, иии... начинаем ее анализировать.
Для начала вспоминаем цели оптимизации - зачем мы это делаем?
Наиболее часто встречающиеся на практике цели, это:
Оптимизация по скорости выполнения (время отклика, общее время выполнения)
Оптимизация по потреблению ресурсов(потреблению памяти, нагрузке на CPU)
Подход общий для анализа по любому отдельно выделенному параметру.
В этот момент конечно, неплохо бы вспомнить, что разные архитектурные характеристики\атрибуты каче��тва часто конфликтуют между собой, т.е.. оптимизация может быть многокритериальная.
Например:
уменьшил время отклика -> растет потребление памяти
уменьшил потребление памяти -> увеличилась нагрузка на CPU и т.д
Но, мы же не будем сразу начинать с постройки космолета, и разберем простой, всем понятный и часто встречающийся случай оптимизации по одному параметру, с фиксацией\ограничением остальных. Программа начала работать быстрее, и при этом потребление ресурсов не изменилось в худшую сторону? Все Ок, заверните, покупаем.
Наша цель на текущем этапе - понять где происходит наиболее серьезная просадка по оптимизируемому параметру. Нужно найти узкое место - его в зависимости от контекста называют критический компонент, бутылочное горлышко\bottleneck, горячая точка\hotspot.
Почему это важно? Нам нужно получить максимальный результат, минимальными усилиями.
Чем больше изменений в архитектуру\код программы вы вносите, тем больше усилий вам потребуется на разработку, последующее тестирование и исправление внесенных ошибок.
Соответственно, эффективнее всего, вносить изменения только в те части программы, которые оказывают максимальное влияние на производительность, причем не на "сферическую производительность в вакууме", а на значение конкретного оптимизируемого параметра.
Для многих разработчиков это психологически сложный момент.
Кому то хочется переписать все с нуля, кто то не может пройти мимо неоптимально написанного кода в любом месте программы.
А кто то наоборот, до ужаса боится взять на себя ответственность за изменения в критичном участке кода, и поэтому вносит правки только в безопасных и часто мало влияющих на целевой показатель компонентах.
В общем, в этот момент нужно взять себя в руки, и выбрать "правильное место для удара".
6. Определите изменения, их стоимость и выигрыш от применения
Итак, после проведения трассировки\профилирования программы, мы нашли потенциальные места для оптимизации.
Часто их несколько, но нам нужно выбрать одно место, внесение изменений в которое принесет максимальный эффект.
Какие критерии используются для выбора?
В трассировке мы видим что максимум времени\ресурса тратиться в этой точке.
Эмпирическое правило:
Тратится 20 и больше % времени. Бинго, мы идем к вам.
Меньше 10 % - пока отложим.
В тяжелых случаях в программе нет явных точек для оптимизации, и трассировка показывает равномерное распределение времени работы программы. Скажем по 1-2 % на каждый модуль.
Тут возможны два варианта:
Программа в целом написана плохо, и нужно методично идти по всей реализации и последовательно переписывать код\запросы.
Программа уже не раз оптимизировалась, из нее выжали все что могли. В этом случае, приходится рассматривать изменение архитектуры или еще раз уточнить актуальность\корректность требований к производительности с точки зрения бизнеса.
В этом месте лучше в явном виде сформулировать гипотезу которую вы будете проверять.
Зачем?
Начиная вносить изменения в код, разработчики часто скатываются на путь ковровой оптимизации, т.е. вносят исправления во все что видят в выбранном модуле\компоненте.
Иногда это правильное решение, но все же часто приводит к излишнему расходу времени и ресурсов.
Избежать этого достаточно просто. Нужно в явном виде сформулировать что вы делаете и зачем, прямо текстом, буквами описать свою гипотезу.
В качестве основы можем взять довольно популярное описание цикла генерации и проверки гипотез - HADI. Подробное описание приводить не буду, можно поискать, их есть в наличии.
Цикл HADI состоит из четырех последовательных шагов:
Hypothesis (Гипотеза): Формулируется предположение по шаблону: «Если {действие}, то {метрика} изменится на {величина } потому что { объяснение }.
Action (Действие): Проведение эксперимента для проверки гипотезы. Вносим изменения в настройки\изменяем алгоритм\добавляем индекс для таблицы БД и т.д.
Data (Данные): Сбор количественных или качественных данных, полученных в ходе действия. Выполняем повторный замер\трассировку, и смотрим как внесенное изменение повлияло на вашу метрику\параметр оптимизации.
Insights (Выводы): Анализ собранных данных и подтверждение или опровержение гипотезы. В этой точке мы должны определить, помогло ли внесенная доработка, оставляем или откатываем? Продолжаем развивать направление работ, или нужно посмотреть в другую сторону.
Сначала кажется что это бесполезный, никому не нужный формализм. Однако, во-первых, это удержит ваше внимание на том что вы делаете, а во-вторых, позволить проанализировать то что уже было сделано, каких результатов удалось добиться, и сформулировать новую гипотезу для оптимизации.
7. Выберите и реализуйте изменения
Основные подходы к оптимизации в порядке значимости:
Оптимизация БД
Оптимизация интеграции
Оптимизация неэффективных алгоритмов
1. Оптимизация БД
Ключевые задачи:
Минимизация времени выполнения запросов
Снижение блокировок и конкуренции транзакций
Улучшение масштабируемости базы данных
Сокращение загрузки сети и диска
Методы:
Профилирование запросов и анализ планов выполнения (EXPLAIN, профайлеры СУБД)
Индексация: создание и оптимизация индексов (включая составные и полнотекстовые)
Нормализация/денормализация: баланс между избыточностью данных и производительностью запросов
Оптимизация транзакций: минимизация длительности блокировок, использование уровней изоляции с учётом требований консистентности
Партиционирование данных: горизонтальное или вертикальное для масштабируемости
Кэширование на уровне данных: материализованные представления, in-memory кэширование
Оптимизация операций записи: batch-операции, уменьшение числа коммитов
Использование специальных СУБД и технологий: NoSQL, колоночное хранилище для аналитики и проч.
2. Оптимизация интеграции
Ключевые задачи:
Минимизация задержек при обмене данными между подсистемами
Уменьшение избыточного сетевого трафика и загрузки на интеграционные шины
Повышение устойчивости и отказоустойчивости интеграций
Методы:
Анализ и профилирование интеграционных транзакций (трассировка передачи данных, анализ логов)
Оптимизация протоколов и форматов обмена: выбор более лёгких форматов представления, сжатие данных
Асинхронная интеграция: внедрение очередей сообщений (Kafka, RabbitMQ)
Пакетная обработка и агрегация запросов для снижения количества вызовов\уменьшения накладных расходов
Кэширование результатов запросов к внешним системам
Оптимизация маршрутизации и балансировки нагрузки на интеграционные точки
Мониторинг и оповещения по интеграционным задержкам и ошибкам
3. Оптимизация неэффективных алгоритмов
Ключевые задачи:
Уменьшение временной и пространственной сложности вычислений
Повышение производительности критичных\"горячих" участков кода
Улучшение отзывчивости и снижения нагрузки на систему
Методы:
Анализ алгоритмов: оценка сложности (Big O), поиск узких мест на уровне кода
Рефакторинг и улучшение алгоритмов: использование более эффективных алгоритмов и структур данных (например хэш-таблиц)
Параллелизация и асинхронное выполнение
Оптимизация работы с памятью: снижение аллокаций, использование пулов объектов
Мемоизация и кэширование промежуточных результатов
Использование специализированных библиотек и аппаратных ускорителей (SIMD, GPU)
Профилирование CPU и памяти на уровне функций/методов
Выводы\заключение и прочее разное
В каждый пункт из написанных выше, можно провалиться как в Кроличью нору из всем известного произведения Льюиса Кэрролла «Алиса в Стране чудес». Провалиться и бесконечно обсуждать особенности применения какого то специфического метода оптимизации производительности, а так же сравнительных характеристик его альтернатив.
Поэтому, СТОП. Сделаем паузу, пока все это не превратилось в очередной лонгрид который никто не дочитывает до конца:)
В конце хочу привести короткий план\чеклист из обязательных шагов, который вы можете применять в своей работе.
Основные шаги проведения оптимизации производительности
Определить цель\параметры оптимизации
Получить\подготовить эталонные тестовые случаи
Провести тесты производительности и зафиксировать результаты
Определить проблемные места с точки зрения производительности
Определить изменения, их стоимость и выигрыш от их применения
Выбрать и реализовать изменения
Проанализировать результат. В случае необходимости, повторить .. Сontinue everything\непрерывное совершенствование и вот это все
И конечно хочется пожелать успехов в этом сложном, но от этого не менее интересном занятии.
"Безумству храбрых поем мы песню" :)
Список материалов
Материалов можно было бы привести много, но с учетом того, что первоначально статья была ориентирована на SAP разработчиков, не буду их разочаровывать и приведу набор базовых материалов по производительностия для платформы SAP S/4HANA.
Optimize SAP HANA Performance ( ссылки на sap note, например стоит посмотреть 2000002 - FAQ: SAP HANA SQL Optimization )
