Здравствуйте! Я Александр Макаров, и вы можете меня знать по фреймворку «Yii» — я один из его разработчиков. У меня также есть full-time работа — и это уже не стартап — Stay.com, который занимается путешествиями.

Сегодня я буду рассказывать про горизонтальное масштабирование, но в очень-очень общих словах.

Что такое масштабирование, вообще? Это возможность увеличить производительность проекта за минимальное время путем добавления ресурсов.

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

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

Самый классный вопрос, который задают, — а зачем оно надо, если у меня все и на одном сервере прекрасно работает? На самом-то деле, надо проверить, что будет. Т.е., сейчас оно работает, но что будет потом? Есть две замечательные утилиты — ab и siege, которые как бы нагоняют тучу пользователей конкурента, которые начинают долбить сервер, пытаются запросить странички, послать какие-то запросы. Вы должны указать, что им делать, а утилиты формируют такие вот отчеты:


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

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

Есть еще один параметр — Responce time — время ответа, за которое в среднем сервер отдал страничку. Оно бывает разное, но известно, что около 300 мс — это норма, а что выше — уже не очень хорошо, потому что эти 300 мс отрабатывает сервер, к этому прибавляются еще 300-600 мс, которые отрабатывает клиент, т.е. пока все загрузится — стили, картинки и остальное — тоже проходит время.

Бывает, что на самом деле пока и не надо заботиться о масштабировании — идем на сервер, обновляем PHP, получаем 40% прироста производительности и все круто. Далее настраиваем Opcache, тюним его. Opcache, кстати, тюнится так же, как и APC, скриптом, который можно найти в репозитории у Расмуса Лердорфа и который показывает хиты и мисы, где хиты — это сколько раз PHP пошел в кэш, а мисы — сколько раз он пошел в файловую систему доставать файлики. Если прогнать весь сайт, либо запустить туда какой-то краулер по ссылкам, либо вручную потыкать, то у нас будет статистика по этим хитам и мисам. Если хитов 100%, а мисов — 0%, значит, все нормально, а если есть мисы, то надо выделить больше памяти, чтобы весь наш код влез в Opcache. Это частая ошибка, которую допускают — вроде Opcache есть, но что-то не работает…

Еще часто начинают масштабировать, но не смотрят, вообще, из-за чего все работает медленно. Чаще всего лезем в базу, смотрим — индексов нет, ставим индексы — все сразу залетало, еще на 2 года хватит, красота!

Ну, еще надо включить кэш, заменить apache на nginx и php-fpm, чтобы сэкономить память. Будет все классно.

Все перечисленное достаточно просто и дает вам время. Время на то, что когда-то этого станет мало, и к этому уже сейчас надо готовиться.

Как, вообще, понять, в чем проблема? Либо у вас уже настал highload, а это не обязательно какое-то бешеное число запросов и т.д., это, когда у вас проект не справляется с нагрузкой, и тривиальными способами это уже не решается. Надо расти либо вширь, либо вверх. Надо что-то делать и, скорее всего, на это мало времени, что-то надо придумывать.

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

Что может показать мониторинг? Мы можем упереться в диск, т.е. в файловую систему, в память, в процессор, в сеть… И может быть такое, что, вроде бы, все более-менее, но какие-то ошибки валятся. Все это разрешается по-разному. Можно проблему, допустим, с диском решить добавлением нового диска в тот же сервер, а можно поставить второй сервер, который будет заниматься только файлами.

На что нужно обращать внимание прямо сейчас при мониторинге? Это:

  1. доступность, т.е. жив сервер, вообще, или нет;
  2. нехватка ресурсов диска, процессора и т.д.;
  3. ошибки.

Как это все мониторить?

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

Первые 4 инструмента можно поставить на сервер, они мощные, классные. А ServerDensity хостится у кого-то, т.е. мы за нее платим деньги, и она может собирать с серверов все данные и отображать их для анализа.

Для мониторинга ошибок есть два хороших сервиса:

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

Sentry можно поставить к себе на сервер, есть исходник, а Rollbar — нет, но Rollbar лучше, потому что он берет деньги за количество ошибок, т.е. стимулирует их исправлять.

Про нотификации повторю, что спамить не стоит, теряется внимание.

Что, вообще, надо анализировать?


RPS и Responce time — если у нас начинает время ответа падать, то надо что-то делать.

Количество процессов, потоков и размеры очередей — если это все начинает плодиться, забиваться и т.д., то что-то здесь опять не так, надо анализировать более детально и как-то менять инфраструктуру.

Также стоит смотреть на бизнес-анализ. Google Analytics для сайтовых типов отлично подходит, а mixpanel — для логирования ивентов, он работает на десктопных приложениях, на мобильных, на веб. Можно и на основе каких-то своих данных писать, но я бы советовал готовые сервисы. Смысл в том, что наш мониторинг может показывать, что сервис жив, что все работает, что общий Responce time нормальный, но когда мы, допустим, регистрацию в mixpanel"е начинаем трекать, он показывает, что их как-то маловато. В этом случае надо смотреть, насколько быстро отрабатывают определенные ивенты, страницы, и в чем состоят проблемы. Проект всегда должен быть «обвешан» анализом, чтобы всегда знать, что происходит, а не работать вслепую.

Нагрузка, вообще, возникает или запланировано, или нет, может возникать постепенно, может не постепенно:


Как бороться с нагрузкой? Решает все бизнес, и важна только цена вопроса. Важно:

  1. чтобы сервис работал,
  2. чтобы это было не сильно дорого, не разорило компанию.

Остальное не очень важно.


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


На рисунке синяя штука — это Интернет, из которого идут запросы. Ставится балансировщик, единственная задача которого — распределить запросы на отдельные фронтенды-сервера, принять от них ответы и отдать клиенту. Смысл тут в том, что 3 сервера могут обработать (в идеале) в 3 раза больше запросов, исключая какие-то накладные расходы на сеть и на саму работу балансировщика.

Что это нам дает? Указанную выше возможность обработать больше запросов, а еще надежность. Если в традиционной схеме валится nginx или приложение, или в диск уперлись и т.п., то все встало. Здесь же, если у нас один фронтенд отвалился, то ничего страшного, балансировщик, скорее всего, это поймет и отправит запросы на оставшиеся 2 сервера. Может, будет чуть помедленнее, но это не страшно.

Вообще, PHP — штука отличная для масштабирования, потому что он следует принципу Share nothing по умолчанию. Это означает, что если мы возьмем, допустим, Java для веба, то там приложение запускается, читает весь код, записывает по максимуму данных в память программы, все там крутится, работает, на request уходит очень мало времени, очень мало дополнительных ресурсов. Однако есть засада — т.к. приложение написано так, что оно должно на одном инстансе работать, кэшироваться, читать из своей же памяти, то ничего хорошего у нас при масштабировании не получится. А в PHP по умолчанию ничего общего нет, и это хорошо. Все, что мы хотим сделать общим, мы это помещаем в memcaсhed, а memcaсhed можно читать с нескольких серверов, поэтому все замечательно. Т.е. достигается слабая связанность для слоя серверов приложений. Это прекрасно.

