Сложности и ошибки проектирований высоконагруженных приложений

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

Распространённые проблемы высоконагруженных приложений

Проблемы и ошибки, которые возникают при разработке, бывают разными. Они могут появляться независимо, а могут выявляться в комплексе. Наша задача — как можно раньше их выявлять и предлагать их решение. Разберём самые популярные ошибки. Их всего семь.

1. Неправильный выбор технологий

При проектировании высоконагруженной системы важно уделить внимание изучению и выбору подходящих технологий. Для этого нужно провести тестирование и определить, соответствует ли технология конкретно вашим задачам.
Например, сервис Dropbox изначально использовал Python для своей платформы, но с ростом пользовательской базы перешёл на Go для определённых компонентов, чтобы улучшить производительность и эффективность. Это отняло много времени и ресурсов, но дало свои плоды.

2. Недооценка нагрузки и некорректное тестирование

Недостаточно реалистичное тестирование нагрузки ведёт к неожиданным сбоям в производственной среде. Понятно, что не всегда можно предугадать, с какой нагрузкой столкнётся система. Но стоит сделать всё возможное, чтобы определить точные НФТ.
Например, приложение Pokemon Go столкнулось с серьёзными проблемами масштабируемости и доступности в первые дни после запуска из-за недооценки популярности игры и последующей высокой нагрузки на серверы.

3. Неэффективное использование ресурсов

Неоптимальное использование вычислительных мощностей, памяти или других ресурсов, ведущее к избыточным расходам и снижению производительности. Также нужно помнить о том, что ресурсы не бесконечны. Чем больше ваша система потребляет ресурсов, тем дороже она обходится и тем меньше она полезна для бизнеса.
Поэтому необходимо уделять достаточное внимание оптимизации производительности и регулярно выполнять нагрузочное тестирование. Часто проблема решается с помощью использования оборудования и паттернов, которые подходят под ваши сценарии нагрузки.
Например, Instagram* в своё время перешёл с облачной инфраструктуры Amazon EC2 на высокопроизводительные серверы Facebook*, что позволило оптимизировать использование ресурсов и улучшить производительность их сервисов.

4. Пренебрежение оптимизацией запросов к базе данных

Сложные или неправильно оптимизированные запросы к базе данных — одна из самых распространённых ошибок, которые в итоге приводят к высоким задержкам и неэффективному использованию ресурсов.
Нужно использовать средства анализа планов запросов. Существует множество техник и подходов: от оптимизации отдельных запросов и индексирования до рефакторинга всей схемы БД.
Например, LinkedIn столкнулся с проблемами производительности из-за неоптимизированных запросов SQL. Из-за этого компания внедрила специализированные инструменты для анализа и оптимизации запросов, что позволило обрабатывать ей тысячи запросов в секунду.

5. Игнорирование пользовательского опыта при проектировании API

Создание API без учёта удобства и эффективности использования приводит к снижению производительности клиентских приложений. Эту ошибку вы уже разбирали в уроках о проектировании front-to-back взаимодействий. Однако она довольна распространена, поэтому всегда анализируйте, насколько ваш API подходит под пользовательские сценарии.
Например, Slack значительно улучшил производительность своего API, пересмотрев методы и структуру запросов/ответов, используя в том числе GraphQL для сокращения количества запросов и объёма передаваемых данных.

6. Недооценка тестирования

Недостаточное или неполное тестирование системы, особенно в плане нагрузочного тестирования. В случае некачественного тестирования вам будет сложно предсказать, какие узкие места «выстрелят» у приложения в производственной среде. Покрытие тестированием должно стремиться к 100%-му объёму функционала. Если такой возможности нет, то необходимо выделить критичный для системы функционал и сконцентрироваться на нём. Тестирование не должно проходить для «галочки»: это важный инструмент, который позволяет не «тушить пожары» на проде.
Например, компания Twitter после выхода каждой новой версии приложения проводит симуляцию реального трафика и нагрузочное тестирование для предотвращения сбоев.

7. Неверная декомпозиция системы

Часто системы сталкиваются с излишним потреблением ресурсов, которые связаны с неверной декомпозицией системы. Это проявляется из-за слишком гранулярной декомпозиции, что приводит к высоким накладным расходам на обмен данными между компонентами. Также узкие места появляются из-за неверно выбранных границ ответственности, когда функциональность одного компонента «просачивается» в другие. Это приводит к циклическим зависимостям, которые усложняют масштабирование системы.
Например, приложение Amazon Prime Video столкнулось с проблемой слишком гранулярной декомпозиции, которая привела к избыточному взаимодействию между компонентами. В результате компания понесла высокие издержки на ресурсы сети и хранения. После объединения некоторых компонентов команде сервиса удалось сократить затраты на ресурсы почти на 90%.

