Logo
Overview

Stateless и Stateful: примеры, отличия и когда что использовать

May 20, 2026
10 min read

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.1
Authorization: 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-сценарий, и за консистентностью этого хранилища кто-то должен следить.

Диаграмма: как идёт запрос в каждой модели

100%
flowchart TB
  subgraph SL["Stateless"]
    C1["Клиент"] -- "Запрос 1 с токеном" --> LB1["Балансировщик"]
    LB1 --> N1A["Нода A"]
    LB1 --> N1B["Нода B"]
    N1A --> DB1["База данных"]
    N1B --> DB1
    C1 -- "Запрос 2 с тем же токеном" --> LB1
  end
  subgraph SF["Stateful с памятью на ноде"]
    C2["Клиент"] -- "Запрос 1, cookie session=abc" --> LB2["Балансировщик sticky"]
    LB2 --> N2A["Нода A с сессией abc"]
    N2A --> MEM["Память ноды: сессии"]
    C2 -- "Запрос 2 обязан прийти сюда" --> N2A
    N2A --> DB2["База данных"]
  end
  subgraph SX["Stateful с внешним хранилищем"]
    C3["Клиент"] -- "Запрос с session_id" --> LB3["Балансировщик любой"]
    LB3 --> N3A["Нода A"]
    LB3 --> N3B["Нода B"]
    N3A --> RDS["Redis: сессии"]
    N3B --> RDS
    N3A --> DB3["База данных"]
    N3B --> DB3
  end
  style C1 fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style C2 fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style C3 fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style LB1 fill:#f0a500,stroke:#c88400,color:#fff
  style LB2 fill:#f0a500,stroke:#c88400,color:#fff
  style LB3 fill:#f0a500,stroke:#c88400,color:#fff
  style N1A fill:#50c878,stroke:#3a9a5c,color:#fff
  style N1B fill:#50c878,stroke:#3a9a5c,color:#fff
  style N2A fill:#50c878,stroke:#3a9a5c,color:#fff
  style N3A fill:#50c878,stroke:#3a9a5c,color:#fff
  style N3B fill:#50c878,stroke:#3a9a5c,color:#fff
  style MEM fill:#e0e0e0,stroke:#999,color:#222
  style RDS fill:#7b68ee,stroke:#5a4db2,color:#fff
  style DB1 fill:#7b68ee,stroke:#5a4db2,color:#fff
  style DB2 fill:#7b68ee,stroke:#5a4db2,color:#fff
  style DB3 fill:#7b68ee,stroke:#5a4db2,color:#fff

На диаграмме три сценария. В stateless балансировщик отправляет любые запросы на любые ноды — те читают токен и идут в общую базу. В классическом stateful сессия живёт в памяти конкретной ноды, и пользователь привязан к ней — отсюда sticky sessions. В третьем варианте состояние вынесено в Redis: ноды снова взаимозаменяемы, но появляется лишний компонент, за которым нужно следить.

Таблица сравнения: stateless и stateful

ПараметрStatelessStateful
Где хранится контекст запросаНа клиенте (токен, параметры)На сервере (сессия, оперативная память)
МасштабированиеГоризонтально, любая нода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.

Как выбирать: короткий алгоритм

100%
flowchart TD
  Q1["Нужен ли длинный многошаговый сценарий?"]
  Q1 -- "Нет" --> SL1["Stateless: токен + контекст в запросе"]
  Q1 -- "Да" --> Q2["Можно ли описать шаги как явный автомат?"]
  Q2 -- "Да" --> SX1["Stateful во внешнем хранилище: Redis или БД"]
  Q2 -- "Нет, состояния много и динамики много" --> SF1["Stateful с серверной сессией и sticky"]
  SL1 --> R1["Простое масштабирование и retry"]
  SX1 --> R2["Масштабирование ок, но добавляется зависимость"]
  SF1 --> R3["Самый сложный режим, нужны sticky и мониторинг"]
  style Q1 fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style Q2 fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style SL1 fill:#50c878,stroke:#3a9a5c,color:#fff
  style SX1 fill:#f0a500,stroke:#c88400,color:#fff
  style SF1 fill:#7b68ee,stroke:#5a4db2,color:#fff
  style R1 fill:#e0e0e0,stroke:#999,color:#222
  style R2 fill:#e0e0e0,stroke:#999,color:#222
  style R3 fill:#e0e0e0,stroke:#999,color:#222

Это не строгий алгоритм, а скорее чек-лист на ревью. Большинство публичных REST API живут в зелёной ветке. Жёлтая — типичный гибрид для платежей и чекаутов. Фиолетовая — нишевые сценарии, где другого выхода нет.

А что про REST и HATEOAS

REST как стиль архитектуры предписывает stateless по определению. Это один из его ограничений — сервер не должен хранить клиентский контекст между запросами. Если хотите по-серьёзному погрузиться в принципы — почитайте про основные принципы REST API. Любая стейтфул-логика поверх HTTP — это не «нарушение REST», просто это уже не REST в чистом виде, а гибридная архитектура. Что, как мы видели, в проде нормальная ситуация.

Замечу важное: stateless у REST относится к серверной памяти о сессии, а не к запрету хранить данные вообще. База данных, кэши, очереди — всё это нормально. Stateless ≠ «всё в оперативке клиента».

Заключение

Stateless и stateful — это не «правильно vs неправильно». Это два инструмента с разными ценами. Stateless даёт простое масштабирование и платит за это размером запроса. Stateful даёт удобство длинных сценариев и платит сложностью эксплуатации.

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

И последнее. Если на ревью архитектуры кто-то говорит «у нас всё stateless» — уточните, что именно имеется в виду. Часто это означает «у нас сессии в Redis», что не одно и то же. Слова дешевы, схемы дороже. Работает — не трогай, но называй своими именами.