Singleton Post

Шпаргалка про производительность

| Comments

Участвуя в конкурсе UA Web Challenge я описал в одном документе всю информацию про причины снижения произвоительности, которые я знаю. Буду рад, если это кому-то пригодится. Также буду рад дополнениям и изменениям.

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

Горизонтальное масштабирование

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

Плюсы:

  • Невероятно низкое время реализации. Современные облачные платформы вроде AWS или Heroku позволяют поднять новый инстанс в течении нескольких минут. В случае собственного кластера может быть дольше и дороже, но если он качественно администрируется, добавления ещё одного сервера и развёртывание на нём приложения также не дол жно занять много времени

Минусы:

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

Балансировка нагрузки

Самым простым алгоритмом работы балансировщика нагрузки является Round-robin, который передаёт запросы на обработку различным серверам “по кругу”, т.е., каждому по очереди, в независимости от его загруженности. Такой подход может быть не эффективным при большом количестве запросов, потому что никак не страхует от ситуации, при которой сложные, требующие большого количества ресурсов на обработку, запросы попадают на сервер, который использует свои мощности на максимуме, в то время как другой простаивает. Помочь может использование другого алгоритма, который передаёт запрос на обработку тому серверу, у которого на данный момент меньше всего занятых ресурсов.

Плюсы:

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

Минусы:

  • Требуется время на реализацию (или использование существующего) решения

Память

Другой проблемой может быть нехватка памяти. Например, при увеличении количества запросов к приложению увеличился и объём данных, которые загружаются из БД и хранятся в памяти. Когда место в памяти закончилось, ОС начала использовать swap на жёстком диске, а работа с ним, как известно, в разы медленнее работы с оперативной памятью. Что можно сделать в этом случае?

Добавить памяти

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

Плюсы:

  • Может быть быстро и не очень дорого

Минусы:

  • Временное решение, поскольку нагрузка может увеличиваться практически неограниченно, а объём памяти - нет.
  • В некоторых случаях может не помочь. Если существует утечка памяти, она может съесть любой объём.

Проверить на утечки памяти

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

Плюсы:

  • “Фундаментальное” решение.

Минусы:

  • Может занять непредсказуемое время. Профилирование и сравнение снэпшотов - довольно кропотливое и долгое занятие.

Настроить сборку мусора

Платформы, которые имеют сборщик мусора (JVM, .NET etc), как правило, предоставляют широкие возможности по его тонкому тюнингу. Сам процесс сборки мусора является достаточно ресурсоёмким и может быть причиной снижения производительности.

Плюсы:

  • Может привести к увеличению производительности (как существенному, так и не очень)

Минусы:

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

Канал связи

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

Увеличить канал

Плюсы:

  • Может помочь, но, как и в случае с памятью, количество трафика может расти быстрее скорости увеличения канала.

Минусы:

  • Не всегда возможно, либо не всегда возможно быстро
  • Может быть очень дорого

Уменьшить количество данных

Уменьшение объёма передаваемых данных может быть сделано разными способами, которые зависят от типа этих данных. В случае с бинарными данными, вроде изображений или видео, можно использовать решения вроде AWS S3, оптимизированные для подобных задач, и таким образом перенаправить трафик на другой канал данных, освободив свой собственный.

В случае с REST API, который, например, возвращает массив JSON-объектов, можно пересмотреть формат возвращаемых данных. Например, можно уменьшить названия полей в объекте (“lat” вместо “latitude”, “long” вместо “longitude”, “app” вместо “application” и т.д.). Это напрямую уменьшит размер HTTP ответа и поможет выиграть несколько десятков процентов от загрузки канала.

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

Плюсы:

  • В некоторых случаях может принести большой эффект, с запасом

Минусы:

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

База данных

Шардинг

Для уменьшения нагрузки на сервер БД можно разнести данные по нескольким серверам (например, все данные о заказах с чётными индексами хранить на одном сервере, а с нечётными - на другом).

Плюсы:

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

Минусы:

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

Пул соединений