Задание 1

Компания Amazon Prime Video столкнулась с проблемами из-за слишком гранулярной декомпозиции системы, что привело к высоким издержкам на ресурсы сети и хранения. Что можно сделать для решения этой проблемы в другом высоконагруженном приложении? Выберите все правильные ответы.
Увеличение гранулярности усугубит проблему, так как это приведёт к ещё большим накладным расходам на взаимодействие между компонентами.
Объединение часто взаимодействующих компонентов уменьшит накладные расходы на их взаимодействие, что было одной из проблем в кейсе Amazon Prime Video.
Специализированные серверы позволят оптимизировать использование ресурсов и снизить издержки, как это сделано в примере с Instagram*.
Полный отказ от распределённой архитектуры может значительно ограничить возможности масштабирования системы, что не всегда оказывается правильным решением для высоконагруженных приложений.
Увеличение объёма данных, передаваемых между компонентами, не решает проблему, а, наоборот, увеличивает нагрузку на сеть и ресурсы.

Задание 2

Молодая компания-стартап столкнулась с проблемой масштабируемости своей платформы, построенной на монолитной архитектуре. Из-за роста пользовательской базы система начала страдать от высоких задержек и потерь данных при увеличении нагрузки. Какие шаги помогут компании улучшить масштабируемость и производительность? Выберите все правильные ответы.
Переход на микросервисную архитектуру позволяет разделить систему на независимые компоненты, что облегчает её масштабирование и управление. Этот шаг часто применяется для улучшения производительности высоконагруженных приложений.
Горизонтальное масштабирование — это добавление серверов для распределения нагрузки, что помогает системе выдерживать большие объёмы трафика без потери производительности.
Увеличение вычислительных мощностей серверов может временно улучшить производительность, но не решит проблему с масштабируемостью в долгосрочной перспективе.
Перенос базы данных в облако может повысить доступность, но без изменения архитектуры и способов масштабирования это не решит проблему высокой нагрузки.
Отключение мониторинга системы не улучшит её производительность, а лишит компанию возможности контролировать состояние системы и предвидеть проблемы.

Дополнительные материалы

  • Как Instagram* развёртывал функционал в разных ЦОД и в процессе решал возникающие проблемы
  • Как команда Amazon Prime Video отказалась от микросервисов в пользу монолита ради снижения костов и увеличения производительности — полезно учитывать, когда вы работаете над декомпозицией своего функционала.

Итоги

  • При проектировании высоконагруженной системы важно уделить внимание изучению и выбору подходящих технологий. Для этого нужно провести тестирование и определить, соответствует ли технология конкретно вашим задачам.
  • Недостаточно реалистичное тестирование нагрузки ведёт к неожиданным сбоям в производственной среде. Понятно, что не всегда можно предугадать, с какой нагрузкой столкнётся система. Но стоит сделать всё возможное, чтобы определить точные НФТ.
  • Неоптимальное использование вычислительных мощностей, памяти или других ресурсов, ведущее к избыточным расходам и снижению производительности. Также нужно помнить о том, что ресурсы не бесконечны. Чем больше ваша система потребляет ресурсов, тем дороже она обходится и тем меньше она полезна для бизнеса.
  • Сложные или неправильно оптимизированные запросы к базе данных — одна из самых распространённых ошибок, которые в итоге приводят к высоким задержкам и неэффективному использованию ресурсов.
  • Создание API без учёта удобства и эффективности использования приводит к снижению производительности клиентских приложений. Всегда анализируйте, насколько ваш API подходит под пользовательские сценарии.
  • Недостаточное или неполное тестирование системы, особенно в плане нагрузочного тестирования. В случае некачественного тестирования вам будет сложно предсказать, какие узкие места «выстрелят» у приложения в производственной среде. Покрытие тестированием должно стремиться к 100%-му объёму функционала.
  • Часто системы сталкиваются с излишним потреблением ресурсов, которые связаны с неверной декомпозицией системы. Это проявляется из-за слишком гранулярной декомпозиции, что приводит к высоким накладным расходам на обмен данными между компонентами.
Вот и всё, теория подошла к концу. Впереди вас ждёт проектная работа.
* — сервисы, запрещённые на территории РФ.