Проектирование Амазон
Введение
Amazon — крупнейший в мире интернет-магазин и один из самых богатых объектов для System Design. Несколько цифр для понимания масштаба:
- ~300 млн активных покупателей
- ~350 млн товаров в каталоге
- ~1.6 млн заказов в день (~18 заказов/сек в среднем)
- ~400 заказов/сек в пике (Black Friday, Prime Day)
- ~$500 млрд товарооборот в год
На первый взгляд это «просто интернет-магазин». Но под капотом — несколько принципиально сложных задач, которые делают Amazon классикой System Design:
- Инвентарь: два пользователя одновременно кладут в корзину последний товар — кто его получит? Это классическая проблема параллельного доступа к ограниченному ресурсу
- Корзина покупателя: именно ради надёжности корзины Amazon написал Dynamo Paper (2007) — один из самых влиятельных текстов в распределённых системах
- Размещение заказа: одно нажатие «Купить» затрагивает оплату, инвентарь, склад и доставку — как обеспечить согласованность без распределённых транзакций?
- Пиковые нагрузки: в первые минуты Prime Day трафик вырастает в 10 раз за секунды — как не положить систему?
- Рекомендации: Amazon в 2003 году опубликовал paper по Item-to-Item Collaborative Filtering — алгоритм, который изменил e-commerce навсегда
Именно эти задачи мы и будем проектировать.
Требования
Функциональные требования
| # | Требование |
|---|---|
| FR-1 | Пользователь может искать товары по ключевым словам и фильтрам (цена, категория, рейтинг) |
| FR-2 | Пользователь может просматривать карточку товара с описанием, фото и наличием |
| FR-3 | Пользователь может добавить товар в корзину и управлять её содержимым |
| FR-4 | Пользователь может оформить заказ: выбрать адрес доставки и оплатить |
| FR-5 | Пользователь видит статус своего заказа и историю покупок |
| FR-6 | Система отображает персональные рекомендации на основе истории просмотров и покупок |
Нефункциональные требования
| # | Требование | Целевое значение |
|---|---|---|
| NFR-1 | Время загрузки карточки товара | < 200 мс (p99) |
| NFR-2 | Задержка поиска | < 100 мс |
| NFR-3 | Корзина: сохранность данных | 100% (корзина не теряется никогда) |
| NFR-4 | Инвентарь: корректность | Не допускать overselling (продажа сверх наличия) |
| NFR-5 | Доступность | 99.99% (~54 минуты downtime в год) |
| NFR-6 | Масштабируемость | 10x пиковая нагрузка (Prime Day) без деградации |
Оценка масштаба
Нагрузка на каталог
300 млн активных покупателей. Допустим, 60 млн DAU, каждый просматривает ~20 карточек товаров:
Просмотров карточек в день = 60 000 000 × 20 = 1.2 млрд/день
Просмотров в секунду (avg) = 1.2 млрд / 86 400 ≈ 14 000 RPS
Просмотров в секунду (пик) ≈ 14 000 × 5 = 70 000 RPS
70 000 запросов к карточкам товаров в секунду — это критически важный показатель. Без агрессивного кеширования база данных станет узким местом.
Поиск
Каждый DAU выполняет ~5 поисковых запросов:
Поисковых запросов/сек (avg) = 60 000 000 × 5 / 86 400 ≈ 3 500 RPS
Заказы и инвентарь
Заказов в секунду (avg) = 1 600 000 / 86 400 ≈ 18 зак/сек
Заказов в секунду (пик) ≈ 400 зак/сек (Black Friday, Prime Day)
Обновлений инвентаря/сек (пик) ≈ 400 × 5 позиций = 2 000 обновлений/сек
2 000 обновлений инвентаря в секунду — высокая нагрузка на запись. При этом нужна строгая корректность: два разных покупателя не должны купить один и тот же последний экземпляр.
Хранилище
Товаров в каталоге: 350 000 000
Метаданные одного товара (~10 КБ): 3.5 ТБ суммарно
Фото (avg 5 фото × 200 КБ на товар): 350 ТБ суммарно
Заказов в год: 1.6 млн × 365 ≈ 584 млн строк
Фотографии — очевидный кандидат для Object Storage + CDN.
High-level архитектура
┌──────────────────────────────────────────────────────────────────────────┐
│ ПОКУПАТЕЛЬ │
└────────────────────────────────────┬─────────────────────────────────────┘
│ HTTPS
▼
┌───────────────────────┐
│ API Gateway / │
│ Load Balancer │
└───────────┬───────────┘
│
┌──────────────────┬──────────────────┬──────────────────┐
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Catalog │ │ Search │ │ Cart │ │ Order │
│ Service │ │ Service │ │ Service │ │ Orchestrator│
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ DynamoDB │ │Elasticsearch │ │ Redis │ │Inventory/Payment/│
│ + Redis │ │ Index │ │ (корзина) │ │Fulfillment Svc │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────────┘
│
┌───────────────────────────────────┘
▼
┌──────────────────────────┐
│ Message Queue (Kafka) │
└──────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Payment │ │ Fulfillment │ │Recommendation│
│ Service │ │ Service │ │ Service │
└──────────────┘ └──────────────┘ └──────────────┘
Система разделена на независимые домены:
- Catalog — карточки товаров, описания, фото
- Search — поиск по каталогу
- Cart — корзина покупателя
- Order Orchestrator — координация шагов при оформлении заказа
- Inventory — учёт остатков товаров
- Fulfillment — передача заказа на склад
- Recommendation — персональные рекомендации
Каталог товаров и поиск
Модель данных каталога
Товарная карточка — это денормализованный документ. У каждого товара:
- Базовые поля: название, описание, бренд, цена, категория
- Атрибуты: у телефона — объём памяти и цвет, у одежды — размер и материал
- Медиа: список URL фотографий в Object Storage (S3)
Реляционная база данных плохо подходит для разнородных атрибутов: таблица с 500 колонками — это антипаттерн. Сравним подходы:
| Подход | База данных | Плюсы | Минусы |
|---|---|---|---|
| JSON-колонка | PostgreSQL | Гибкость атрибутов, поддержка SQL | Сложный поиск по атрибутам |
| Документная БД | DynamoDB / MongoDB | Нативная гибкость схемы | Нет JOIN, сложные транзакции |
| EAV (entity-attribute-value) | MySQL | Произвольные атрибуты | Крайне медленные запросы |
Amazon использует DynamoDB для каталога: точечное чтение по product_id за единицы миллисекунд, горизонтальное масштабирование из коробки. Бизнес-логика и сложные запросы выносятся на уровень приложения.
Кеширование карточек
70 000 RPS на чтение карточек — критично. Популярные товары читаются тысячи раз в секунду, но их данные меняются редко (цена — чаще, остаток — постоянно):
Покупатель ──► Catalog Service
│
├── [cache hit] ──► Redis (TTL 5 мин) ──► ответ < 1 мс
│
└── [cache miss] ──► DynamoDB ──► записать в Redis ──► ответ
Топ-10% самых популярных товаров отвечают за 80%+ запросов — Redis поглощает большую часть нагрузки с базой данных.
Инвалидация кеша: когда цена меняется — удаляем ключ из Redis. Следующий запрос пойдёт в DynamoDB и запишет свежие данные. Это простой паттерн cache-aside, он работает здесь, потому что секундная задержка после смены цены допустима.
Поиск: Elasticsearch
Поиск по 350 млн товаров требует инвертированного индекса (inverted index — структура данных: слово → список документов, содержащих это слово). Это работа для Elasticsearch.
Индексируемые поля:
title (boost: 3.0) → "iPhone 15 Pro Max 256GB"
description (boost: 1.0)
brand (boost: 2.0) → "Apple"
category → "Смартфоны"
attributes → {"color": "черный", "storage": "256GB"}
price → 89990
rating → 4.8
sales_count → 15000 ← для сортировки по популярности
Как строится индекс: при создании или обновлении товара в Kafka публикуется событие product.updated. Index Service слушает очередь и обновляет Elasticsearch асинхронно. Небольшая задержка (секунды) между изменением товара и его переиндексацией приемлема.
Product DB ──► product.updated (Kafka) ──► Index Service ──► Elasticsearch
Проблема: новые товары плохо ранжируются. У нового товара нет продаж и отзывов — он уходит в конец выдачи. Решение: time decay boost — товары с датой добавления «сегодня» получают временный boost к score, который убывает экспоненциально.
Корзина покупателя: уроки Dynamo
Amazon Dynamo Paper (2007) — один из основополагающих текстов в distributed systems. Dynamo был создан именно для решения задачи корзины покупателя. Почему это сложно?
Требования к корзине:
- Доступна всегда: даже при частичном отказе инфраструктуры пользователь должен добавить товар в корзину
- Сохраняется между устройствами: добавил на телефоне — видишь на компьютере
- Не теряет данные: операция добавления не должна «потеряться» ни при каких условиях
Проблема согласованности: пользователь одновременно открыл корзину на телефоне и на компьютере и добавил разные товары. Как объединить изменения?
Модель данных корзины
cart
├── user_id (ключ партиции)
├── version (для разрешения конфликтов)
└── items: [
{ product_id, quantity, price_at_add, added_at },
{ product_id, quantity, price_at_add, added_at },
...
]
Корзина — не список строк, а единый документ. Хранится в Redis:
KEY: cart:{user_id}
VALUE: {"items": [...], "version": 7, "updated_at": 1710000000}
TTL: 30 дней (гость) / без TTL (авторизованный пользователь)
Стратегия: eventual consistency с merge
Если корзина обновлялась одновременно на двух устройствах — выбираем стратегию разрешения конфликта:
- Last-write-wins: последнее обновление побеждает → можно потерять товары с другого устройства
- Add-wins merge: объединяем оба состояния, берём union → корзина может «распухнуть», но товары не теряются
Amazon выбрал add-wins merge: потерять товар из корзины хуже, чем показать лишний. Это бизнес-решение, а не техническое.
Устройство A добавило: [товар1, товар2]
Устройство B добавило: [товар1, товар3]
После merge: [товар1, товар2, товар3] ← union по product_id
Цена в корзине vs. актуальная цена
Цена товара в момент добавления в корзину ≠ цена при оплате. Политика Amazon: при переходе к checkout Cart Service запрашивает актуальные цены у Catalog Service. price_at_add хранится только для отображения «цена изменилась» — предупреждения, которое пользователь видит в корзине.
Инвентарь: проблема параллельности
Это сердце Amazon с точки зрения корректности. Overselling — продажа товара, которого нет в наличии — это бизнес-катастрофа. 10 000 пользователей нажали «Купить» на страничке с последним iPhone — кто его получит, и кто получит ошибку?
Наивное решение (неправильное)
1. SELECT quantity FROM inventory WHERE product_id = X
2. IF quantity > 0:
3. UPDATE inventory SET quantity = quantity - 1 WHERE product_id = X
4. CREATE ORDER
Проблема: между шагами 1 и 3 другой процесс успевает уменьшить quantity. Это race condition (состояние гонки) — оба покупателя прошли проверку, оба уменьшили счётчик, один получил товар которого уже нет.
Решение 1: пессимистичная блокировка
BEGIN TRANSACTION; SELECT quantity FROM inventory WHERE product_id = X FOR UPDATE; -- блокируем строку IF quantity > 0: UPDATE inventory SET quantity = quantity - 1 WHERE product_id = X; COMMIT; ELSE: ROLLBACK;
FOR UPDATE блокирует строку — другие транзакции ждут её освобождения. Гарантированно корректно, но при 2 000 обновлений/сек «горячий» товар становится узким местом: всё выстраивается в очередь на одну строку.
Решение 2: оптимистичная блокировка (версионирование)
-- Читаем версию: SELECT quantity, version FROM inventory WHERE product_id = X -- Получаем: quantity=5, version=42 -- Обновляем только если версия не изменилась: UPDATE inventory SET quantity = quantity - 1, version = version + 1 WHERE product_id = X AND version = 42 AND quantity > 0 -- Если affected_rows = 0 → кто-то успел раньше → повторить попытку
Нет блокировок — транзакции не ждут друг друга. При низкой конкуренции (большинство товаров) работает отлично. При высокой конкуренции (один горячий товар) количество повторных попыток растёт экспоненциально.
Решение 3: очередь на запись (для Flash Sales)
При сотнях покупателей в секунду на один товар — кардинально иное решение. Все запросы на покупку конкретного товара сериализуются через одну партицию Kafka:
10 000 запросов "купить iPhone" ──► Kafka (partition by product_id)
│
▼ (один consumer на одну партицию)
Inventory Service
│
┌──────────┴──────────┐
▼ ▼
[quantity > 0] [quantity = 0]
резервируем, отправляем
создаём заказ "sold out"
Один consumer для одного product_id — никакой конкуренции за блокировки. Порядок обработки — FIFO. Цена — задержка ответа: пользователь получает «заказ создан» не мгновенно, а после прохождения через очередь. Для flash sales это приемлемо.
Сравнение подходов
Обычные заказы Flash Sale / горячий товар
──────────────────────────────────────────────────────────────────────
Конкуренция Низкая Высокая (сотни/сек на товар)
Решение Оптимистичная блок. Kafka очередь + один consumer
Задержка ответа Мгновенная Секунды (очередь)
Сложность Простое Требует отдельной инфраструктуры
Размещение заказа: паттерн Saga
«Оформить заказ» — это не одно действие. Под капотом — последовательность шагов:
- Проверка инвентаря — товар ещё в наличии?
- Резервирование инвентаря — уменьшить остаток
- Авторизация платежа — проверить карту, захолдить сумму
- Создание записи заказа — запись в Order DB
- Передача на склад — уведомление Fulfillment Service
- Отправка подтверждения — email покупателю
Это распределённая транзакция: каждый шаг затрагивает отдельный сервис. Классическое решение — двухфазный коммит (2PC) — здесь не подходит: он требует одновременной доступности всех сервисов и создаёт долгие блокировки. При отказе одного сервиса вся система зависает.
Паттерн Saga
Saga — последовательность локальных транзакций. Каждая транзакция публикует событие, которое запускает следующий шаг. При сбое на любом шаге запускаются компенсирующие транзакции — отмена предыдущих шагов.
[1. Reserve Inventory]
│ inventory.reserved
▼
[2. Authorize Payment]
│ payment.authorized
▼
[3. Create Order Record]
│ order.created
▼
[4. Notify Fulfillment]
│ fulfillment.requested
▼
[5. Send Confirmation]
При сбое на шаге 2 (авторизация платежа провалилась):
[2. Authorize Payment] ──✕── ОТКАЗ (недостаточно средств)
│
▼ компенсирующая транзакция
[1C. Release Inventory] ← возвращаем зарезервированный остаток
Два варианта реализации Saga
Choreography (хореография): сервисы слушают события Kafka и реагируют самостоятельно. Нет центрального координатора — каждый сервис знает, что делать после получения события.
Kafka события:
inventory.reserved ──► Payment Service начинает авторизацию
payment.authorized ──► Order Service создаёт запись
payment.failed ──► Inventory Service освобождает резерв
order.created ──► Fulfillment Service принимает заказ
Orchestration (оркестрация): центральный Order Orchestrator управляет всей Saga.
Order Orchestrator:
1. → inventory-service: reserve(product_id, qty)
← OK: inventory.reserved
2. → payment-service: authorize(user_id, amount, idempotency_key)
← OK: payment.authorized
3. → order-service: create(order_details)
← OK: order.created
...
| Подход | Плюсы | Минусы |
|---|---|---|
| Choreography | Нет единой точки отказа | Логика размазана по сервисам, сложно дебажить |
| Orchestration | Централизованная логика, полный трейсинг | Orchestrator — единая точка отказа |
Amazon использует оркестрацию для заказов: один сервис знает полный статус каждого заказа — это критично для customer support и разрешения споров.
Идемпотентность
При ретраях важно не выполнить операцию дважды. Payment Service должен быть идемпотентным (idempotent — повторный вызов с тем же ключом возвращает тот же результат без повторного списания). Реализация: idempotency_key = order_id при каждом запросе к платёжной системе. Если ключ уже обрабатывался — возвращаем закешированный ответ.
Flash Sales и пиковые нагрузки
Prime Day и Black Friday — тест на прочность. Amazon должен выдержать 10x нормального трафика за секунды. В 2013 году на старте Prime Day Amazon упал на 20 минут. Что пошло не так?
Проблема: thundering herd
10 000 000 пользователей одновременно обновляют страницу в 12:00:00 — это thundering herd (стадо буйволов). Все кеши холодные (Redis только что перезапустили для подготовки к акции), все запросы идут в базу данных одновременно. База не выдерживает — падает. Следом падает кеш, потому что все запросы снова идут в базу. Cascading failure (каскадный отказ).
Решение 1: прогрев кеша (cache warming)
За несколько минут до старта акции — проактивно загружаем в Redis все карточки акционных товаров:
11:55:00 ──► Cache Warming Job: загрузить топ-1000 товаров Prime Day в Redis
12:00:00 ──► Старт акции
95% запросов → cache hit → Redis → < 1 мс
5% запросов → cache miss → DynamoDB → записать в Redis
К моменту thundering herd кеш уже тёплый. База данных не видит пика.
Решение 2: виртуальная очередь для дефицитных товаров
Горячий товар (iPhone за ₽9 990, 500 штук) — вместо того чтобы пустить всех одновременно, показываем виртуальную очередь:
Покупатель нажимает "Купить" в 12:00:01
│
▼
Queue Service: вы получили позицию #4731
│
│ (Inventory Service обрабатывает ~50 покупок/сек)
▼
~95 секунд → ваша очередь → оформляйте заказ
(позиции > 500 → немедленно "sorry, sold out")
Это защищает Order Service и Inventory Service от пикового всплеска, распределяя нагрузку во времени.
Решение 3: rate limiting на уровне API Gateway
Token bucket (алгоритм «ведро с токенами»: N токенов добавляется в секунду, каждый запрос тратит один токен) — ограничение на пользователя:
Пользователь API Gateway Backend
│ │ │
│── GET /product/X ─────► │ [bucket: 10/10] ──►│
│── GET /product/X ─────► │ [bucket: 9/10] ──►│
│── GET /product/X ─────► │ [bucket: 0/10] │
│◄── 429 Too Many Requests ────────────────────│
│ Retry-After: 1s │ │
Решение 4: predictive scaling
Auto-scaling по метрикам CPU и latency включается за минуты, но для cold start новых инстансов нужно время. Решение: масштабируемся по расписанию, а не реактивно. За час до Prime Day — уже вдвое больше инстансов. За 30 минут — в 5 раз больше.
Рекомендации: Item-to-Item Collaborative Filtering
«Покупатели также купили» — алгоритм, описанный Amazon в paper 2003 года. По некоторым оценкам он принёс компании 35% дополнительной выручки. Идея:
Если покупатели, купившие товар A, часто покупают товар B → рекомендуем B всем, кто смотрит на A.
Построение матрицы совместных покупок
История покупок:
Иван: [iPhone, AirPods, чехол]
Мария: [iPhone, зарядка MagSafe, кабель USB-C]
Алексей: [MacBook, AirPods, мышь Magic Mouse]
Подсчёт совместных покупок (пары в одном заказе):
iPhone + AirPods: 2 раза
iPhone + чехол: 1 раз
iPhone + зарядка: 1 раз
MacBook + AirPods: 1 раз
MacBook + мышь: 1 раз
Нормализуем: для каждого товара — список наиболее часто совместно покупаемых товаров, отсортированных по частоте. Это и есть Item-to-Item матрица — справочник: product_id → [top-10 рекомендаций].
Архитектура рекомендательного сервиса
Kafka (события покупок) ──► Batch Job (Apache Spark, каждые N часов)
│
│ пересчёт матрицы
▼
Item-Item матрица
(product_id → top-10 похожих товаров)
│
▼
DynamoDB / Redis
│
▼
Recommendation Service ──► "С этим покупают..."
Почему batch, а не real-time? Матрица рассчитывается на основе миллиардов пар покупок — это тяжёлая агрегация. Пересчёт раз в несколько часов даёт достаточно свежие данные, при этом Spark-джоб не влияет на latency продовых запросов.
Онлайн-персонализация
Поверх статической Item-Item матрицы — персональный слой. История просмотров конкретного пользователя используется для re-ranking рекомендаций:
Статические рекомендации для iPhone (Item-Item матрица):
[AirPods, чехол, зарядка MagSafe, кабель USB-C, ...]
Пользователь смотрел: MacBook Pro, iPad Air
Персонализация (re-ranking):
[кабель USB-C, AirPods, зарядка MagSafe, чехол, ...] ← USB-C поднялся выше
Персонализация происходит в реальном времени при формировании ответа: берём статическую матрицу, применяем веса по истории просмотров. Это быстрее, чем real-time переобучение модели, и достаточно эффективно.
Ключевые trade-offs
| Решение | Альтернатива | Почему так |
|---|---|---|
| DynamoDB для каталога | PostgreSQL | Разнородные атрибуты; 70 000 RPS на чтение; горизонтальное масштабирование из коробки |
| Redis + merge-стратегия для корзины | SQL таблица cart_items | Корзина — hot data, нужна мс-задержка; merge защищает от потери товаров при конфликтах между устройствами |
| Оптимистичная блокировка для инвентаря | SELECT FOR UPDATE | Меньше блокировок при нормальной нагрузке; не блокирует строку, пока другие транзакции ждут |
| Kafka очередь для Flash Sales | Пессимистичная блокировка на всех | Сериализация через один consumer исключает race condition без БД-блокировок; масштабируется горизонтально |
| Saga с оркестрацией для заказов | 2PC / хореография | 2PC требует синхронности всех участников; хореография сложно дебажить; оркестрация даёт полный трейсинг для support |
| Batch Item-Item для рекомендаций | Real-time ML-модель | Spark-джоб раз в несколько часов дешевле и стабильнее; свежесть матрицы достаточна для бизнеса |
| Predictive scaling перед Prime Day | Только reactive auto-scaling | Auto-scaling реагирует с задержкой в минуты; thundering herd убивает систему раньше, чем успевают стартовать новые инстансы |
Amazon vs. небольшой e-commerce
Amazon (пик) Средний магазин OZON/Wildberries
────────────────────────────────────────────────────────────────────────────
Заказы 400/сек < 1/сек 50/сек
Каталог 70 000 RPS < 100 RPS 5 000 RPS
Инвентарь Opt. lock + queue Pessimistic lock Queue/opt. lock
Корзина Dynamo / Redis БД-таблица Redis
Транзакции Saga + оркестр. Простая транзакция Saga/оркестрация
Рекомендации Item-Item + boost Плагин / нет Item-Item + DL
Итого
Покупатель ──[HTTPS]──► API Gateway / Load Balancer
│
┌─────────────────────┼────────────────────────┐
▼ ▼ ▼
Catalog Service Search Service Cart Service
(DynamoDB + Redis) (Elasticsearch) (Redis, add-wins
70 000 RPS кеш 3 500 RPS merge)
│ │
└──────────────────────┬───────────────────────┘
│ оформление заказа
▼
Order Orchestrator
│
┌─────────────────┼──────────────────┐
▼ ▼ ▼
Inventory Payment Fulfillment
Service Service Service
(opt. lock / (идемпотентный, (Kafka →
Kafka queue) idempotency_key) склад)
│
▼
Order DB ──► Status API (история заказов)
│
└──► Kafka events ──► Recommendation Service
(batch Spark job
+ Redis матрица
+ online re-ranking)
Три главных инсайта, которые стоит запомнить:
-
Корзина — это не CRUD, это распределённый документ. Amazon написал Dynamo Paper именно ради корзины. Ключевой урок: для данных, которые должны быть доступны всегда, eventual consistency с add-wins merge лучше, чем сильная консистентность с риском недоступности. Потерять товар из корзины хуже, чем показать лишний.
-
Инвентарь требует разных решений для разных сценариев. При нормальной нагрузке — оптимистичная блокировка с версионированием: нет блокировок, низкая конкуренция. При Flash Sales на горячий товар — очередь с одним consumer на
product_id: сериализация вместо конкуренции. Нет универсального ответа — контекст определяет инструмент. -
Распределённые транзакции — это Saga, а не 2PC. В системе с десятками независимых сервисов двухфазный коммит становится узким местом и источником deadlock'ов. Saga с компенсирующими транзакциями — правильная абстракция для e-commerce: eventually consistent, но корректный. Оркестрация предпочтительна там, где важен полный трейсинг каждого заказа.