Чем, вообще, балансировать нагрузку?

Чаще всего это делали Squid"ом или HAProxy, но это раньше. Сейчас же автор nginx взял и партировал из nginx+ балансировщик в nginx, так что теперь он может делать все то, что раньше делали Squid"ом или HAProxy. Если оно начинает не выдерживать, можно поставить какой-нибудь крутой дорогой аппаратный балансировщик.

Проблемы, которые решает балансировщик — это как выбрать сервер и как хранить сессии? Вторая проблема — чисто PHP"шная, а сервер может выбираться либо по очереди из списка, либо по географии каких-то IP"шников, либо по какой-то статистике (nginx поддерживает least-connected, т.е. к какому серверу меньше коннектов, на него он и будет перекидывать). Можем написать для балансировщика какой-то код, который будет выбирать, как ему работать.


Что, если мы упремся в балансировщик?

Есть такая штука как DNS Round robin — это замечательный трюк, который позволяет нам не тратиться на аппаратный балансировщик. Что мы делаем? Берем DNS-сервер (обычно DNS-сервера у себя никто не хостит, это дорого, несильно надежно, если он выйдет из строя, то ничего хорошего не получится, все пользуются какими-то компаниями), в А-записи прописываем не один сервер, а несколько. Это будут А-записи разных балансировщиков. Когда браузер туда идет (гарантий, на самом деле, нет, но все современные браузеры так действуют), он выбирает по очереди какой-нибудь IP-адрес из А-записей и попадает либо на один балансировщик, либо на второй. Нагрузка, конечно, может размазываться не равномерно, но, по крайней мере, она размазывается, и балансировщик может выдержать немного больше.

Что делать с сессиями?

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

Возникает очевидное желание сделать общую файловую систему, подключить NFS. Но делать так не надо — она до жути медленная.

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

Можно писать в memcached, но очень-очень осторожно, потому что memcached — это, все-таки, кэш и он имеет свойство вытираться, как только у него мало ресурсов, или некуда писать новые ключи — тогда он начинает терять старые без предупреждения, сессии начинают теряться. За этим надо либо следить, либо выбрать тот же Redis.

Redis — нормальное решение. Смысл в том, что Redis у нас на отдельном сервере, и все наши фронтенды ломятся туда и начинают с Redis"а свои сессии считывать. Но Redis однопоточный и рано или поздно можем хорошенько упереться. Тогда делают sticky-сессии. Ставится тот же nginx и сообщается ему, что нужно сделать сессии так, чтобы юзер, когда он пришел на сервер и ему выдалась сессионная coockies, чтобы он впоследствии попадал только на этот сервер. Чаще всего это делают по IP-хэшу. Получается, что если Redis на каждом инстансе, соответственно, сессии там свои, и пропускная способность чтения-записи будет гораздо лучше.

Как насчет coockies? Можно писать в coockies, никаких хранилищ не будет, все хорошо, но, во-первых, у нас все еще куда-то надо девать данные о сессии, а если мы начнем писать в coockies, она может разрастись и не влезть в хранилище, а, во-вторых, можно хранить в coockies только ID, и нам все равно придется обращаться к БД за какими-то сессионными данными. В принципе, это нормально, решает проблему.

Есть классная штука — прокси для memcached и Redis:


Они, вроде как, поддерживают распараллеливание из коробки, но делается это, я не сказал бы, что очень оптимально. А вот эта штука — twemproxy — она работает примерно как nginx с PHP, т.е. как только ответ получен, он сразу отправляет данные и в фоне закрывает соединение, получается быстрее, меньше ресурсов потребляет. Очень хорошая штука.


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

В PHP есть такой механизм как session handler, т.е. мы можем поставить свой handler и писать в coockies, в БД, в Redis — куда угодно, и все это будет работать со стандартными session start и т.д.


Сессии надо закрывать вот этим замечательным методом.

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

Что делать с файлами?

С ними можно справляться двумя способами:

  1. какое-то специализированное решение, которое дает абстракцию, и мы работаем с файлами как с файловой системой. Это что-то вроде NFS, но NFS не надо.
  2. «шардирование» средствами PHP.

Специализированные решения из того, что действительно работает, — это GlusterFS. Это то, что можно поставить себе. Оно работает, оно быстрое, дает тот же интерфейс, что NFS, только работает с нормальной терпимой скоростью.

И Amazon S3 — это, если вы в облаке Amazon"а, — тоже хорошая файловая система.

Если вы реализуете со стороны PHP, есть замечательная библиотека Flysystem, покрытая отличными тестами, ее можно использовать для работы со всякими файловыми системами, что очень удобно. Если вы сразу напишете всю работу с файлами с этой библиотекой, то потом перенести с локальной файловой системы на Amazon S3 или др. будет просто — в конфиге строчку переписать.

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

Лучше всего раздавать файлы nginx"ом или Varnish"ем, но лучше все делать nginx"ом, т.к. мы все его любим и используем — он справится, он хороший.


Что у нас происходит с базой данных?

Если у вас все уперлось в код PHP, мы делаем кучу фронтендов и все еще обращаемся к одной БД — она справится достаточно долгое время. Если нагрузка не страшная, то БД живет хорошо. Например, мы делали JOIN"ы по 160 млн. строк в таблице, и все было замечательно, все бегало хорошо, но там, правда, оперативки надо больше выделить на буферы, на кэш…

Что делать с БД, если мы уперлись в нее? Есть такие техники как репликация. Обычно делается репликация мастер-слэйв, есть репликация мастер-мастер. Можно делать репликацию вручную, можно делать шардирование и можно делать партицирование.

Что такое мастер-слэйв?


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

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

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

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

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

Есть такая штука как read/write split.Делается 2 пула серверов — мастер, слэйв, соединение по требованию, и логика выбора соединения варьируется. Смысл в том, что если мы будем всегда читать со слэйвов, а писать всегда в мастер, то будет небольшая засада:


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

Есть два типа выборок:

  1. для чтения или для вывода;
  2. для записи, т.е., когда мы что-то выбрали и потом это что-то надо изменить и записать обратно.

Если выборка для записи, то мы можем либо всегда читать с мастера и писать на мастер, либо мы можем выполнить «SHOW SLAVE STATUS» и посмотреть там Seconds_Behind_Master (для PostgreSQL тоже супер-запрос есть на картинке) — он покажет нам число. Если это 0 (нуль), значит, все у нас уже реплицировалось, можно смело читать со слэйва. Если число больше нуля, то надо смотреть значение — либо нам стоит подождать немного и тогда прочитать со слэйва, либо сразу читать с мастера. Если у нас NULL, значит еще не реплицировали, что-то застряло, и надо смотреть логи.

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

Что такое мастер-мастер?

Это ситуация, когда стоит несколько серверов, и везде и пишется, и читается. Плюс в том, что оно может быть быстрее, оно отказоустойчивое. В принципе, все то же, что и у слэйвов, но логика, вообще, простая — мы просто выбираем рандомное соединение и с ним работаем. Минусы: лаг репликации выше, есть шанс получить какие-то неконсистентные данные, и, если произошла какая-нибудь поломка, то она начинает раскидываться по всем мастерам, и никому уже неизвестно, какой мастер нормальный, какой поломался… Это все дело начинает реплицироваться по кругу, т.е. очень неслабо забивает сеть. Вообще, если пришлось делать мастер-мастер, надо 100 раз подумать. Скорее всего, можно обойтись мастер-слэйвом.

