Представьте две схемы магазина на листе бумаги. Слева один прямоугольник, внутри которого живёт всё: веб-сервер, приложение, очередь писем, кэш, база данных. Справа два прямоугольника поменьше, соединённые стрелкой: в первом веб и приложение, во втором база. Интуиция подсказывает, что справа «профессиональнее», потому что у каждой роли свой дом. Но стрелка между прямоугольниками стоит денег, времени и внимания, а слово «два» ещё не означает «надёжнее». Давайте не спорить о вкусах, а проведём через обе схемы один заказ и посмотрим, что реально меняется.
Один заказ, две дороги
Покупатель нажимает «Оформить». В схеме с одним узлом происходит следующее: запрос приходит на веб-сервер, передаётся приложению, приложение обращается к базе по локальному сокету или через loopback, проверяет остатки, создаёт запись заказа, списывает резерв, ставит в очередь письмо и возвращает страницу «Спасибо». Все шаги идут внутри одной операционной системы. Сетевых границ между приложением и базой нет, а значит нет и сетевых причин для сбоя на этом отрезке.
В схеме с двумя узлами начало такое же, но каждое обращение приложения к базе превращается в сетевой запрос. Один заказ в типичном магазине порождает не один запрос, а десятки: загрузка корзины, цен, скидок, адресов доставки, проверка остатков, вставка строк заказа, обновление счётчиков. Если каждый запрос добавляет небольшую задержку на сеть, то эти добавки суммируются. Одна задержка незаметна, а тридцать последовательных уже могут стать ощутимой частью времени ответа. Какой именно это будет вклад, зависит от расположения машин и от того, как написано приложение, и угадывать здесь бессмысленно: это нужно мерить на своём каталоге и своих сценариях.
Есть и обратная сторона. Приложение, которое делает много мелких последовательных запросов, на одном узле прощает себе эту привычку. После разделения привычка вылезает наружу. Поэтому первая практическая работа перед разделением состоит не в покупке второго сервера, а в том, чтобы посмотреть, сколько обращений к базе делает страница корзины и страница оформления. Если их сотни, то разделение сначала придётся сопровождать правкой запросов, иначе вы заплатите задержкой за архитектурную аккуратность.
Кто с кем делит процессор и память
Главный аргумент за разделение звучит так: веб и база конкурируют за ресурсы, и всплеск трафика не должен душить базу. Аргумент справедлив, но его нужно разобрать на части, потому что конкуренция бывает разной.
Веб-часть магазина прожорлива на процессор в моменты пиков: рендеринг страниц, обработка изображений, фоновые задачи импорта товаров. База чаще упирается в память и диск: ей нужен кэш горячих данных и быстрые записи. Когда всё сидит на одном узле, импорт каталога в полночь может вытеснить из памяти кэш базы, и утренние запросы станут медленнее, хотя никто ничего не ломал. На двух узлах такой эффект исчезает по построению: у базы своя память, которую веб не съест.
Но разделение машин не единственный способ развести конкурентов. Если приложения запущены в контейнерах, помните, что по умолчанию контейнер не ограничен в ресурсах и может брать столько, сколько доступно хосту, а ограничения процессора и памяти задаются отдельно. То есть на одном большом узле можно сказать базе: вот твоя гарантированная часть памяти, а импорту: вот твой потолок. Это дешевле второго сервера и не добавляет сети. Правда, лимит нужно подобрать и проверить: слишком жёсткий потолок памяти для процесса сам станет причиной аварийных остановок, и ограничение ресурса не равно обещанию производительности приложения.
Получается развилка. Если конкуренция за ресурсы единственная боль, сначала стоит попробовать лимиты на одном узле. Если нужны разные профили диска, разный режим обслуживания или разные окна перезагрузки, тогда разделение начинает окупаться уже не как защита от конкуренции, а как независимость операций.
Обновление веб-части отдельно от базы
Здесь разделение раскрывается лучше всего. Веб-часть магазина меняется часто: тема оформления, плагины, версия языка выполнения, правки в шаблонах. База меняется редко, и каждое её изменение рискованнее, потому что данные заказов нельзя откатить так же легко, как код.
В схеме с одним узлом любое серьёзное обновление затрагивает всю машину. Обновление пакетов операционной системы может потребовать перезагрузки, а перезагрузка останавливает и витрину, и базу одновременно. Если после обновления приложения что-то пошло не так, а база на той же машине уже получила миграции, откат становится двухсоставным: вернуть код и разобраться с данными.
На двух узлах появляется удобная возможность. Можно поднять новый веб-узел рядом, развернуть на нём новую версию, направить на него небольшую часть проверок, а старый оставить в резерве. База при этом не перезагружается и не меняется. Для магазина это означает, что самые частые обновления перестают быть событиями, затрагивающими хранилище заказов. Перезагрузка веб-узла после обновления ядра занимает короткое время и не ставит под вопрос целостность данных.
Но эта выгода реальна только при дисциплине. Если новая версия приложения требует изменения схемы базы, то разделение не помогает: миграцию всё равно придётся согласовывать со старым и новым кодом. Рабочее правило: миграции делаются совместимыми в обе стороны на время перехода, то есть сначала добавляем столбец, потом выкатываем код, который его использует, и только потом убираем старое. Тогда старый веб-узел продолжает работать с обновлённой базой, а откат кода не превращается в аварию.
Обновление самой базы живёт по другому ритму. Ему нужны окно обслуживания, проверенная копия и репетиция на отдельной машине. Второй небольшой сервер удобен и здесь, но как площадка для репетиции: восстановили копию, прогнали миграцию, измерили время. Это хороший довод для схемы с двумя узлами, даже если основной трафик идёт на один. Планирование самих обновлений операционной системы на длинном горизонте мы разбирали отдельно в материале про график обновления ОС на два года вперёд: при двух узлах этот график просто удваивается по числу машин, и это нужно закладывать в трудозатраты.
Отказ каждого узла: что действительно происходит
Теперь самое важное, ради чего многие и затевают разделение, и самое частое заблуждение. Две машины с разными ролями не дают резервирования. Резервирование появляется, когда у роли есть замена, готовая принять нагрузку. Если у вас один веб-узел и одна база, то у каждой роли по одному экземпляру. Отказ любого из них останавливает магазин.
Разберём по очереди.
- Упал веб-узел, база жива. Витрина недоступна, но данные целы и никуда не делись. Восстановление сводится к подъёму нового веб-узла из заранее подготовленного образа или сценария развёртывания и подключению к существующей базе. Это быстрее, чем восстановление всего магазина, потому что тяжёлые данные не переезжают.
- Упала база, веб жив. Витрина отвечает ошибками или показывает заглушку. Это худший вариант по ощущению: сервер «работает», но продавать нельзя. Восстановление зависит от копий и от того, насколько свежи данные.
- Упали оба. Вы в той же ситуации, что и на одном узле, с той разницей, что поднимать нужно две машины.
Если сравнить с одним узлом честно, то вероятность того, что хоть что-то в схеме откажет, при двух машинах не меньше, чем при одной, потому что компонентов стало больше, а магазин зависит от обоих. Точных вероятностей мы не называем: у нас нет измерений, а выдуманные цифры только вводили бы в заблуждение. Зато логика видна без чисел: цепочка из двух обязательных звеньев не прочнее цепочки из одного.
Зато меняется характер отказа и цена восстановления. На одном узле отказ диска убивает всё сразу, и восстановление означает возвращение системы, приложения и данных. На двух узлах отказ веб-узла можно пережить без потери данных вообще, а отказ узла с базой не затрагивает код и конфигурацию витрины. Это не повышение надёжности, а сужение зоны поражения. Разница принципиальная: вы не реже падаете, вы меньше теряете при падении.
Если нужна именно замена базы
Настоящее резервирование базы строится на реплике. Документация PostgreSQL про warm standby описывает два важных для магазина свойства. Первое: при асинхронной передаче журнала WAL остаётся окно потери ещё не переданных изменений, а синхронная фиксация может ждать подтверждения от standby. Для магазина это вопрос деловой, а не технический: сколько секунд последних заказов вы готовы потерять при аварии и готовы ли замедлять каждую оплату ради гарантии. Второе: пропускная способность канала должна превышать скорость образования WAL, а после отключения реплике нужно догнать основной сервер. Значит, во время крупной распродажи, когда журнал растёт быстро, слабый канал или перегруженная реплика начнут отставать, и окно возможной потери вырастет именно тогда, когда заказов больше всего.
Реплика, таким образом, это третья машина в вашей схеме или, по меньшей мере, повод пересчитать смету: основной узел с базой, реплика и веб. Для небольшого магазина такая конструкция часто избыточна по затратам и по сложности, потому что кто-то должен уметь переключать роли и проверять состояние репликации. Если этого человека нет, лучше вложить усилия в проверенное восстановление из копий, чем в реплику, которую никто не умеет повышать до основной.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Восстановление из копии: проверка, которая решает больше, чем число серверов
Независимо от схемы, ваша реальная страховка это копии и умение из них вернуться. Здесь разделение даёт приятную гибкость: файлы витрины и данные базы можно копировать и восстанавливать независимо. Если, к примеру, вы используете restic, то по документации по восстановлению снимок можно извлечь в отдельную целевую директорию и сначала посмотреть результат. Там же различаются фильтры: параметр выбора снимка по пути и параметры включения или исключения файлов делают разное, и путать их опасно. Один выбирает, какой снимок брать, другие ограничивают, какие файлы из него извлекать. Для магазина это значит, что можно вернуть только каталог загруженных изображений товаров, не трогая остальное, и предварительно убедиться, что вернулось именно то.
Практическое следствие для сравнения схем: восстановление веб-узла из копии и из сценария развёртывания занимает минуты-часы и не зависит от размера базы. Восстановление базы зависит от её размера и от скорости диска. Если у вас два узла, эти два времени вы можете измерить и записать отдельно, и тогда вопрос «сколько мы простоим при аварии» получает конкретный ответ по каждой роли. На одном узле вы измеряете одно общее время, и оно складывается из всего сразу.
Учебная смета: что считать, кроме строки с тарифом
Теперь деньги. Цен мы не называем: у вас они свои, и подставлять чужие цифры нельзя. Но структуру сравнения показать можно. Ниже условная таблица с единицами ресурса вместо рублей. Это учебный пример, а не измерение и не чей-то реальный случай.
| Статья | Один крупный узел | Два небольших узла |
|---|---|---|
| Тарифы | Один счёт, ресурсы общие | Два счёта; сумма ресурсов обычно не равна ресурсам одного крупного плана, потому что у каждого узла свои накладные расходы системы |
| Запас по памяти | Общий запас, виден один пул | Запас нужен на каждом узле отдельно; свободная память веб-узла базе не поможет |
| Обновления ОС | Одна машина, одна перезагрузка, остановка всего | Две машины, два графика, но перезагрузки независимы |
| Мониторинг | Один набор метрик | Метрики по каждому узлу плюс задержка и ошибки между ними |
| Доступы и ключи | Один сервер | Два сервера и правила, кто к кому подключается |
| Копии и проверка восстановления | Один набор заданий | Отдельные задания для файлов и базы, отдельные проверки |
| Время человека | Меньше операций | Больше операций, но каждая локальнее |
Главная скрытая строка здесь запас по памяти. Если вместо одного узла на 16 ГБ вы берёте два по 8 ГБ, суммарно цифры совпадают, но пользоваться ими можно иначе. На одном узле память, не занятая приложением в данный момент, достаётся кэшу базы. На двух узлах свободная память веб-части простаивает, пока базе её не хватает. Поэтому делить ресурсы пополам по умолчанию неверно: вес ролей обычно неравный, и распределение стоит выводить из наблюдений за реальной нагрузкой.
В публичном предложении MAATRIX планы выстроены ступенями: Ba с 1 vCPU и 2 ГБ RAM, Uab с 2 vCPU и 4 ГБ, Heka с 4 vCPU и 8 ГБ, Maat с 8 vCPU и 16 ГБ, Djed с 16 vCPU и 32 ГБ, диски NVMe. Эти цифры удобны как отправная точка для расчёта, но не как обещание ёмкости: сколько заказов выдержит конкретная связка, покажет только нагрузочная проверка вашего магазина. Рассуждение для примера: если замеры показали, что веб-часть в пике держится в пределах плана Heka, а базе тесно в памяти, то пара «Uab для веба и Heka для базы» может оказаться разумнее, чем симметричные узлы. А если замеры показывают, что вся система в пике использует малую часть одного плана, то второй узел покупать не за чем.
Методология стоимостной оптимизации в AWS Well-Architected связывает затраты с функциональными требованиями, использованием ресурсов и соответствием спроса мощности. Это общий подход, не про конкретного провайдера, и для нашей задачи он полезен как порядок вопросов. Сначала что магазину нужно уметь, потом сколько ресурса реально используется, и лишь затем сколько мощности купить. Покупка «с запасом на всякий случай» без ответа на первые два вопроса превращает смету в угадывание.
Цена сети между узлами
Отдельно о стрелке на схеме. Для её надёжной работы нужно решить несколько практических вопросов, каждый из которых стоит времени.
- Кто имеет право подключаться к базе. На одном узле база слушает только локальный адрес. На двух она должна принимать соединения по сети, и порт нужно закрыть для всех, кроме веб-узла. Это правила файервола и учётные записи с минимально нужными правами.
- Шифрование соединения. Если трафик между узлами идёт через общую сеть, его стоит защитить. Это дополнительная настройка и ещё один сертификат, у которого есть срок жизни.
- Что делает приложение при недоступной базе. На одном узле такой сценарий почти не встречается, на двух он возможен ежедневно: кратковременный обрыв, перезагрузка соседа. Приложение должно переподключаться и не показывать покупателю внутренние ошибки.
- Расположение узлов. Чем ближе узлы по сети, тем меньше задержка. Если развести их по разным локациям, на каждый запрос к базе ляжет заметная добавка. В публичном предложении перечислены UK, US, RU и FR, и для связки веб плюс база разумно выбирать одну локацию, если нет особых причин иначе.
Обратите внимание на ещё один момент: два VPS не гарантируют размещение на разном оборудовании и сами по себе не дают отказоустойчивости приложению. Рассчитывать, что сбой хоста затронет только один из двух ваших серверов, нельзя, пока вы не уточнили это у провайдера. Для нашего сравнения мы исходим из худшего: оба узла могут пострадать от одной причины.
Когда один узел лучше
Теперь вернёмся к началу и честно скажем, где левая схема выигрывает.
Магазин с умеренным числом заказов, каталогом, который умещается в память базы вместе с кэшем приложения, и одним человеком, отвечающим за сервер, получает от одного узла простоту. Меньше перезагрузок координировать, меньше правил доступа, меньше точек, где что-то может не соединиться. Все метрики в одном месте, и причина медленной страницы ищется на одной машине. Если добавить лимиты ресурсов для тяжёлых процессов, большая часть выгоды от разделения достигается без разделения. Подходящий размер узла подбирается ступенями: запустили на плане поменьше, понаблюдали за нагрузкой, перешли на следующий, когда запас кончился. Сама возможность переехать на более крупный план появляется быстро: по публичному описанию, VPS создаётся автоматически через пару минут после оплаты, то есть проверку на новом размере можно провести рядом, не трогая рабочий узел.
Один узел плох в двух ситуациях. Первая: нельзя перезагружаться без остановки продаж, а обновления накапливаются. Вторая: вы не можете восстановить его целиком за приемлемое время, потому что база выросла, а копии не проверялись. Обе проблемы решаются не обязательно разделением, но разделение смягчает обе.
Когда два узла оправданы
Схема с разделением становится разумной, когда выполняется хотя бы несколько условий из списка ниже, и каждое из них можно проверить, а не почувствовать.
- Нагрузка веб-части и базы растёт по-разному, и по замерам одна из ролей регулярно упирается в предел, пока вторая простаивает.
- Обновления веб-части выходят часто, а окно простоя базы приходится согласовывать с бизнесом, поэтому хочется перезагружать их независимо.
- Есть человек, который понимает сетевые правила между узлами и следит за ними.
- Приложение умеет переживать кратковременную недоступность базы без повреждения данных.
- Восстановление веб-узла из образа или сценария проверено и занимает известное время.
- Запас по памяти и диску на каждом узле посчитан отдельно, а не получен делением общей цифры пополам.
Если же выполняется только первое условие, стоит рассмотреть промежуточный вариант: крупный узел с лимитами и вынесенной копией данных, а не два равноправных сервера.
Как принять решение без споров
Сведём всё к порядку действий, который можно выполнить за пару вечеров. Сначала запишите, какие рабочие операции вы выполняете регулярно: обновление темы, обновление плагинов, перезагрузка после обновления системы, импорт каталога, ночное копирование, восстановление из копии. Для каждой напишите, что останавливается, пока она идёт.
Затем для одного узла и для двух проставьте в таблицу: что останавливается при этой операции, сколько она длится, можно ли её откатить без потери данных, кто её выполняет. Часто выясняется, что решающее различие не в производительности, а в том, что на одном узле операция с базой вынуждает остановить витрину, а на двух нет. Если для вашего магазина это не критично, то и второй узел не нужен. Если критично, то он оправдан, и его цену, включая время человека на сопровождение сети, надо закладывать в бюджет сразу.
Если вы выбираете двухузловую схему, начните с проверок: поднимите оба узла в одной локации, настройте закрытый доступ к базе, прогоните сценарий оформления заказа под нагрузкой, отключите базу на минуту и посмотрите, как ведёт себя витрина. Затем восстановите копию базы на отдельной машине и запишите время. Эти три замера скажут о схеме больше, чем любое сравнение в теории.
Архитектуру магазина удобнее всего выбирать по управляемости рабочих операций: какой набор узлов позволяет вам обновлять, откатывать и восстанавливать каждую часть предсказуемо, за время, которое вы измерили, и силами тех, кто реально есть в команде. Если один крупный узел справляется с этим, он лучше. Если разделение ролей делает хотя бы две самые частые операции безопаснее, два небольших узла стоят своих хлопот. Число машин на схеме тут вторично: важно, сколько из них вы действительно умеете обслуживать.