Как мы строили системы городского масштаба: архитектура и нагрузка

Когда платформой одновременно пользуются десятки тысяч жителей города, цена ошибки в архитектуре измеряется не в строчках кода, а в очередях на остановках, недоступных госуслугах и потерянной выручке. Системы городского масштаба — это транспортные приложения, платёжные шлюзы, порталы госуслуг, биллинг коммунальных платежей, агрегаторы доставки. Их объединяет одно: нагрузка приходит не равномерно, а волнами, и в пиковый момент система обязана работать так же стабильно, как в три часа ночи. В этой статье мы делимся подходом, который OneDev применяет при проектировании таких решений в Узбекистане.
Почему «городской масштаб» — это отдельный класс задач
Главная иллюзия заказчика звучит так: «у нас просто будет много пользователей, добавим серверов помощнее». На практике высоконагруженная система отличается от обычной не количеством, а характером нагрузки и требованиями к отказоустойчивости. Приложение, которое отлично работает на тысяче пользователей, может полностью лечь на пятидесяти тысячах — не из-за нехватки мощности, а из-за архитектурных решений, которые не масштабируются линейно.
Городской масштаб приносит три специфические сложности. Первая — пиковая неравномерность: оплата проезда утром и вечером, подача деклараций в последний день срока, открытие записи к врачу в 8:00. Вторая — недопустимость простоя: если транспортный валидатор не принимает оплату, страдает реальная физическая инфраструктура, а не абстрактный «UX». Третья — связанность данных: один платёж порождает запись в биллинге, фискальный документ, push-уведомление, обновление баланса и строку в отчётности, и всё это должно остаться согласованным даже при сбое посредине цепочки.
Архитектурный фундамент: где принимаются ключевые решения
Большинство фатальных проблем высоконагруженных систем закладывается в первые недели проектирования, а проявляется через год под нагрузкой. Поэтому мы начинаем не с фреймворка, а с модели нагрузки: сколько запросов в секунду ожидается в пике, какое соотношение чтения и записи, какие операции критичны к задержке, а какие можно выполнять асинхронно.
Базовый принцип — разделять путь чтения и путь записи. В городских системах чтений почти всегда на порядок больше: расписания, балансы, статусы смотрят постоянно, а изменяют редко. Это позволяет выносить чтение на реплики и кэш, разгружая основную базу, которая остаётся узким местом любой системы.
Как мы держим нагрузку: практические слои
Устойчивость к нагрузке — это не одно решение, а несколько слоёв защиты, каждый из которых снимает часть давления с следующего.
- Кэширование. Часто запрашиваемые и редко меняющиеся данные (справочники, расписания, тарифы) отдаются из кэша в памяти. Это снижает обращения к базе в разы. Главное правило — заранее продумать инвалидацию: устаревший кэш баланса опаснее, чем его отсутствие.
- Очереди и асинхронная обработка. Всё, что не обязано выполняться в момент запроса пользователя — отправка уведомлений, генерация документов, выгрузка в смежные системы — уходит в очередь. Пользователь получает быстрый ответ, а тяжёлая работа выполняется в фоне с возможностью повтора при сбое.
- Горизонтальное масштабирование stateless-сервисов. Сервисы приложения не хранят состояние в себе, поэтому под пик можно добавить экземпляры за балансировщиком, а после пика убрать. Состояние живёт в базе и кэше, а не в памяти конкретного процесса.
- Пул соединений к базе. База данных переживает ограниченное число одновременных подключений. Между приложением и СУБД ставится пул (например, пулер соединений), иначе тысяча параллельных запросов исчерпает лимит коннектов и положит базу при живых серверах приложения.
- Деградация вместо падения. При перегрузке система должна отключать второстепенное (аналитику, рекомендации), но сохранять ядро — приём платежа, выдачу билета. Полный отказ всегда хуже частичного.
Данные и согласованность под нагрузкой
В городских системах с деньгами и юридически значимыми действиями согласованность данных важнее скорости. Здесь мы придерживаемся строгого подхода: критичные операции (списание баланса, регистрация платежа) выполняются в транзакциях с понятными гарантиями, а не «отложенно ради скорости».
Отдельная дисциплина — идемпотентность. Сеть в реальном городе ненадёжна: мобильный клиент теряет связь и повторяет запрос. Если повторная отправка оплаты спишет деньги дважды, доверие к платформе рушится мгновенно. Поэтому каждая платёжная операция получает уникальный ключ, и повтор с тем же ключом не создаёт дубль, а возвращает результат первой операции.
Не менее важна устойчивость интеграций. Городская система почти никогда не существует изолированно — она общается с банками, фискальными операторами, госреестрами. Эти внешние сервисы падают и тормозят независимо от вас. Поэтому каждый внешний вызов оборачивается таймаутами, повторами с задержкой и «предохранителем» (circuit breaker), который временно отключает обращения к упавшему сервису, чтобы он не утянул за собой всю платформу.
Наблюдаемость: нельзя удержать то, что не видишь
Высоконагруженную систему невозможно эксплуатировать вслепую. До запуска под реальную аудиторию мы закладываем три вещи: метрики (нагрузка, задержки, ошибки по каждому критичному пути), структурированные логи и трассировку запросов через все компоненты. Это превращает разбор инцидента из многочасового гадания в точечную диагностику.
Отдельно настраивается мониторинг бизнес-показателей, а не только технических. Падение числа успешных платежей в минуту — сигнал раньше и важнее, чем рост загрузки процессора. Хорошая система оповещает о проблеме до того, как о ней сообщат пользователи и СМИ.
Типичные ошибки, которые мы видим в существующих проектах
Когда нас приглашают спасать систему, которая «не держит нагрузку», набор причин почти всегда повторяется:
- Нагрузочное тестирование не проводилось вовсе либо проводилось на нереалистичных данных — пиковая нагрузка впервые встречается в день запуска.
- Состояние хранится в памяти процесса (сессии, кэши), из-за чего систему невозможно масштабировать горизонтально без потери данных пользователей.
- Синхронные вызовы внешних сервисов в основном потоке запроса — один медленный банк подвешивает всю платформу.
- Отсутствие лимитов и защиты от всплесков, из-за чего один сбойный клиент или ботнет кладёт систему.
- Деплой без возможности отката и без «прогрева» — обновление в час пик превращается в инцидент.
Хорошая новость в том, что почти всё это лечится поэтапно, без полной переписки, если за дело берётся команда, которая понимает природу нагрузки.
Вывод
Системы городского масштаба выигрывают не за счёт модных технологий, а за счёт дисциплины: честной модели нагрузки на старте, разделения чтения и записи, асинхронной обработки, идемпотентности платежей, устойчивых интеграций и реальной наблюдаемости. Каждый из этих слоёв снимает часть риска, и вместе они дают систему, которая остаётся стабильной в пиковые часы и предсказуемой в эксплуатации. Если вы запускаете или развиваете платформу, от которой зависит ежедневная жизнь тысяч горожан или работа госсектора, обсудите архитектуру с командой OneDev до того, как нагрузка проверит её за вас — мы поможем спроектировать решение, рассчитанное на рост, и подскажем, где у текущей системы узкие места.
С какого числа пользователей систему уже считают высоконагруженной?
Обязательно ли строить микросервисы для большого масштаба?
Можно ли усилить существующую систему без полной переписки?
Что обычно становится узким местом первым?
Как избежать двойных списаний при нестабильной мобильной сети?
Нужно ли нагрузочное тестирование, если запуск ещё нескоро?
Нужна похожая система или хотите обсудить проект?
Опишите задачу — предложим архитектуру, технический подход и план работ. Часто для старта достаточно короткого звонка.
Обсудить проект