Можно делать репликацию всегда руками, т.е. организовать пару соединений и писать сразу в 2, в 3, либо что-то делать в фоне.

Что такое шардирование?

Фактически это размазывание данных по нескольким серверам. Шардировать можно отдельные таблицы. Берем, допустим, таблицу фото, таблицу юзеров и др., растаскиваем их на отдельные сервера. Если таблицы были большие, то все становится меньше, памяти ест меньше, все хорошо, только нельзя JOIN"ить и приходится делать запросы типа WHERE IN, т.е. сначала выбираем кучу ID"шников, потом все эти ID"шники подставляем запросу, но уже к другому коннекту, к другому серверу.

Можно шардировать часть одних и тех же данных, т.е., например, мы берем и делаем несколько БД с юзерами.


Можно достаточно просто выбрать сервер — остаток от деления на количество серверов. Альтернатива — завести карту, т.е. для каждой записи держать в каком-нибудь Redis"е или т.п. ключ значения, т.е. где какая запись лежит.

Есть вариант проще:


Сложнее — это когда не удается сгруппировать данные. Надо знать ID данных, чтобы их достать. Никаких JOIN, ORDER и т.д. Фактически мы сводим наш MySQL или PostgreSQL к key-valuе хранилищу, потому что мы с ними ничего делать не можем.

Обычные задачи становятся необычными:

  • Выбрать TOP 10.
  • Постраничная разбивка.
  • Выбрать с наименьшей стоимостью.
  • Выбрать посты юзера X.

Если мы зашардировали так, что все разлетелось по всем серверам, это уже начинает решаться очень нетривиально. В этой ситуации возникает вопрос — а зачем нам, вообще SQL? Не писать ли нам в Redis сразу? А правильно ли мы выбрали хранилище?

Из коробки шардинг поддерживается такими штуками как:

  • memcache;
  • Redis;
  • Cassandra (но она, говорят, с какого-то момента не справляется и начинает падать).

Как быть со статистикой?

Часто статистику любят считать с основного сервера — с единственного сервера БД. Это прекрасно, но запросы в статистике обычно жуткие, многостраничные и т.д., поэтому считать статистику по основным данным — это большая ошибка. Для статистики в большинстве случаев realtime не нужен, так что мы можем настроить мастер-слэйв репликацию и на слэйве эту статистику уже посчитать. Или мы можем взять что-нибудь готовое — Mixpanel, Google Analytics или подобное.


Это основная идея, которая помогает раскидывать все по разным серверам и масштабировать. Во-первых, от этого сразу виден профит — даже если у вас один сервер и вы начинаете в фоне что-то выполнять, юзер получает ответ гораздо быстрее, но и впоследствии размазывать нагрузку, т.е. мы можем перетащить всю эту обработку на другой сервер, можно обрабатывать даже не на PHP. Например, в Stay.com картинки ресайзятся на Go.

Можно сразу взять Gearman. Это готовая штука для обработки в фоне. Есть под PHP библиотеки, драйвера… А можно использовать очереди, т.е. ActiveMQ, RabbitMQ, но очереди пересылают только сообщения, сами обработчики они не вызывают, не выполняют, и тогда придется что-то придумывать.

Общий смысл всегда один — есть основное ПО, которое помещает в очереди какие-то данные (обычно это «что сделать?» и данные для этого), и какой-то сервис – он либо достает, либо ему прилетают (если очередь умеет активно себя вести) эти данные, он все обрабатывает в фоне.

Перейдем к архитектуре.

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

Связанность бывает в коде. SOLID, GRASP — это принципы, которые позволяют избежать связанности именно в коде. Но связанность в коде на разнос по серверам, конечно, влияет, но не настолько, насколько связанность доменного слоя с нашим окружением. Если мы в контроллере пишем много-много кода, получается, что в другом месте мы это использовать, скорее всего, не сможем. Нам непросто будет все это переносить из веб-контроллера в консоль и, соответственно, сложнее переносить на другие сервера и там обрабатывать по-другому.


Service-oriented architecture.

Есть 2 подхода разбиения систем на части:

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

    Это хорошо, но когда эти очереди, изображения и т.п. взаимодействуют в рамках двух доменных областей… Например, в проекте есть область Sales и область Customer — это разные области, с ними работают разные пользователи, но и у тех, и у тех есть разные очереди. Когда все начинает сваливаться в кучу, проект превращается в месиво;

  2. правильное решение — бить на отдельные логические части, т.е. если в областях Sales и Customer используется модель user, то мы создаем 2 модели user. Они могут читать одни и те же данные, но представляют они их немного по-разному. Если разбить систему таким образом, то все гораздо лучше воспринимается и намного проще все это раскидать.

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

Что с доменным слоем?

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

Есть 2 книги про доменный слой, которые всем советую:

  • «Domain-Driven Design: Tackling Complexity in the Heart of Software» от Eric Evans,
  • «Implementing Domain-Driven Design, Implementing Domain-Driven Design».
  • про BoundedContext — http://martinfowler.com/bliki/BoundedContext.html (то, о чем было выше — если у вас две области вроде как пересекаются, но они
    разные, то стоит некоторые сущности продублировать, такие как модель user);
  • про DDD в общем — — ссылка еще на одну книгу.

В архитектуре, опять же, стоит придерживаться принципа share nothing, т.е. если вы хотите что-то сделать общим, делайте это всегда сознательно. Логику предпочтительно закидывать на сторону приложения, но и в этом стоит знать меру. Никогда не стоит, допустим, делать хранимые процедуры в СУБД, потому что масштабировать это очень тяжело. Если это перенести на сторону приложения, то становится проще — сделаем несколько серверов и все будет выполняться там.

Не стоит недооценивать браузерную оптимизацию. Как я уже говорил, из тех 300-600 мс, которые запросы выполняются на сервере, к ним прибавляется 300-600 мс, которые тратятся на клиенте. Клиенту все равно, сервер ли у нас быстрый, или это сайт так быстро отработал, поэтому советую использовать Google PageSpeed и т.д.

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

Никогда не действуйте вслепую, всегда мониторьте, анализируйте. Вслепую практически все решения по умолчанию неправильные. Думайте! Не верьте, что существует «серебряная пуля», всегда проверяйте.

Еще немного ссылок полезных:


Контакты

Этот доклад - расшифровка одного из лучших выступлений на обучающей конференции разработчиков высоконагруженных систем за 2015 год.

Старьё! - скажите вы.
- Вечные ценности! - ответим мы.

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

Ну и главная новость - мы начали подготовку весеннего фестиваля "Российские интернет-технологии ", в который входит восемь конференций, включая HighLoad++ Junior .

Олег Спиряев

