Stateless и Stateful: примеры, отличия и когда что использовать
Когда системный аналитик впервые слышит «у нас всё stateless», в голове возникает картинка из учебника: сервер, который ничего не помнит. Хорошо, не помнит — а как же логин, корзина и баланс? А если помнит, то это уже stateful? А куда тогда деть Redis, базу и кэш?
На самом деле спор «stateless или stateful» — не про память сервера вообще, а про то, кто держит контекст конкретного запроса. Это разница, которую легко проскочить в теории и больно встретить на проде.
Этот пост — практическое расширение базовой статьи Stateless и Stateful на пальцах. Если вы её уже читали — мы пойдём глубже: примеры из банкинга, e-commerce, разбор типичных ошибок и таблица сравнения, которую можно показать команде на ревью архитектуры.
Что значит «состояние» в архитектуре API
Состояние (state) — это любые данные, которые описывают «где сейчас находится клиент» внутри логики сервиса: кто он, что уже сделал, что планирует, какие у него полномочия и временные флаги.
Состояние бывает двух типов. Первое — прикладное (application state): корзина, заказ, баланс, история операций. Оно живёт в базе и никуда не девается, независимо от того, stateless у вас API или нет.
Второе — сессионное (session state): кто сейчас залогинен, на каком шаге визарда находится, какой next-step ожидается. Вот именно про этот тип состояния и идёт весь спор.
Stateless-сервис не хранит сессионное состояние между запросами. Stateful — хранит. Прикладное состояние есть и там, и там — иначе мы бы вообще ничего не считали и не сохраняли.
Stateless: каждый запрос — самостоятельная история
В stateless-подходе клиент при каждом обращении приносит с собой всё, что нужно для обработки. Сервер не помнит, кто звонил минуту назад. Получил запрос, проверил токен, обработал, ответил, забыл.
Пример из жизни — REST API банковского приложения. Вы открываете список переводов, и клиент шлёт что-то вроде:
GET /api/v1/transfers?from=2026-05-01 HTTP/1.1Authorization: Bearer eyJhbGciOi...X-Request-Id: 8f3a9c1dСервер берёт токен, валидирует, по sub достаёт user_id, идёт в базу, возвращает список. Никаких «а помню, минуту назад вы уже спрашивали». Если запрос упадёт — ретрай безопасен. Если за окном балансировщик и трафик ушёл на другую ноду — ей всё равно, она примет тот же токен.
Где stateless силён
- Горизонтальное масштабирование. Любая нода обрабатывает любой запрос. Подняли ещё одну — и в путь.
- Простой деплой. Можно гасить инстансы по одному, не теряя сессий (потому что их нет).
- Кэшируемость. Прокси и CDN могут безопасно кэшировать ответы — у них есть всё для построения ключа.
- Отказоустойчивость. Падение ноды не уносит с собой пользователей.
Где stateless больно
Платой за всю эту красоту становится размер запроса. Клиенту приходится таскать с собой токен на каждый запрос, а это лишние байты. Плюс — нужен надёжный механизм аутентификации, потому что сервер не помнит, что вы уже доказали свою личность пять секунд назад. Подробнее про этот механизм — в посте OAuth и JWT для REST API.
И ещё нюанс. Stateless — это про сервер. Сам клиент при этом стейтфулен по самые гланды: хранит токены, флаги, корзину, последний открытый экран. Куда-то состояние всё равно уходит — просто оно уезжает на клиентскую сторону.
Stateful: сервер ведёт диалог
Stateful-сервис помнит контекст. Сделали первый запрос — сервер положил в свою память «вы сейчас на шаге 2 оформления визы», и при втором запросе работает уже с учётом этого.
Классика — банковский кол-центр или legacy-системы со «сценариями оформления». Вы звоните, оператор открывает сессию, и пока она жива — все ваши действия идут в один контекст. Закрылась — начали с нуля.
В вебе stateful выглядит как сессии в памяти сервера: пришёл запрос с JSESSIONID=abc123, сервер по этому ключу нашёл объект сессии, в нём корзину, шаг чекаута, выбранный способ доставки. И отдал ответ, дописав что-то в эту же сессию.
Где stateful уместен
- Длинные интерактивные сценарии — визарды KYC, многошаговые опросы, чат-боты с памятью диалога.
- Игры реального времени — там без серверного состояния никуда.
- Связи через WebSocket — само соединение уже состояние, и логика обычно тоже.
- Старые корпоративные системы — где переписать на stateless дороже, чем продолжать жить со старым.
Где stateful ломается
Главная боль — масштабирование. Если сессия лежит в памяти конкретной ноды, балансировщик обязан гнать все запросы пользователя именно на неё (sticky sessions). Упадёт нода — пользователь потерял корзину. Подняли ещё одну ноду — она пустая, нагрузка не распределилась.
Решают это вынесением сессии в общее хранилище — Redis, Memcached, база. Формально сервис снова становится stateless: ноды одинаковые, состояние снаружи. Но логически у вас всё равно stateful-сценарий, и за консистентностью этого хранилища кто-то должен следить.
Диаграмма: как идёт запрос в каждой модели
На диаграмме три сценария. В stateless балансировщик отправляет любые запросы на любые ноды — те читают токен и идут в общую базу. В классическом stateful сессия живёт в памяти конкретной ноды, и пользователь привязан к ней — отсюда sticky sessions. В третьем варианте состояние вынесено в Redis: ноды снова взаимозаменяемы, но появляется лишний компонент, за которым нужно следить.
Таблица сравнения: stateless и stateful
| Параметр | Stateless | Stateful |
|---|---|---|
| Где хранится контекст запроса | На клиенте (токен, параметры) | На сервере (сессия, оперативная память) |
| Масштабирование | Горизонтально, любая нода | Sticky sessions или внешнее хранилище |
| Размер запроса | Больше (контекст внутри) | Меньше (только ID сессии) |
| Восстановление после сбоя | Простое — ретрай безопасен | Сложное — сессия может быть потеряна |
| Кэширование на прокси | Возможно по ключу запроса | Почти невозможно |
| Подходит для | REST API, микросервисы, мобильные API | Чаты, игры, длинные визарды, legacy |
| Аутентификация | Токен в каждом запросе (JWT, Bearer) | Session ID в cookie |
| Сложность операционная | Ниже | Выше (мониторинг сессий, sticky-логика) |
| Стоимость трафика | Выше | Ниже |
| Цена ошибки клиента | Низкая (отправил ещё раз) | Средняя (состояние могло уплыть) |
Таблица — упрощение. В реальном проекте у вас всегда смесь: REST stateless для CRUD, stateful для WebSocket-уведомлений, отдельное хранилище сессий для админки.
Реальные примеры
Пример 1. Банковское мобильное приложение — stateless
Открываете приложение, видите список карт. Каждый экран — отдельный GET-запрос. В заголовке Authorization: Bearer ... — JWT-токен с user_id, ролями и сроком жизни. Сервер не помнит, что вы только что переключались между экранами. Подняли ещё пять нод под нагрузкой Black Friday — никаких миграций сессий, просто добавили инстансы за балансировщик.
Пример 2. Чекаут в e-commerce — гибрид
Корзина лежит в базе и привязана к пользователю — это не state в нашем смысле, это данные. А вот процесс оформления часто делают stateful: на сервере живёт объект CheckoutSession, который хранит выбранную доставку, промокод, шаг и временный резерв товара. Через 30 минут сессия истекает, резерв снимается.
Можно это переписать в stateless и складывать всё в JWT-токен или localStorage. Но если пользователь оплатил, а резерв не подтвердился, придётся компенсировать руками. С серверной сессией такие гонки проще ловить.
Пример 3. Чат-бот для кол-центра — stateful
Бот ведёт длинный диалог. На каждом шаге он помнит: кто звонит, какой был первый вопрос, что уже уточнили, на каком уровне FAQ остановились. Засунуть весь этот контекст в каждый webhook от мессенджера — теоретически можно, практически — больно. Поэтому используется серверный state, чаще всего в Redis.
Пример 4. Платёжный шлюз — stateless снаружи, stateful внутри
Снаружи у API всё чисто: запросы с токеном, ответы по идемпотентному ключу. Внутри живёт длительный сценарий: «авторизация → fraud-check → списание → подтверждение». Это конечный автомат, и он stateful по природе. Но клиент об этом не знает — он просто опрашивает статус по payment_id.
Типичные ошибки при выборе подхода
Sticky sessions — режим балансировщика, в котором запросы одного клиента всегда уходят на одну и ту же ноду по идентификатору сессии или IP.
Ошибка 1. «Сделаем stateless, потому что модно». И тащим в JWT 20 килобайт прав, флагов и пользовательских настроек. Размер токена улетает, мобильный клиент жалуется на трафик, при ротации ключа всё ломается. Stateless — это про маленький контекст в запросе, а не про «давайте впихнём сюда всё».
Ошибка 2. «У нас сессия в Redis, значит stateless». Технически — да, ноды одинаковые. Логически — у вас по-прежнему длинный сценарий с накопленным контекстом, и Redis стал единой точкой отказа. Этот выбор окей, просто называйте вещи своими именами и закладывайте мониторинг.
Ошибка 3. Хранить токен и сессию одновременно. Видел в одном проекте: JWT с user_id и роль, а параллельно ещё JSESSIONID с теми же данными. Любое изменение прав вызывало рассинхрон — токен говорит «админ», сессия «менеджер». Угадайте, чей вердикт побеждал в каждой конкретной ручке.
Ошибка 4. Полагаться на stateless там, где нужна транзакционность. Идемпотентность через токен — это не транзакция. Если у вас многошаговый процесс с резервами и компенсациями, добавьте серверный state и оформите его как явный конечный автомат, а не как кучу полей в JWT.
Как выбирать: короткий алгоритм
Это не строгий алгоритм, а скорее чек-лист на ревью. Большинство публичных REST API живут в зелёной ветке. Жёлтая — типичный гибрид для платежей и чекаутов. Фиолетовая — нишевые сценарии, где другого выхода нет.
А что про REST и HATEOAS
REST как стиль архитектуры предписывает stateless по определению. Это один из его ограничений — сервер не должен хранить клиентский контекст между запросами. Если хотите по-серьёзному погрузиться в принципы — почитайте про основные принципы REST API. Любая стейтфул-логика поверх HTTP — это не «нарушение REST», просто это уже не REST в чистом виде, а гибридная архитектура. Что, как мы видели, в проде нормальная ситуация.
Замечу важное: stateless у REST относится к серверной памяти о сессии, а не к запрету хранить данные вообще. База данных, кэши, очереди — всё это нормально. Stateless ≠ «всё в оперативке клиента».
Заключение
Stateless и stateful — это не «правильно vs неправильно». Это два инструмента с разными ценами. Stateless даёт простое масштабирование и платит за это размером запроса. Stateful даёт удобство длинных сценариев и платит сложностью эксплуатации.
На своём примере могу сказать: чем больше я работаю с распределёнными системами, тем чаще выбираю явный stateful-автомат во внешнем хранилище. Не потому что «модно», а потому что в проде обычно проще отлаживать машину состояний с понятными переходами, чем длинную цепочку stateless-запросов, где половина контекста плавает в клиенте, а вторая половина — в JWT.
И последнее. Если на ревью архитектуры кто-то говорит «у нас всё stateless» — уточните, что именно имеется в виду. Часто это означает «у нас сессии в Redis», что не одно и то же. Слова дешевы, схемы дороже. Работает — не трогай, но называй своими именами.