Stateless и Stateful — это не про «сервер всё забывает» и «сервер всё помнит». Это про то, кто отвечает за хранение контекста между запросами и какую архитектурную цену вы платите за это решение. На практике выбор между этими двумя моделями определяет, сможете ли вы масштабироваться горизонтально или упрётесь в потолок при росте нагрузки.
Если вы проектируете REST API — центральные принципы и конвенции разобраны в базовых принципах REST API. А здесь — объяснение stateless/stateful на конкретных примерах кода с пояснениями построчно. Текущий пост — более глубокий разбор: архитектурные последствия, масштабирование и осознанный выбор модели.
Что такое состояние в контексте API
Состояние — это контекст, который связывает разрозненные HTTP-запросы в единый пользовательский сценарий. Без состояния каждый запрос — изолированное событие, как отдельная страница в блокноте. С состоянием — это главы одной книги, где каждая следующая страница продолжает предыдущую.
Что может быть состоянием:
- Сессия пользователя — факт аутентификации, время входа, роль, права доступа
- Промежуточные данные — содержимое корзины до оформления заказа, незавершённая форма, черновик документа
- Рабочий контекст — на каком шаге многошагового процесса находится клиент (визард, онбординг, оформление кредита)
- Пользовательские настройки — выбранный язык, тема интерфейса, активные фильтры в каталоге
Главный вопрос: где это состояние хранить — на клиенте или на сервере? Ответ на него разделяет stateless и stateful архитектуры. И цена ошибки здесь — переписывание половины бэкенда.
Stateless: каждый запрос сам за себя
В stateless-модели сервер не хранит абсолютно никакой информации о клиенте между запросами. Каждый запрос обязан содержать все данные, необходимые для его обработки.
На диаграмме: два запроса от одного клиента приземляются на разные серверы — балансировщику без разницы, куда отправлять. Оба сервера успешно обрабатывают запросы. Секрет в том, что состояние лежит в базе данных — едином источнике правды, доступ к которому есть у каждого сервера. JWT-токен в заголовке подтверждает личность клиента, но сами серверы про этого клиента «забывают» сразу после ответа.
В базовых принципах REST API statelessness — один из ключевых архитектурных ограничений. REST без него формально перестаёт быть REST.
Как это выглядит в коде
// Stateless endpointapp.get('/orders', async (req, res) => { const token = req.headers.authorization; const userId = verifyJWT(token); const orders = await db.query( 'SELECT * FROM orders WHERE user_id = ?', [userId] ); res.json(orders);});Каждая строчка делает ровно то, что написано: извлекли токен из заголовка, проверили, сходили в базу, вернули результат. Никакой памяти о предыдущем запросе. Никаких скрытых зависимостей между вызовами.
Сервер можно перезагрузить в любой момент — следующий запрос приедет с токеном, и всё продолжит работать как ни в чём не бывало.
Плюсы stateless
Горизонтальное масштабирование становится тривиальным. Добавили ещё 10 серверов за балансировщиком — и они сразу готовы к работе. Не нужно синхронизировать сессии между экземплярами, настраивать sticky sessions, думать о репликации состояния. Это основная причина, почему облачные платформы (AWS, GCP, Azure) спроектированы под stateless-сервисы. Kubernetes поднимает и убивает поды — и ничего не ломается.
Отказоустойчивость. Упал сервер API №1? Балансировщик перестал слать на него трафик. Клиент даже не заметил — его запрос ушёл на сервер №2. В stateful-мире клиент потерял бы сессию и увидел ошибку.
Простота тестирования. Каждый запрос изолирован, можно писать модульные тесты без поднятия Redis для сессий и сложного окружения. Меньше моков — быстрее тесты — выше покрытие.
Кэширование на стороне сервера. Поскольку ответ зависит только от переданных данных, результаты GET-запросов можно агрессивно кэшировать. CDN, nginx, Redis — все работают «из коробки».
Минусы
Плата за простоту — объём передаваемых данных. Токен аутентификации, параметры пагинации, идентификаторы ресурсов — всё это кочует из запроса в запрос. В высоконагруженных системах оверхед заметен. Плюс каждый запрос потенциально требует повторной загрузки данных из базы, которые в stateful-модели могли бы лежать в оперативной памяти.
Примечательно, что для большинства бизнес-приложений это приемлемая цена. Условный интернет-магазин на 10 000 заказов в день не почувствует разницы.
Stateful: сервер, который помнит
Stateful-модель предполагает, что сервер хранит состояние клиента между запросами в своей оперативной памяти или внешнем быстром хранилище (Redis, Memcached). Клиент передаёт только идентификатор сессии, а сервер восстанавливает контекст по этому идентификатору.
Диаграмма показывает три запроса от клиента. Первый — логин, сервер создаёт сессию и отдаёт идентификатор sess_abc. Второй и третий запросы передают этот идентификатор, и сервер восстанавливает контекст из хранилища сессий. Обратите внимание: повторная аутентификация не нужна — идентификатор сессии её заменяет. В этом ключевое удобство stateful для клиента.
Когда stateful — зло (и когда нет)
В контексте REST API stateful считается антипаттерном. Но выкидывать stateful на свалку истории рано. Есть сценарии, где он оправдан на 100%:
WebSocket-соединения. Чат, онлайн-игра, биржевой терминал — здесь состояние соединения принципиально. Клиент и сервер держат открытый канал, и контекст (комната чата, игровая сессия, подписки на котировки) живёт на стороне сервера. Иначе сделать невозможно.
FTP, SSH и прочие протоколы с длинными сессиями. FTP открывает управляющее соединение, авторизуется один раз и дальше гоняет файлы. Stateful по стандарту — и никто не жалуется.
Банковские и платёжные шлюзы с двухфазными транзакциями. Иногда нужно зафиксировать промежуточное состояние (платёж инициирован, ожидается подтверждение от эквайера), и терять его между запросами нельзя. Здесь stateful — не баг, а требование безопасности.
Легаси-монолиты, которые никто не перепишет. «Работает — не трогай». Десятки тысяч строк кода с серверными сессиями, которые никто не даст рефакторить. Иногда дешевле купить ещё пару серверов, чем переписывать.
Подводные камни масштабирования stateful
Главная боль: если клиент «привязан» к конкретному экземпляру сервера (sticky session), то при падении этого экземпляра клиент теряет сессию. Приходится городить: репликацию сессий между экземплярами, внешние Redis-хранилища, сложные схемы фейловера.
При росте нагрузки нельзя просто взять и добавить серверов — нужно параллельно реплицировать сессионное состояние. Каждый новый экземпляр либо копирует память с соседей, либо ходит в общее Redis-хранилище. И то и другое добавляет сетевую задержку. В stateless-мире этой проблемы просто не существует.
Таблица сравнения
| Критерий | Stateless | Stateful |
|---|---|---|
| Хранение состояния | На клиенте (токен, query-параметры) | На сервере (память, Redis, БД) |
| Масштабирование | Горизонтальное, линейное | Ограничено sticky sessions или стоимостью репликации |
| Отказоустойчивость | Высокая — любой сервер обработает запрос | Зависит от механизма восстановления сессий |
| Простота тестирования | Изолированные юнит-тесты | Требуется контекст сессии |
| Объём данных в запросе | Выше (токен, параметры, контекст) | Ниже (только session ID) |
| Задержка (latency) | Каждый запрос ходит в БД | Часть данных может быть в памяти |
| Рестарт сервера | Без последствий | Потеря сессий без персистентного хранилища |
| Типичные протоколы | REST, GraphQL | WebSocket, FTP, SSH, gRPC-стримы |
| Идемпотентность | Легко обеспечить | Сложнее из-за зависимостей между запросами |
Таблица не означает, что stateless всегда лучше. Stateful проигрывает по масштабированию — но выигрывает по latency для повторяющихся операций. Вопрос в том, что для вас критичнее.
Как сделать stateful более живучим
Если совсем без состояния нельзя (например, WebSocket-чат), есть несколько стратегий выживания:
-
Вынос сессионного состояния в Redis/Memcached. Сервер всё ещё stateful, но сессии не теряются при рестарте и доступны с разных экземпляров. Минус — дополнительный сетевой вызов на каждый запрос, но для большинства приложений это доли миллисекунды.
-
Stateless-ядро + stateful-надстройка. Основная бизнес-логика stateless. Состояние требуется только для отдельных сценариев (websocket-уведомления, реалтайм-дашборды). Тогда 95% запросов масштабируются горизонтально, а оставшиеся 5% живут на выделенных серверах с WebSocket.
-
Распределённые сессии с аффинностью «best effort». Балансировщик старается отправлять клиента на тот же сервер (session affinity), но при падении сервера сессия восстанавливается из Redis. Компромисс между производительностью и надёжностью — и довольно популярный на практике.
Когда stateful — осознанный выбор
Несмотря на доминирование stateless в современной веб-разработке, есть моменты, когда stateful даёт ощутимый выигрыш:
-
Игровые серверы. В реальном времени состояние игры меняется десятки раз в секунду. Гонять его в БД на каждом тике — самоубийство. Игровой сервер держит состояние мира в оперативной памяти и синхронизирует с клиентами через UDP/WebSocket.
-
Потоковая обработка данных. Apache Flink, Kafka Streams — stateful по природе. Они держат окна агрегации, водяные знаки и чекпоинты в состоянии, и это фича, а не баг. Без состояния здесь просто невозможно посчитать скользящее среднее или детектировать аномалии в потоке.
-
AI-диалоговые системы и ассистенты. История диалога, извлечённые сущности, контекст текущей задачи — это состояние, которое живёт в рамках сессии общения с LLM. Хранение всего этого на клиенте неэффективно: в одном диалоге могут быть десятки сообщений с эмбеддингами и суммаризациями.
Это не серебряная пуля — каждый из этих кейсов требует отдельного инженерного решения для отказоустойчивости. Но осознанный stateful выигрывает у слепого stateless, когда домен требует состояния.
Заключение
Stateless и stateful — не враги и не конкуренты. Это инструменты с разной ценой владения. В мире REST API stateless — золотой стандарт: он упрощает масштабирование, тестирование и отказоустойчивость до уровня «просто добавь серверов». Но как только вы выходите за рамки «запрос-ответ» — WebSocket, стриминг, игры, диалоговые системы — stateful становится не проклятием, а осознанной необходимостью.
Главное — не вешать ярлык «плохо» на stateful. Спрашивайте: «Какую проблему мы решаем и какую цену платим за хранение состояния здесь, а не на клиенте?» Честный ответ на этот вопрос экономит месяцы переписывания архитектуры потом.
PS. Если после прочтения вы побежали переписывать stateful-сервис на stateless — перечитайте раздел про легаси. Возможно, оно того не стоит.