В последнее время нередки утверждения, что серверы среднего и старшего класса активно заменяются на группы серверов начального уровня, объединенные в стойки или кластеры. Однако некоторые эксперты с этим не согласны. Так, по данным Dataquest, доля моделей ценой от 500 тыс. долл. и выше (к ним относятся средние и старшие серверы SMP) в общем объеме продаж серверов с 2000 до 2002 г. выросла с 38 до 52%.

Другие данные, полученные компанией IDC, свидетельствуют о росте (по крайней мере, по числу машин) в секторе младших моделей серверов - с двумя процессорами. IDC также предсказывает, что в 2005 г. самой распространенной операционной системой для серверов стоимостью от 50 тыс. до 3 млн долл. будет Unix. Из сравнения этих данных видно, что Unix-серверы среднего и старшего класса останутся преобладающей платформой для центров обработки данных, но будут дополняться все растущим числом небольших (обычно двухпроцессорных) серверов.

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

Вертикальная и горизонтальная архитектуры

Рассмотрим основные различия между вертикальной и горизонтальной архитектурами. Серверы с вертикальным масштабированием - это большие SMP-системы (с симметричной многопроцессорной обработкой или совместно используемой памятью), насчитывающие свыше четырех центральных процессоров. В них используется только одна копия ОС, управляющая работой всех процессоров, памяти и компонентов ввода-вывода. Обычно все эти ресурсы размещены в одной стойке или шкафу. Межсоединения у таких серверов осуществляются по высокоскоростной центральной или объединительной панели с небольшим временем запаздывания и согласованным доступом к кэш-памяти. Добавить ресурсы можно путем установки внутрь шкафа дополнительных системных плат. В системах с вертикальной архитектурой (или SMP-системах) память используется совместно, т. е. все процессоры и компоненты ввода-вывода получают доступ ко всей памяти. Пользователь "видит" память как единый большой объект.

При альтернативном, горизонтальном масштабировании системы соединяются через сеть или объединяются в кластер. Для межсоединений обычно используются стандартные сетевые технологии, такие, как Fast Ethernet, Gigabit Ethernet (GBE) и Scalable Coherent Interconnect (SCI), дающие меньшую пропускную способность и большее запаздывание по сравнению с вертикальными системами. Ресурсы в этом случае распределяются между узлами, обычно содержащими от одного до четырех процессоров; каждый узел имеет собственный процессор и память и может иметь собственную подсистему ввода-вывода или использовать ее совместно с другими узлами. На каждом узле работает отдельная копия ОС. Ресурсы расширяются за счет добавления узлов, но не добавления ресурсов в узел. Память в горизонтальных системах распределена, т. е. у каждого узла есть собственная память, к которой напрямую обращаются его процессоры и подсистема ввода-вывода. Доступ к этим ресурсам с другого узла происходит намного медленнее, чем с узла, где они расположены. Кроме того, при горизонтальной архитектуре отсутствует согласованный доступ узлов к памяти, а используемые приложения потребляют относительно немного ресурсов, поэтому они "умещаются" на одном узле и им не нужен согласованный доступ. Если же приложению потребуется несколько узлов, то оно само должно обеспечить согласованный доступ к памяти.

Если горизонтальная система удовлетворяет требованиям приложений, то такая архитектура предпочтительна, поскольку расходы на ее приобретение меньше. Обычно стоимость приобретения в расчете на один процессор у горизонтальных систем ниже, чем у вертикальных. Разница в цене объясняется тем, что в вертикальных системах применяются более мощные функции надежности, доступности и обслуживаемости - RAS (reliability, availability, serviceability), а также высокопроизводительные межсоединения. Однако есть ряд ограничений на применение систем с горизонтальной архитектурой. Ниже мы обсудим, в каких условиях возможно применение горизонтальных систем и когда обязательно вертикальное масштабирование.

Помимо одного большого SMP-сервера, к вертикальной архитектуре относятся также кластеры больших SMP-серверов, используемые для одного крупномасштабного приложения.

Недавно появившиеся на рынке модульные, или blade-серверы, обычно оборудуемые одним-двумя процессорами, - пример горизонтальных серверов. Здесь кластер состоит из небольших узлов, в каждом из которых установлен SMP-сервер начального уровня с числом центральных процессоров от 1 до 4.

Другой способ горизонтального масштабирования - это большие вычислительные системы с массовым параллелизмом (MPP), состоящие из множества установленных в одном шкафу небольших процессоров, каждый из которых имеет собственную копию ОС или копию микроядра ОС. В настоящее время выпускаются всего несколько систем MPP, которые чаще всего представляют специализированные решения. Это, например, системы Terradata производства компании NCR, IBM RS/6000SP (SP-2) и HP Tandem non-stop.

Таблица 1. Особенности вертикальной и горизонтальной архитектур

Параметр Вертикальные системы Горизонтальные системы
Память Большая совместно используемая Небольшая выделенная
Потоки Много взаимозависимых потоков Много независимых потоков
Межсоединения Сильносвязанные внутренние Слабосвязанные внешние
RAS Мощные RAS одиночной системы Мощные RAS с использованием репликации
Центральные процессоры Много стандартных Много стандартных
ОС Одна копия ОС на множество центральных процессоров Несколько копий ОС (по одной копии на 1-4 процессора)
Компоновка В одном шкафу Размещение большого числа серверов в стойке
Плотность размещения Высокая плотность размещения процессоров на единицу площади пола
Оборудование Стандартное и специально разработанное Стандартное
Масштабирование В пределах корпуса одного сервера В масштабе нескольких серверов
Расширение Путем установки в сервер дополнительных компонентов Путем добавления новых узлов
Архитектура 64-разрядная 32- и 64-разрядная

Табл. 1 позволяет провести сравнительный анализ вертикальной и горизонтальной архитектур.

  • В вертикальных системах память используется совместно и обеспечивается согласованный доступ к кэш-памяти.
  • Вертикальные системы идеальны для потоков выполнения задач, которые должны обмениваться данными между собой.
  • Вертикальные системы характеризуются мощными функциями RAS, а в горизонтальных системах доступность реализуется с помощью массивной репликации (в кластер соединяются несколько узлов, поэтому отказ одного из них мало влияет на работу всей системы).
  • В вертикальных системах одна копия ОС охватывает все ресурсы. Некоторые вертикальные системы, например, мидфреймы и серверы класса high-end Sun Microsystems (от Sun Fire 4800 до Sun Fire 15K), можно разделить на меньшие вертикальные серверы.
  • В вертикальных системах используется максимально возможное число стандартных компонентов, но некоторые основные составляющие (например, межсоединения) специально разрабатываются.
  • Вертикальные системы можно расширять, устанавливая в существующий каркас дополнительные компоненты (более мощные процессоры, добавочную память, дополнительные и более производительные соединения ввода-вывода и т. п.). Горизонтальные системы расширяются за счет добавления узла или замены старых узлов на новые.
  • Практически все вертикальные системы 64-разрядные, а горизонтальные могут быть как 32-разрядными, так и 64-разрядными.

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

Таблица 2. Типы приложений для вертикальной и горизонтальной архитектур

