Не секрет, что разработчикам программных систем часто приходится решать проблемы производительности, высокой нагрузки, обработки больших объемов данных и отказоустойчивости. В идеале, все эти вопросы учитываются при проектировании системы. Но на практике их часто пытаются решить запоздалыми «оптимизациями» после запуска.
Почему так происходит? Обеспечение высокой производительности и надежности ошибочно почитается многими за «черную магию». И неспроста — чуть ли не в каждой книге или статье на эту тему вы первым делом наткнетесь на утверждение типа «нельзя просто так взять и повысить производительность».
Книжные авторитеты советуют нам сначала определяться с целями, строить какие-то модели нагрузки, обсчитывать требования к аппаратным ресурсам, тестировать свои предположения, и заниматься тому подобной шелухой, не имеющей отношения к реальному делу. При всем этом они еще и не дают конкретных советов. Что я должен поменять в своей системе, чтобы она стала быстро работать? Нет ответа.
Рискуя навлечь на себя гнев высоколобых теоретиков софтостроения, скажу: есть конкретные, понятные практики, следуя которым, вы на 99% решите все проблемы быстродействия, надежности и доступности вашего ПО. И я готов с вами этими практиками поделиться. Сразу отмечу, что речь идет прежде всего о серверных приложениях с бизнес-логикой более сложной, чем обычный CRUD.
Итак, поехали — 7 практических советов повышения производительности, которые реально работают.
Не вникайте в базу данных
Ваша система наверняка использует некоторое хранилище данных. Так вот, не надо сильно вникать в принципы и механизмы его работы, а также многообразие настроек.
Используете классическую реляционную базу типа Oracle? Если возникнут проблемы, то есть специально обученные специалисты СУБД, они подкрутят настройки где надо, и все будет хорошо. Используете NoSQL-хранилище вроде MongoDB? Так там и вовсе ничего знать не надо, разработчики 10gen уже обо всем позаботились за вас.
Не надо думать за разработчиков СУБД. У вас есть ORM или клиентская библиотека — вот ее и используйте. Как оно там работает на стороне хранилища, никого не волнует. К тому же, вдруг вы захотите сменить движок БД? Нельзя «затачиваться» на специфику конкретной базы.
Одиночные операции вместо пакетных
Предположим, вашей системе нужно обработать миллион объектов, информация по каждому из которых лежит в базе. Пожалуйста, не пытайтесь обрабатывать их пачками. Вы будете вынуждены писать отдельную логику для извлечения и сохранения пакета объектов в БД, решать вопрос с обработкой ошибок. Да и бизнес-логику менять придется.
Вместо всего этого просто используйте одиночные операции — обрабатывайте один объект за другим. Так база данных будет нагружена равномернее, логика приложения останется простой, а время обработки нисколько не пострадает.
Никаких кэшей
Доморощенные оптимизаторы будут советовать вам использовать кэши (для контента страниц, бизнес-объектов, сложных результатов вычислений и тому подобного). Якобы эти кэши позволяют снизить время отклика и уменьшить нагрузку на систему. Ну да, конечно! А как вы будете решать вопрос с устареванием данных в кэшах? Их непротиворечивостью? Эластичностью? Лишними ресурсами, которые они потребляют?
Мой вам совет — просто не используйте кэши. Дисковые подсистемы в современных ОС сами знают, что и когда кэшировать. Добавьте к этому быстрые SSD-диски, и вы поймете, что время кэшей безвозвратно прошло.
Используйте единственный примитив синхронизации
Ваша система, конечно же, активно использует мультипоточное программирование для эффективной обработки конкурентных запросов. А многопоточность, как вы знаете, влечет за собой проблемы при доступе к разделяемым ресурсам. Как здесь быть? Очень просто: широко применяйте простой и проверенный временем примитив — блок синхронизации, в котором допускается выполнение только одного потока в каждый момент времени.
Не надо использовать высокоуровневые паттерны многопоточности — все эти неблокирующие коллекции, атомарные типы, агенты и тому подобное. Все они переусложнены и навязывают вам совершенно неудобную модель использования (чего стоит один метод compare_and_set, грубо нарушающий принцип single responsibility).
Если потребуется дальнейшая оптимизация, то от блоков синхронизации можно просто избавиться, и система заработает еще быстрее! Конечно, могут возникнуть небольшие проблемы с конкурирующими потоками, но в итоге все будет в порядке (вы наверно слышали, это называется eventual consistency — достаточно актуальная сейчас тема).
Применяйте как можно более простые алгоритмы
Иногда приходится решать алгоритмические проблемы, не предусмотренные вашей стандартной библиотекой. Например, распределить объекты по кластерам, или решить какую-то задачу на графах. В таких ситуациях используйте самый простой алгоритм, который только пришел вам в голову.
Вместо того, чтобы рассуждать об асимптотической сложности и пытаться оценить O-большое для занимаемой памяти, просто возьмите и решите задачу перебором на вложенных циклах. Нет сомнений, что в 95% случаев решение окажется достаточно хорошим. К тому же современные компиляторы прекрасно умеют отлавливать паттерны неэффективных операций в коде и исправлять их прозрачно для программиста.
Используйте умолчательные настройки
Наверняка ваша система работает в каком-то контейнере. Это может быть веб-сервер, сервер приложений, виртуальная машина или что-то еще. Просто доверьтесь им! Нет никакой нужды копаться в многостраничной документации или, тем более, форумах и юзер-группах, в поисках священного грааля оптимизации. Контейнеры разрабатываются неглупыми людьми, и все умолчательные настройки достаточно хороши для вас.
Локальное взаимодействие ничем не отличается от удаленного
Допустим, у вас в системе есть API, который используется локально. Подойдет ли он для взаимодействия по сети, когда клиент может находиться в любой точке земного шара? Конечно, да! Нужно ли как-то его модифицировать, уменьшать гранулярность, особым образом обрабатывать прерывание соединения, вводить дополнительную типизацию ошибок, поддерживать несколько версий интерфейса? Конечно, нет! Библиотеки и фреймворки делают удаленное взаимодействие совершенно прозрачным для клиента и сервера, а стремительный рост пропускной способности и стабильности глобальных сетей нивелирует их отличие от сетей локальных.
Я неоднократно видел, как описанные выше принципы успешно применяются разработчиками в самых разных организациях и проектах. И они неизменно срабатывали! По оценке самих разработчиков, системы получались исключительно быстрые и надежные. Возникали, конечно, иногда некоторые проблемы с их использованием. Но это частности, вызванные несовершенством окружения в котором вынуждена работать система, и они не заслуживают серьезного рассмотрения.
Смело берите 7 советов на вооружение, и вы очень скоро увидите результат в своей системе!
P.S. Иллюстрации любезно предоставлены пользователями Flickr:
- turtle run by David Spreekmeester
- oracle headquarters by Dave_B_
- Wild Carts. by arycogre
- synchrotron p0rn by tsc_traveler
- Bubbles revisited, part deux by hebedesign
- Cactus by Holger Zscheyge
Автор: algenon