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

Такая история типична для малого бизнеса: сервер интеграций вырастает не по плану. Сначала один скрипт синхронизирует остатки, потом рядом появляется отправка заказов в CRM, потом выгрузка отчётов для бухгалтера. Вопрос не в том, нужен ли второй сервер прямо сейчас. Вопрос в том, как разложить задачи на одном VPS так, чтобы сбой одной не тянул остальные, и по какому признаку понять, что экономия на одном узле перестала быть экономией.

Три задачи с разным характером

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

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

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

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

Сведём требования в таблицу, потому что здесь сравнение полезнее прозы.

Чего ждёт каждая из трёх задач
ЗадачаЧто для неё критичноЧем опасна для соседейЧто можно отложить
Срочная заявкаКороткая задержка, повтор при сбоеПочти ничем, но страдает первойНичего: клиент ждёт
Пакетный импортЗавершиться за ночное окно, не потерять половину данныхПроцессор, память, нагрузка на диск и базуСам запуск и скорость
Отчётная выгрузкаПолнота и корректность файлаМесто на диске, чтение большой выборкиПочти всё, кроме срока сдачи

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

Что значит «развести» процессы на практике

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

Второй шаг — очереди. Если заявка приходит вебхуком, проще всего принять её, быстро положить в очередь и ответить «принято», а разбираться с ней отдельным рабочим процессом. Так всплеск входящих не превращается в отказ приёма. Импорт при этом не лезет в ту же очередь: у него своя, и рабочие процессы у каждой очереди свои. Тогда медленная обработка прайса может сколько угодно копить свои задания, не вытесняя заказы.

Третий шаг — владельцы данных. Это не техническая, а организационная мера, но без неё разделение остаётся декоративным. Полезно честно записать, чьи данные кто меняет:

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

Когда у каждого поля один автор, пропадает целый класс ночных расследований вида «кто перезаписал наценку». Если же импорт обновляет несколько связанных таблиц, его стоит оборачивать в транзакцию базы: по документации PostgreSQL, транзакция объединяет шаги в операцию «всё или ничего», а ROLLBACK отменяет изменения незавершённой транзакции. Тогда сбой на сорок тысячной строке не оставит каталог наполовину старым, наполовину новым. Важная оговорка оттуда же: эти гарантии относятся к операциям самой базы, а не к произвольному внешнему HTTP API. Если по ходу импорта вы уже отправили запрос в чужую систему, откатить его транзакцией своей базы нельзя, и повтор нужно проектировать отдельно.

Секреты: у каждой интеграции свои ключи

Ключ от CRM, токен складской системы, пароль к почте для отчётов часто лежат в одном файле окружения «чтобы не искать». Это удобно ровно до первой утечки или первой ошибки. В руководстве OWASP по управлению секретами рекомендованы минимальные права доступа к отдельным секретам, и для нашей схемы это читается просто: обработчику заявок не нужен ключ от почты бухгалтера, а выгрузке не нужен ключ записи в CRM.

Docker Compose поддерживает такой подход напрямую: доступ к секрету выдаётся конкретному сервису явно, а сам секрет монтируется в контейнер файлом. Механизм при этом описан для Linux-контейнеров. Не стоит читать это как шифрование: в той же документации отмечено, что передача секрета файлом не означает шифрования исходного файла и не лишает доступа владельца хоста. А владелец хоста здесь — вы, и любой, кто получит root на сервере, увидит всё. Так что разделение секретов ограничивает ущерб от ошибки или компрометации одного сервиса, но не защищает от компрометации самого сервера.

Там же OWASP советует поддерживать ротацию секретов, по возможности автоматизированную. Это рекомендация по безопасности, а не свойство любой интеграции: поддерживает ли конкретная CRM или складская система выпуск нового токена без остановки обмена, нужно смотреть в её собственной документации. Разные секреты по сервисам косвенно помогают и тут: заменить ключ одной интеграции можно, не трогая две другие.

Что остаётся общим, как ни раскладывай

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

  • процессор и память. Если импорт раздул потребление памяти, система может начать выгружать данные на диск или принудительно завершать процессы, и жертвой окажется не обязательно виновник;
  • диск и его скорость. Пакетная запись в базу и выгрузка файлов конкурируют за одни и те же операции чтения и записи, а базу обычно читает и срочная заявка;
  • свободное место. Один раздел — одна коробка: логи, база, временные файлы и готовые выгрузки заполняют её вместе;
  • сетевой канал и адрес. Ограничения внешнего API по числу запросов с одного адреса действуют на все три интеграции сразу;
  • сам хост. Обновление ядра, перезагрузка, авария гипервизора или проблема на стороне площадки остановят всё одновременно.