Для небольших и модульных серверов хорошо подходят приложения, которые не используют информацию о состоянии, невелики по масштабу и легко реплицируются. А для приложений, использующих информацию о состоянии и большие объемы данных, требующих интенсивной передачи данных внутри системы, идеальным решением будут вертикальные серверы. На рынке высокопроизводительных технических вычислений (HPTC) имеется множество приложений, в которых потоки зависят друг от друга и обмениваются данными между собой. Существуют также приложения, которым нужны большие объемы совместно используемой памяти. Для этих двух типов приложений лучше всего подходят большие SMP-серверы. Однако имеются и такие приложения HPTC, в которых потоки исполнения независимы и им не требуется совместно используемая память большого объема. Такие приложения можно разбивать на разделы, и потому для их выполнения идеальны кластеры небольших серверов. Аналогичным образом некоторые коммерческие приложения поддерживают разделы, и для них оптимальны горизонтальные серверы, а другие нельзя разбить на разделы, поэтому для них лучшая платформа - это вертикальные серверы.

Факторы, влияющие на производительность

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

Процессоры и системные межсоединения

Процессоры, безусловно, существенный компонент, но они только отчасти определяют общую производительность системы. Более важно обеспечить работу процессоров с максимальной загрузкой. У мощного процессора, загруженного лишь на 50%, производительность будет хуже, чем у более медленного процессора, который загружен на 80%.

Кроме того, по мере роста числа процессоров в параллельной системе на первый план выходит не их мощность, а системные межсоединения. Именно они отвечают за перемещение данных с диска, из памяти и из сети к процессору. В кластере в качестве межсоединения выступает сетевое соединение, например, Fast Ethernet или Gigabit Ethernet. Кластерные межсоединения перемещают данные между узлами, а системные - внутри отдельной системы. Если межсоединение работает слишком медленно, то процессор в ожидании данных будет простаивать.

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

Основное техническое различие между горизонтальными и вертикальными системами - это пропускная способность и запаздывание их межсоединений. У межсоединений кластеров пропускная способность может составлять от 125 Мбайт/с для Fast Ethernet до 200 Мбайт/с для SCI, а запаздывание - от 100 тыс. нс для GBE и до 10 тыс. нс для SCI. С помощью интерфейса InfiniBand возможно реализовать более быстрые межсоединения с пиковой скоростью от примерно 250 Мбайт/с для первой версии и до 3 Гбайт/с для последующих.

Ввод и вывод

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

Операционная система

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

Доступность системы

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

Оптимизированные приложения

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

Размер приложений

Как уже отмечалось, в больших SMP-системах используются высокоскоростные межсоединения, обеспечивающие достаточную производительность системы. В горизонтальных системах могут возникать проблемы с производительностью из-за низкой пропускной способности и значительной задержки межсоединений в тех случаях, когда требуется часто передавать данные между узлами. Однако некоторым приложениям для достижения высокой производительности не нужна высокая скорость межсоединений - обычно это небольшие приложения и приложения, которые можно легко реплицировать (например, Web-серверы, прокси-серверы, брандмауэры и небольшие серверы приложений). В таких горизонтальных системах каждый узел выполняет небольшую задачу независимо от работы всех остальных.

Например, в случае горизонтальной архитектуры (или архитектуры с распределенной памятью) четыре процессорных узла (каждый с отдельным ОЗУ и выделенной либо используемой совместно подсистемой ввода-вывода) могут использовать сетевое межсоединение, например, Gigabit Ethernet. В этой вычислительной среде выполняются рабочие нагрузки трех типов. Самая маленькая нагрузка помещается на одном узле, но по мере ее увеличения для выполнения требуется уже несколько узлов. Как утверждают специалисты, при выполнении одной задачи на нескольких узлах производительность значительно ухудшается из-за медленных межузловых межсоединений. Небольшие нагрузки, которым не нужно обмениваться данными между собой, прекрасно сочетаются с горизонтальной архитектурой, но при выполнении в ней крупномасштабных нагрузок возникают проблемы.

Конфигурация большой системы SMP может включать, например, до 100 процессоров, 576 Гбайт совместно используемой памяти и высокоскоростные межсоединения. Такая система может обрабатывать все типы нагрузок, поскольку в ней отсутствует обмен данными между узлами и эффективно осуществляется обмен данными между процессами. Все центральные процессоры могут одновременно получить доступ ко всем дискам, всей памяти и сетевым соединениям - это ключевая особенность SMP-систем (или вертикальных систем).

Часто возникает вопрос о целесообразности размещения на больших SMP малых нагрузок. Хотя в техническом плане это возможно, с экономической точки зрения такой подход себя не оправдывает. Для больших SMP стоимость приобретения в расчете на один процессор выше, чем для маленьких систем. Поэтому если приложение может работать на небольшом узле (или нескольких небольших узлах) и это не создает серьезных проблем с управлением, для его развертывания лучше подходит горизонтальное масштабирование. Но если приложение слишком велико и не может выполняться на небольшом узле (или нескольких таких узлах), то крупный SMP-сервер будет оптимальным вариантом с точки зрения как производительности, так и системного администрирования.

Производительность на уровне базы данных

Основной вопрос здесь - сравнение производительности одиночных средних и больших SMP-серверов с кластером небольших серверов (не более четырех процессоров).

При обсуждении масштабируемости фирмы-производители используют ряд специальных терминов. Так, рост производительности (Speedup) для SMP определяется как отношение скоростей выполнения приложения на нескольких процессорах и на одном. Линейный рост производительности (Linear speedup) означает, например, что на 40 процессорах приложение работает в 40 раз (40x) быстрее, чем на одном. Рост производительности не зависит от числа процессоров, т. е. для конфигурации из 24 процессоров он будет таким же, как для 48 процессоров. Рост производительности кластера (Cluster speedup) отличается только тем, что при его расчете берется число узлов, а не процессоров. Как и рост производительности SMP, рост производительности кластера остается постоянным для разного числа узлов.

Эффективность масштабирования (Scaling efficiency) характеризует способность приложений, особенно кластерных, масштабироваться на большое число узлов. Обычно считается, что эффективность масштабирования зависит от числа узлов, участвующих в измерении. Эффективность масштабирования SMP (SMP scaling efficiency) - это рост производительности, деленный на число процессоров, а эффективность кластера (Cluster efficiency) - это рост производительности кластера, деленный на число узлов в нем. Нужно понимать, в чем смысл этих параметров, чтобы не складывалась неправильная картина, поскольку эффективность масштабирования 90% на двух узлах - это не то же самое, что эффективность масштабирования 90% на четырех узлах.

На рис. 2 приведены три графика: идеальная линейная масштабируемость, масштабируемость 24-процессорного SMP-сервера в 95% и масштабируемость кластера из двух 4-процессорных серверов в 90%. Видно, что существуют определенные ограничения на масштабируемость баз данных в кластерах (при горизонтальном масштабировании). Соединяя вместе много маленьких серверов, не удается получить масштабируемость, необходимую для средних и крупных приложений. Причина этого - ограничения пропускной способности внутрикластерных межсоединений, дополнительная нагрузка на ПО баз данных, связанная с управлением кластером, и трудности написания приложений для кластерных сред с распределенной памятью.

