Урок по оптимизации серверной части веб-приложений



    Привет, Хабр! Меня зовут Алексей Приставко, я директор по веб-проектам в компании DataLine. Моя сегодняшняя статья посвящена тому, как исправить или предотвратить проблемы с производительностью бэк-энда веб-приложений.

    Речь пойдет о том, как оптимизировать веб-приложения, которые страдают от хронических проблем с масштабируемостью, производительностью или надежностью.
    Всем заинтересовавшимся – добро пожаловать под кат!
     

    Терминология


    Давайте для начала разберемся в терминологии. Говоря о производительности веб-проектов или веб-систем, я в первую очередь имею в виду back-end и серверную составляющую. То, что происходит при загрузке страниц в браузере – это совершенно другая история, которой, скорее всего, посвящу отдельную статью.

    • Мерилом производительности приложения у нас будет являться количество обрабатываемых запросов в секунду (RPS) и скорость их выполнения (TTFB – Time to First Byte).
    • Соответственно, под масштабируемостью системы мы будем понимать пул возможностей для увеличения RPS.

    Теперь о надежности. Здесь обязательно нужно разделять два понятия: отказоустойчивость и катастрофоустойчивость.

    • Устойчивость к отказам – способность системы при отказе одного или нескольких серверов к продолжению работы в рамках требуемых параметров.
    • Устойчивыми к катастрофам считаются системы, имеющие полное дублирующее резервирование  (т.н. второе плечо) и способные без сильной просадки работать при полном отказе одного из дата-центров.

    При этом катастрофоустойчивая система ≠ отказоустойчивая система. Ситуация, в которой катастрофоустойчивая, но не отказоустойчивая система продолжает работать только на одном «плече», вполне нормальна. Но если откажет один из серверов, система также выйдет из строя.

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

    С чего начать оптимизацию




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

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



    «Что? Где? Когда?» — оптимизируем запросы


    Особое внимание уделите синхронным запросам. Напомню, это такие запросы, когда в одном и том же потоке мы отправляем запрос и ждем по нему ответа.  Тут как раз кроются причины серьезных тормозов, когда на другой стороне что-то идет не так. Поэтому, если можете сократить число синхронных запросов или заменить их на асинхронные, сделайте это.

    Вот маленькие хитрости, которые помогут вам отследить запросы:

    • Присваивайте каждому входящему запросу уникальный идентификатор. В Nginx для этого есть встроенная переменная $request_id. Передавайте идентификатор в заголовках на back-end и пишите во все логи. Так вы сможете удобно трассировать запросы.
    • Логируйте не только конец запроса к внешнему компоненту, но и его начало. Так вы измерите реальную продолжительность отработки внешнего вызова. Она может существенно отличаться от того, что вы видите в удаленной системе, например, из-за проблем с сетью или тормозов DNS.

    Итак, данные собраны. Теперь разберем проблемные точки. Определите:

    • Где тратится больше всего времени?
    • Куда приходит наибольшее количество запросов?
    • Куда приходят самые «долгие» запросы?

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

    Совет: Если какая-либо точка «собирает» множество мелких запросов, попробуйте объединить их в один большой запрос для сокращения накладных расходов. Результаты долгих запросов часто имеет смысл сохранить в кэш.

    Кэшируем с умом


    Существуют общие правила кеширования, на которые стоит опираться при оптимизации:

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

    Если десять веб-серверов делают одинаковые запросы к базе данных, централизованный промежуточный кэш, например в Redis, даст более высокий процент попаданий (по сравнению с локальным кэшем) и снизит общую нагрузку на БД, что существенно улучшит общую картину.

    Совет 1: Делайте компонентное кеширование готовой странички на стороне Nginx с помощью Edge Side Includes. Оно хорошо ложится на микросервисную/SOA архитектуру и разгружает систему в целом, значительно улучшая скорость отклика.

    Совет 2: Следите за размером объектов в кэше, показателем hit ratio и объемами записи/чтения. Чем больше объект, тем дольше он будет обрабатываться. Если вы пишете в кэш чаще или больше, чем читаете, такой кэш — вам не товарищ. Его стоит или убрать, или подумать над повышением его эффективности.

    Совет 3: Используйте собственные кэши баз данных там, где это возможно. Их правильное конфигурирование может качественно ускорить работу.

    Профили нагрузки


    Переходим к профилям нагрузки. Как вы знаете, есть два основных типа: OLAP и OLTP.

    • Для OLAP (Online Analytical Processing) важно количество отработанного трафика в секунду.
    • Для OLTP (Online Transaction Processing) ключевой показатель — скорость отклика, миллисекундные тайминги.

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

    Совет: Запросы на чтение из админки, как правило, обрабатываются по типу OLAP. Создайте под эту задачу отдельную копию БД и веб-сервер, чтобы разгрузить основную систему.

    Базы данных




    Итак, мы закономерно подошли к одному из самых сложных этапов оптимизации — а именно, к оптимизации базы данных.

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

    По возможности храните исторические данные, логи приложения и часто используемые данные в разных базах данных. Еще лучше — разнесите их на разные сервера. Это не только облегчит жизнь основной БД, но и даст больше пространства для дальнейшей оптимизации, к примеру в ряде случаев позволит использовать разные индексы под разную нагрузку. Также «однотипность» нагрузки упрощает жизнь планировщику и оптимизатору запросов сервера БД.

    И снова о важности планирования


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

    • Под мелкие, но частые запросы лучше взять больше ядер процессора.
    • Под тяжелые запросы – меньше ядер с более высокой тактовой частотой.

    Постарайтесь поместить рабочий объем базы данных в оперативную память. Если это невозможно или имеет место большое количество запросов на запись пора посмотреть в сторону перенести базы данных на SSD-диски. Они дадут существенный прирост скорости работы с диском.

    Масштабирование




    Выше я описал ключевые механики повышения производительности приложения без увеличения его физических ресурсов.

    Теперь мы поговорим о том, как выбрать стратегию масштабирования и повысить отказоустойчивость.

    Существует два вида масштабирования системы:

    • вертикальное — рост  объема ресурсов при сохранении количества сущностей;
    • горизонтальное — рост количества сущностей.

    Растём в высоту


    Начнем с выбора стратегии вертикального масштабирования.

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

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

    Если в вашей системе несколько серверов и стоит выбор — увеличить мощность существующих или докупить еще несколько, обратите внимание на финансовую сторону. Например, один мощный сервер может оказаться дороже, чем два на 50% «слабее». Поэтому резонно будет остановиться на втором компромиссном варианте. В то же время, при большом количестве серверов решающее значение имеет соотношение производительности, энергопотребления и стоимости полной стойки.

    Растём в ширину


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

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

    • Для хранения сессий используйте Couchbase, а не привычный Memcached, так как он работает с тем же протоколом, но, в отличие от memcached, поддерживает кластеризацию.
    • Всю статику, особенно большие объемы изображений и документов, храните отдельно и отдавайте с помощью Nginx, а не из кода приложения. Так вы сэкономите на потоках и облегчите управление инфраструктурой.

    «Подтягиваем» базы данных


    Сложнее всего масштабировать базы данных. Для этого есть две основные техники: шардирование и тиражирование. Рассмотрим их.

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

    Помните: зачастую кластер БД состоит из одной master-ноды, принимающей на себя поток записи, и нескольких slave-нод, используемых для чтения. С точки зрения отказоустойчивости, это немногим лучше одиночного сервера, так как общая отказоустойчивость определяется наименее устойчивым элементом системы.

    Схемы с более, чем двумя мастерами баз данных (топология «кольцо») без подтверждения записи на каждом из серверов, очень часто страдают от неконсистентности. В случае сбоя одного из серверов восстановить логическую целостность данных в кластере будет крайне затруднительно.

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

    Совет: Если есть необходимость держать более 2-х мастеров баз данных, рекомендую вам рассмотреть NoSQL-решения, так как многие из них имеют встроенные механизмы приведения данных в консистентное состояние.

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

    Помните: Любые важные данные необходимо бэкапить и хранить в виде независимой не редактируемой копии.

    Вместо заключения


    Напоследок — пара советов про производительность при создании резервных копий:

    Совет 1: Снимайте данные с отдельной реплики базы данных, чтобы не нагружать активный сервер сверх меры.

    Совет 2: Имейте под рукой дополнительную, слегка «отстающую» по времени реплику базы данных. В случае аварии это поможет уменьшить количество потерянных данных.

    Приведенные в этой статье методы и техники ни в коем случае нельзя применять вслепую, без анализа текущей ситуации и понимания, чего бы вам хотелось достичь. Вы можете столкнуться с «переоптимизацией», и полученная система окажется лишь на 10% более быстрой, но на 50% – более уязвимой к авариям.

    На этом всё. Если у вас остались какие-то вопросы, я с удовольствием отвечу на них в комментариях.
    • +17
    • 8,2k
    • 2
    DataLine 83,09
    Экосистема на базе дата-центров TIER III
    Поделиться публикацией
    Комментарии 2
      +1
      Спасибо, очень «полезная» и «информативная» статья, уровня нынешнего хоброхабра.
        +2

        Основные технологии в одном месте, а конкретные примеры можно и самим найти. Спасибо за статью.

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

        Самое читаемое