Отсюда прямое следствие: контейнеры нельзя выдавать за независимые физические отказы. Это независимые программные единицы на общем основании. Если основание дало трещину, разделение по контейнерам не спасёт. Из этого же следует, что от одного VPS нельзя ожидать высокой доступности, как бы аккуратно ни была устроена внутренняя схема: она снижает число причин для остановки, но не убирает главную.

Ограничения ресурсов на сервис — разумная проектная мера: ограничить импорту потолок памяти и приоритет, чтобы он не мог съесть всё. Конкретные значения подбирайте по замерам вашей нагрузки, а не по чужим таблицам; точные параметры и команды зависят от вашего оркестратора и версии, и их стоит сверять с его документацией.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Карта зависимостей трёх процессов

Чтобы увидеть, где именно один отказ становится общим, нарисуем карту. Условный стенд: на одном VPS работают три сервиса, база данных и общий диск.

Условная карта зависимостей на одном VPS
РесурсПриём заявокИмпорт прайсаОтчётная выгрузка
Своя очередьдадане нужна, запуск по расписанию
Свои секретыключ CRMключ поставщикадоступ к почте или хранилищу
База данныхчитает и пишет заказыпишет цены и остаткичитает заказы и цены
Дисклоги, базавременные файлы, базаготовые файлы, база
Процессор и памятьобщий пулобщий пулобщий пул
Внешний каналCRMпоставщикпочта или облако
Хостобщийобщийобщий

Читать её нужно по строкам. Верхние строки (очередь, секреты) — то, что вы разделили и что действительно дало независимость. Средние (база, диск) — зона связи: именно здесь импорт и выгрузка достают заявки. Нижние (процессор, память, хост) — то, что делится всегда. Чем больше строк со словом «общий», тем сильнее на самом деле связаны ваши «независимые» службы.

Три разных отказа: приложение, диск, весь узел

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

Отказ приложения. Импортёр упал на некорректной строке. Приём заявок продолжает работать, очередь заказов не растёт, а импорт перезапускается и либо дотягивает, либо останавливается с понятной ошибкой. Если импорт обёрнут в транзакцию, каталог остаётся в прежнем целом состоянии. Это тот случай, ради которого разделение и делалось, и он решается дёшево.

Заполнение диска. Сценарий тише и коварнее. Выгрузка копит файлы, логи не ротируются, временные файлы импорта никто не чистит. Диск наполняется неделями, а потом в один момент перестаёт принимать записи. И здесь отказывают все: база не может записать новый заказ, обработчик не может записать лог, импорт не может создать временный файл. Разделение по процессам не помогло, потому что граница оказалась проведена по программам, а не по месту. Лечится это дисциплиной: ограничить срок хранения выгрузок, ротировать логи, следить за свободным местом и поднимать тревогу задолго до заполнения. А при росте — вынести файлы выгрузок на отдельный раздел или хранилище, чтобы они не делили место с базой.

Остановка всего узла. Перезагрузка хоста, сбой на стороне площадки, ошибка при обновлении системы. Остановились все три интеграции и база. Что с этим можно сделать внутри одного VPS? Почти ничего, кроме того, чтобы после запуска всё поднималось само и без потерь: сервисы стартуют по порядку, очередь не теряет задания, а повторная доставка вебхуков не создаёт дубли. Последнее надо проектировать отдельно, не полагаясь на то, что «новый идентификатор попытки» сам обеспечит идемпотентность: нужен стабильный ключ операции, по которому приёмник узнаёт повтор. Но вернуть работу в ту же секунду нельзя, и вот это уже вопрос не архитектуры внутри узла, а необходимости второго узла.

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

С чего начинать вынос: выбираем по влиянию на соседей

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

Пройдёмся по кандидатам.

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

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

Такая логика подсказывает и размер машины. Выгрузке обычно хватает скромного узла с запасом по диску, импорту нужен процессор и память, приёмщику заявок важнее стабильность, чем мощность. Ресурсы в тарифной линейке VPS MAATRIX масштабируются от 1 vCPU и 2 ГБ RAM (Ba) до 16 vCPU и 32 ГБ (Djed) на NVMe-дисках с AMD EPYC и root-доступом; промежуточные ступени — Uab, Heka и Maat. Ёмкость в числе заявок или файлов эти цифры не обещают, поэтому подбор делается по вашим замерам: сколько памяти занимает импорт на пике, сколько места в сутки прибавляет выгрузка, какова задержка заявки в самый тяжёлый час. Возможность выбрать локацию (в предложении указаны UK, US, RU и FR) тоже стоит учитывать, сверяясь с тем, где живут ваши CRM и поставщики, и помнить, что доступность стороннего API с конкретного адреса зависит от самого сервиса. Подробнее о том, как подбирать размер под малый бизнес, есть в материале VPS для малого бизнеса: зачем и какой.

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

Как отличить «пока хватает» от «уже дорого»

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

Признаки, что внутри одного узла вы упёрлись в потолок, можно назвать конкретно:

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

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

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