Опубликованные результаты эталонных тестов показывают, например, что у Oracle9i RAC (Real Application Cluster) рост производительности составляет 1,8 и эффективность масштабирования равна 90%. Такая эффективность масштабируемости может показаться достаточно высокой, но на самом деле масштабируемость 90% для четырех узлов оказывается неэффективной, если сравнить ее с результатами больших SMP-серверов.

Производительность на уровне приложений

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

В большинстве случаев уровню сервера приложений требуется намного больше процессоров, чем уровню базы данных в расчете на отдельный прикладной сервис. Например, в случае SAP R/3 это соотношение составляет примерно 10 процессоров на каждый процессор базы данных, т. е. если SAP R/3 требуется 20 процессоров для уровня базы данных, то на уровне приложений должно быть примерно 200 процессоров. Вопрос заключается в том, что выгоднее развернуть - 100 двухпроцессорных серверов или десять 20-процессорных. Аналогичным образом в Oracle соотношение процессоров приложений к процессорам баз данных равно примерно 5 к 1.

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

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

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

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

Влияние архитектуры на доступность

Доступность крайне важна для современных центров обработки данных - сервисы приложений должны быть доступны в режиме 24x7x365 (24 часа в сутки, 7 дней в неделю, 365 дней в году). В зависимости от потребностей конкретного центра обработки данных используются разные схемы обеспечения высокой доступности. Для выбора конкретного решения необходимо определить допустимое время простоев (запланированных и незапланированных). На рис. 3 показано, как процент доступности отражается на продолжительности простоев.

По мере роста требований к доступности растет и стоимость решения. Менеджеры центров обработки данных должны определить, какое сочетание стоимости, сложности и доступности наилучшим образом соответствует требованиям к уровню сервиса. Центры обработки данных, которым нужна доступность примерно 99,95%, могут развернуть одиночный SMP-сервер с такими функциями RAS, как полное резервирование аппаратуры и обслуживание в онлайновом режиме.

Однако для достижения доступности выше 99,95% потребуется кластер. ПО Sun Cluster с переключением при отказе HA (High Availability - высокой доступности) обеспечивает доступность 99,975%. Переключение при отказе HA использует основной сервер и находящийся в горячем резерве; при отказе основного сервера резервный берет на себя его нагрузку. Время перезапуска сервиса зависит от приложений и может занять несколько минут, особенно в случае приложений баз данных, которым для восстановления транзакций требуется откат с обработкой большого объема данных.

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

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

Для трехуровневой архитектуры хорошим примером горизонтальной высокой доступности служит развертывание Web-серверов. Можно развернуть много небольших серверов, на каждом из которых будет установлена отдельная копия ПО Web-сервера. Если один Web-сервер выйдет из строя, его транзакции перераспределяются между остальными работоспособными серверами. В случае серверов приложений они могут размещаться как на горизонтальных, так и на вертикальных серверах, и высокая доступность реализуется с помощью дублирования. Независимо от того, развертывается ли несколько крупных SMP-серверов или много небольших, дублирование остается основным способом обеспечения высокого RAS на уровне приложений.

Однако для уровня баз данных ситуация меняется. Базы данных сохраняют состояние и по своей природе требуют в большинстве случаев разделения данных и возможности доступа к ним со всех процессоров/узлов. Это означает, что для высокой доступности с помощью дублирования нужно использовать такое ПО кластеризации, как Sun Cluster или Oracle9i RAC (для очень высокой доступности).

Выводы

Как у вертикальной, так и у горизонтальной архитектуры есть своя ниша в сегодняшнем центре обработки данных. Хотя сегодня основное внимание сосредоточено на таких новых технологиях, как модульные серверы и параллельные базы данных, на рынке сохраняется высокий спрос на серверы среднего класса и класса high-end.

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

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

С ростом популярности web-приложения его поддержка неизбежно начинает требовать всё больших и больших ресурсов. Первое время с нагрузкой можно (и, несомненно, нужно) бороться путём оптимизации алгоритмов и/или архитектуры самого приложения. Однако, что делать, если всё, что можно было оптимизировать, уже оптимизировано, а приложение всё равно не справляется с нагрузкой?

Оптимизация

Первым делом стоит сесть и подумать, а всё ли вам уже удалось оптимизировать:
  • оптимальны ли запросы к БД (анализ EXPLAIN, использование индексов)?
  • правильно ли хранятся данные (SQL vs NoSQL)?
  • используется ли кеширование?
  • нет ли излишних запросов к ФС или БД?
  • оптимальны ли алгоритмы обработки данных?
  • оптимальны ли настройки окружения: Apache/Nginx, MySQL/PostgreSQL, PHP/Python?
О каждом из этих пунктов можно написать отдельную статью, так что детальное их рассмотрение в рамках данной статьи явно избыточно. Важно лишь понимать, что перед тем как приступить к масштабированию приложения, крайне желательно максимально оптимизировать его работу – ведь возможно тогда никакого масштабирования и не потребуется.

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

И так, допустим, что оптимизация уже проведена, но приложение всё равно не справляется с нагрузкой. В таком случае решением проблемы, очевидно, может послужить разнесение его по нескольким хостам, с целью увеличения общей производительности приложения за счёт увеличения доступных ресурсов. Такой подход имеет официальное название – «масштабирование» (scale) приложения. Точнее говоря, под «масштабируемостью » (scalability) называется возможность системы увеличивать свою производительность при увеличении количества выделяемых ей ресурсов. Различают два способа масштабирования: вертикальное и горизонтальное. Вертикальное масштабирование подразумевает увеличение производительности приложения при добавлении ресурсов (процессора, памяти, диска) в рамках одного узла (хоста). Горизонтальное масштабирование характерно для распределённых приложений и подразумевает рост производительности приложения при добавлении ещё одного узла (хоста).

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

Архитектура приложения

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

Каждый их этих слоёв может быть масштабирован. Поэтому если в вашей системе приложение и БД живут на одном хосте – первым шагом, несомненно, должно стать разнесение их по разным хостам.

Узкое место

Приступая к масштабированию системы, первым делом стоит определить, какой из слоёв является «узким местом» - то есть работает медленнее остальной системы. Для начала можно воспользоваться банальными утилитами типа top (htop) для оценки потребления процессора/памяти и df, iostat для оценки потребления диска. Однако, желательно выделить отдельный хост, с эмуляцией боевой нагрузки (c помощью или JMeter), на котором можно будет профилировать работу приложения с помощью таких утилит как xdebug , и так далее. Для выявления узких запросов к БД можно воспользоваться утилитами типа pgFouine (понятно, что делать это лучше на основе логов с боевого сервера).

Обычно всё зависит от архитектуры приложения, но наиболее вероятными кандидатами на «узкое место» в общем случае являются БД и код. Если ваше приложение работает с большим объёмом пользовательских данных, то «узким местом», соответственно, скорее всего будет хранение статики.

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

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