Плюсы:

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

Минусы:

  • Вычислить правильный размер пула не всегда просто, однако способы есть.

Оптимизация запросов

Данная проблема особенно актуальна при использовании ORM, так как тот SQL-код, который ими генерируется, не обязательно будет оптимальным. Например, необходимо быть уверенным, что запрос на выборку данных из таблицы А не выгружает заодно и данные из таблицы Б, которая связана с А отношением один-ко-многим (естественно, если данные из Б в данном случае не нужны).

Плюсы:

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

Минусы:

  • Поиск проблемных запросов может занять неопределённе время.
  • Их оптимизация - тоже.

Кэш второго уровня (результатов запросов)

Плюсы:

  • Достаточно простая реализация при использовании некоторых стандартных технологий (например, Hibernate + Ehcache)

Минусы:

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

Отказ от реляционной модели

NoSQL сейчас в моде, и в некоторых случаях он действительно может быть полезен. Проблема в том, что нельзя просто заменить MySQL на MongoDB и ожидать большого прироста производительности. Модель данных определяет способы работы с этими данными, и её изменение влечёт за собой изменения способов взаимодействия. Это требует серьёзного “сдвига парадигмы” у разработчиков. Многократное увеличение производительности в случаях, когда данные хорошо ложатся на новую модель данных (document-oriented, column-based, key-value etc).

Плюсы:

  • Как правило, более простое горизонтальное масштабирование.

Минусы:

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

Асинхронное выполнение задач

Часть задач может быть выполнена уже после того, как пользователь получил ответ от сервера. Например, если одно из его действий должно инициировать отправку ему электронной почты, он может и не заметить, если почта придёт не через 3 секунды, а через полминуты. Подобные задачи можно ставить в очередь, а их обработку поручить другим серверам, которые не будут отвечать на HTTP-запросы, а будут заниматься только подобными асинхронными, background, задачами.

Плюсы:

  • Снижает время отклика для некоторых запросов (как правило, не для всех)
  • Освобождает ресурсы серверов, которые отвечает на запросы

Минусы:

  • Реализация может потребовать достаточно много времени
  • Не все задачи можно выполнять асинхронно

Статические файлы

Кэширование статики

Плюсы:

  • Уменьшает нагрузку как на веб-сервер, так и на канал

Минусы:

  • Если большинство клиентов - не браузеры (а, например, мобильные клиенты или другие веб-сервисы), они могут не поддерживать кэширование (HTTP-заголовки вроде Cache-Control и Expires), и изменения не будут иметь смысла

CDN

Специализированные сервисы для раздачи статики, вроде AWS S3 или CloudFront, которые уже оптимизированы для таких задач.

Плюсы:

  • Уменьшает нагрузку как на веб-сервер, так и на канал
  • В некоторых случаях может быть быстро и просто

Минусы:

  • В некоторых случаях может быть долго и сложно
  • Увеличение расходов на инфраструктуру

Внешние сервисы

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

Замена внешнего сервиса внутренним

Плюсы:

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

Минусы:

  • Реализация может занять длительное время
  • Увеличение количества поддерживаемого кода.

Сделать работу с внешними сервисами асинхронной

См. раздел Асинхронное выполнение задач

Несоответствие технологий

Самый крайний случай. Все, наверное, знают историю про то, что Твиттер изначально был написан на Ruby on Rails, и быстрый рост популярности приводил к частым простоям сервиса. Было принято решение перейти на JVM (Java + Scala), и через некоторое время количество даунтаймов существенно снизилось.

Может оказаться, что выбранная платформа плохо подходит для высокой нагрузки (например, в Ruby на тот момент не было “честных” потоков, и работа с памятью в JVM сделана на порядок эффективней), и в долгосрочной перспективе может оказаться выгодным поменять технологический стек.

Плюсы:

  • Любимая программистами возможность “выбросить всё и начать с нуля”

Минусы:

  • Ни о каком “быстром решении” не может быть и речи - будет долго и, соответственно, дорого.

Comments