Снизить нагрузку на БД можно разнеся её на несколько хостов. При этом остро встаёт проблема синхронизации между ними, решить которую можно путём реализации схемы master/slave с синхронной или асинхронной репликацией. В случае с PostgreSQL реализовать синхронную репликацию можно с помощью Slony-I , асинхронную – PgPool-II или WAL (9.0). Решить проблему разделения запросов чтения и записи, а так же балансировки нагрузку между имеющимися slave’ами, можно с помощью настройки специального слоя доступа к БД (PgPool-II).

Проблему хранения большого объёма данных в случае использования реляционных СУБД можно решить с помощью механизма партицирования (“partitioning” в PostgreSQL), либо разворачивая БД на распределённых ФС типа Hadoop DFS .

Однако, для хранения больших объёмов данных лучшим решением будет «шардинг » (sharding) данных, который является встроенным преимуществом большинства NoSQL БД (например, MongoDB).

Кроме того, NoSQL БД в общем работают быстрее своих SQL-братьев за счёт отсутствия overhead’а на разбор/оптимизацию запроса, проверки целостности структуры данных и т.д. Тема сравнения реляционных и NoSQL БД так же довольно обширна и заслуживает .

Отдельно стоит отметить опыт Facebook, который используют MySQL без JOIN-выборок. Такая стратегия позволяет им значительно легче масштабировать БД, перенося при этом нагрузку с БД на код, который, как будет описано ниже, масштабируется проще БД.

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

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

Далее необходимо настроить балансировку нагрузки/запросов между этими хостами. Сделать это можно как на уровне TCP (haproxy), так и на HTTP (nginx) или DNS .

Следующим шагом нужно сделать так, что бы файлы статики, cache и сессии web-приложения были доступны на каждом хосте. Для сессий можно использовать сервер, работающий по сети (например, memcached). В качестве сервера кеша вполне разумно использовать тот же memcached, но, естественно, на другом хосте.

Файлы статики можно смонтировать с некого общего файлового хранилища по NFS /CIFS или использовать распределённую ФС (HDFS , GlusterFS , Ceph).

Так же можно хранить файлы в БД (например, Mongo GridFS), решая тем самым проблемы доступности и масштабируемости (с учётом того, что для NoSQL БД проблема масштабируемости решена за счёт шардинга).

Отдельно стоит отметить проблему деплоймента на несколько хостов. Как сделать так, что бы пользователь, нажимая «Обновить», не видел разные версии приложения? Самым простым решением, на мой взгляд, будет исключение из конфига балансировщика нагрузки (web-сервера) не обновлённых хостов, и последовательного их включения по мере обновления. Так же можно привязать пользователей к конкретным хостам по cookie или IP. Если же обновление требует значимых изменений в БД, проще всего, вообще временно закрыть проект.

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

При необходимости хранения большого объёма статики можно выделить две проблемы: нехватка места и скорость доступа к данным. Как уже было написано выше, проблему с нехваткой места можно решить как минимум тремя путями: распределённая ФС, хранение данных в БД с поддержкой шардинга и организация шардинга «вручную» на уровне кода.

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

Мониторинг

Понятно, что большая и сложная система требует постоянного мониторинга. Решение, на мой взгляд, тут стандартное – zabbix, который следит за нагрузкой/работой узлов системы и monit для демонов для подстраховки.

Заключение

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

Итак вы сделали сайт. Всегда интересно и волнительно наблюдать как счетчик посещений медленно, но верно ползет вверх, с каждым днем показывая все лучшие результаты. Но однажды, когда вы этого не ждете, кто-то запостит ссылку на ваш ресурс на каком-нибудь Reddit или Hacker News (или на Хабре - прим. пер.), и ваш сервер ляжет.

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

Немного про оптимизацию

Основные советы всем известны: обновитесь до последней версии PHP (в 5.5 теперь встроен OpCache), разберитесь с индексами в базе данных, кэшируйте статику (редко изменяемые страницы, такие как “О нас”, “FAQ” и т.д.).

Также стоит упомянуть об одном особом аспекте оптимизации - обслуживании статического контента не-Apache сервером, таким как, например, Nginx, Настройте Nginx на обработку всего статического контента (*.jpg, *.png, *.mp4, *.html…), а файлы требующие серверной обработки пусть отсылает тяжелому Apache. Это называется reverse proxy .

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

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

Вертикальное масштабирование.

Представьте себе сервер, обслуживающий веб-приложение. У него 4ГБ RAM, i5 процессор и 1ТБ HDD. Он отлично выполняет свои функции, но, что бы лучше справляться с более высоким трафиком, вы решаете увеличить RAM до 16ГБ, поставить процессор i7, и раскошелиться на SSD диск. Теперь сервер гораздо мощнее, и справляется с высокими нагрузками. Это и есть вертикальное масштабирование.

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

Горизонтальное масштабирование - создание кластера из связанных между собой (часто не очень мощных) серверов, которые вместе обслуживают сайт. В этом случае, используется балансировщик нагрузки (aka load balancer ) - машина или программа, основная функция которой - определить на какой сервер послать запрос. Сервера в кластере делят между собой обслуживание приложения, ничего друг о друге не зная, таким образом значительно увеличивая пропускную способность и отказоустойчивость вашего сайта.

Есть два типа балансировщиков - аппаратные и программные. Программный - устанавливается на обычный сервер и принимает весь трафик, передавая его соответствующим обработчикам. Таким балансировщиком может быть, например, Nginx. В разделе “Оптимизация” он перехватывал все запросы на статические файлы, и обслуживал эти запросы сам, не обременяя Apache. Другое популярное ПО для реализации балансировки нагрузки - Squid . Лично я всегда использую именно его, т.к. он предоставляет отличный дружественный интерфейс, для контроля за самыми глубокими аспектами балансировки.

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

Обратите внимание, что эти два метода не являются взаимоисключающими. Вы можете вертикально масштабировать любую машину (aka Ноду ) в вашей системе.
В этой статье мы обсуждаем горизонтальное масштабирование, т.к. оно дешевле и эффективнее, хотя и сложнее в реализации.

Постоянное соединение

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

Обмен локальными данными.

Разделить данные сессии пользователей между всеми нодами кластера - кажется неплохой идеей. И несмотря на то, что этот подход требует некоторых изменений в архитектуре вашего приложения, оно того стоит - разгружается балансировщик, и весь кластер становится более отказоустойчивым. Смерть одного из серверов совершенно не отражается на работе всей системы.
Как мы знаем, данные сессии хранятся в суперглобальном массиве $_SESSION , который пишет и берет данные с файла на диске. Если этот диск находится на одном сервере, очевидно, что другие сервера не имеют к нему доступа. Как же нам сделать его доступным на нескольких машинах?
Во первых, обратите внимание, что обработчик сессий в PHP можно переопределить . Вы можете реализовать свой собственный класс для работы с сессиями .

Использование БД для хранения сессий

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

Распределенная файловая система

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

Memcached

Вы также можете использовать memcached для хранения данных сессий в RAM. Однако это не безопасно, ибо данные в memcached перезаписываются, если заканчивается свободное место. Вы, наверное, задаетесь вопросом, разве RAM не разделен по машинам? Как он применяется на весь кластер? Memcached имеет возможность объединять доступную на разных машинах RAM в один пул .

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

Для использования этого подхода, нужно немного подредактировать php.ini

Session.save_handler = memcache session.save_path = "tcp://path.to.memcached.server:port"

Redis кластер

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

Другие решения

Итого

Как видите, горизонтальное масштабирование PHP приложений не такое уж простое дело. Существует много трудностей, большинство решений не взаимозаменяемые, так что приходится выбирать одно, и придерживаться его до конца, ведь когда трафик зашкаливает - уже нет возможности плавно перейти на что-то другое.

Надеюсь этот небольшой гайд поможет вам выбрать подход к масштабированию для вашего проекта.

Во второй части статьи мы поговорим о масштабировании базы данных .

С ростом популярности web-приложения его поддержка неизбежно начинает требовать всё больших и больших ресурсов. Первое время с нагрузкой можно (и, несомненно, нужно) бороться путём оптимизации алгоритмов и/или архитектуры самого приложения. Однако, что делать, если всё, что можно было оптимизировать, уже оптимизировано, а приложение всё равно не справляется с нагрузкой?

Оптимизация

Первым делом стоит сесть и подумать, а всё ли вам уже удалось оптимизировать:
  • оптимальны ли запросы к БД (анализ EXPLAIN, использование индексов)?
  • правильно ли хранятся данные (SQL vs NoSQL)?
  • используется ли кеширование?
  • нет ли излишних запросов к ФС или БД?
  • оптимальны ли алгоритмы обработки данных?
  • оптимальны ли настройки окружения: Apache/Nginx, MySQL/PostgreSQL, PHP/Python?
О каждом из этих пунктов можно написать отдельную статью, так что детальное их рассмотрение в рамках данной статьи явно избыточно. Важно лишь понимать, что перед тем как приступить к масштабированию приложения, крайне желательно максимально оптимизировать его работу – ведь возможно тогда никакого масштабирования и не потребуется.

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

И так, допустим, что оптимизация уже проведена, но приложение всё равно не справляется с нагрузкой. В таком случае решением проблемы, очевидно, может послужить разнесение его по нескольким хостам, с целью увеличения общей производительности приложения за счёт увеличения доступных ресурсов. Такой подход имеет официальное название – «масштабирование» (scale) приложения. Точнее говоря, под «масштабируемостью » (scalability) называется возможность системы увеличивать свою производительность при увеличении количества выделяемых ей ресурсов. Различают два способа масштабирования: вертикальное и горизонтальное. Вертикальное масштабирование подразумевает увеличение производительности приложения при добавлении ресурсов (процессора, памяти, диска) в рамках одного узла (хоста). Горизонтальное масштабирование характерно для распределённых приложений и подразумевает рост производительности приложения при добавлении ещё одного узла (хоста).

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

Архитектура приложения

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

Каждый их этих слоёв может быть масштабирован. Поэтому если в вашей системе приложение и БД живут на одном хосте – первым шагом, несомненно, должно стать разнесение их по разным хостам.

Узкое место

Приступая к масштабированию системы, первым делом стоит определить, какой из слоёв является «узким местом» - то есть работает медленнее остальной системы. Для начала можно воспользоваться банальными утилитами типа top (htop) для оценки потребления процессора/памяти и df, iostat для оценки потребления диска. Однако, желательно выделить отдельный хост, с эмуляцией боевой нагрузки (c помощью или JMeter), на котором можно будет профилировать работу приложения с помощью таких утилит как xdebug , и так далее. Для выявления узких запросов к БД можно воспользоваться утилитами типа pgFouine (понятно, что делать это лучше на основе логов с боевого сервера).

Обычно всё зависит от архитектуры приложения, но наиболее вероятными кандидатами на «узкое место» в общем случае являются БД и код. Если ваше приложение работает с большим объёмом пользовательских данных, то «узким местом», соответственно, скорее всего будет хранение статики.

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

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

Снизить нагрузку на БД можно разнеся её на несколько хостов. При этом остро встаёт проблема синхронизации между ними, решить которую можно путём реализации схемы master/slave с синхронной или асинхронной репликацией. В случае с PostgreSQL реализовать синхронную репликацию можно с помощью Slony-I , асинхронную – PgPool-II или WAL (9.0). Решить проблему разделения запросов чтения и записи, а так же балансировки нагрузку между имеющимися slave’ами, можно с помощью настройки специального слоя доступа к БД (PgPool-II).

Проблему хранения большого объёма данных в случае использования реляционных СУБД можно решить с помощью механизма партицирования (“partitioning” в PostgreSQL), либо разворачивая БД на распределённых ФС типа Hadoop DFS .

Однако, для хранения больших объёмов данных лучшим решением будет «шардинг » (sharding) данных, который является встроенным преимуществом большинства NoSQL БД (например, MongoDB).

Кроме того, NoSQL БД в общем работают быстрее своих SQL-братьев за счёт отсутствия overhead’а на разбор/оптимизацию запроса, проверки целостности структуры данных и т.д. Тема сравнения реляционных и NoSQL БД так же довольно обширна и заслуживает отдельной статьи .

Отдельно стоит отметить опыт Facebook, который используют MySQL без JOIN-выборок. Такая стратегия позволяет им значительно легче масштабировать БД, перенося при этом нагрузку с БД на код, который, как будет описано ниже, масштабируется проще БД.

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

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

Далее необходимо настроить балансировку нагрузки/запросов между этими хостами. Сделать это можно как на уровне TCP (haproxy), так и на HTTP (nginx) или DNS .

Следующим шагом нужно сделать так, что бы файлы статики, cache и сессии web-приложения были доступны на каждом хосте. Для сессий можно использовать сервер, работающий по сети (например, memcached). В качестве сервера кеша вполне разумно использовать тот же memcached, но, естественно, на другом хосте.

Файлы статики можно смонтировать с некого общего файлового хранилища по NFS /CIFS или использовать распределённую ФС (HDFS , GlusterFS , Ceph).

Так же можно хранить файлы в БД (например, Mongo GridFS), решая тем самым проблемы доступности и масштабируемости (с учётом того, что для NoSQL БД проблема масштабируемости решена за счёт шардинга).

Отдельно стоит отметить проблему деплоймента на несколько хостов. Как сделать так, что бы пользователь, нажимая «Обновить», не видел разные версии приложения? Самым простым решением, на мой взгляд, будет исключение из конфига балансировщика нагрузки (web-сервера) не обновлённых хостов, и последовательного их включения по мере обновления. Так же можно привязать пользователей к конкретным хостам по cookie или IP. Если же обновление требует значимых изменений в БД, проще всего, вообще временно закрыть проект.

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

При необходимости хранения большого объёма статики можно выделить две проблемы: нехватка места и скорость доступа к данным. Как уже было написано выше, проблему с нехваткой места можно решить как минимум тремя путями: распределённая ФС, хранение данных в БД с поддержкой шардинга и организация шардинга «вручную» на уровне кода.

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

Мониторинг

Понятно, что большая и сложная система требует постоянного мониторинга. Решение, на мой взгляд, тут стандартное – zabbix, который следит за нагрузкой/работой узлов системы и monit для демонов для подстраховки.

Заключение

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