Основы
Как сохранить историю каждого заказа
Как ключ разделяет память о заказах, когда события многих заказов приходят вперемешку.
Изучать с ИИ-ассистентом 🤖
Скопируйте промпт и вставьте его в чат с ИИ-ассистентом.
Ты — преподаватель курса «Потоковая обработка».Тема: «Как сохранить историю каждого заказа»О чём: Как ключ разделяет память о заказах, когда события многих заказов приходят вперемешку.Текст статьи в Markdown: https://stream-processing-systems.github.io/ru/course/fundamentals/keeping-each-orders-story-together.mdОпирайся на эту статью. Сначала спроси, как мне удобнее: кратко, пошагово или глубоко с вопросами на понимание. Объясняй на примере из статьи и в её терминах. Если добавляешь сведения, которых в статье нет, явно это отмечай.В нашем сервисе доставки продуктов обработчик (processor) ведёт в магазине
список заказов, которые ждут ответа покупателя.
Он помнит, ждёт ли заказ ответа:
для вашего заказа ord-1042 — «да» после предложения в 10:04 и «нет» после
вашего согласия в 10:06. Но магазин собирает много заказов одновременно. Красные
яблоки закончились, поэтому в 10:05 сборщик другого заказа, ord-1337, тоже
предлагает вместо них зелёные. Это событие (event) приходит между предложением
по вашему заказу и вашим ответом.
События обоих заказов приходят из одного источника (source) вперемешку. Когда
придёт ваше согласие, обработчик должен убрать из списка ваш заказ и оставить в
нём ord-1337. Как ему не смешать память о разных заказах и как найти нужную
память для каждого события?
Память для каждого заказа
Заголовок раздела «Память для каждого заказа»Одно сохранённое «да» или «нет» не может описать два заказа. Если бы обработчик
хранил один ответ на весь магазин, ваше согласие в 10:06 поменяло бы его на
«нет», и ord-1337 ушёл бы из списка, хотя его покупатель ещё решает.
Поэтому обработчик хранит состояние (state) отдельно для каждого заказа, как
маленькую таблицу, где у каждого заказа своя строка. Когда приходит событие,
обработчик читает его order_id, находит строку этого заказа и меняет только
её1. Значение, по которому определяется, к какой памяти относится событие,
называется ключом (key). Здесь ключ — order_id.
Чтобы хранить память по ключам, не нужны несколько машин или особый продукт. Небольшая программа может держать строку для каждого заказа, который ещё в работе.
Два заказа в одном потоке
Заголовок раздела «Два заказа в одном потоке»Вот как приходят события обоих заказов и что обработчик помнит после каждого:
| Время | Событие | Ключ | ord-1042 ждёт? | ord-1337 ждёт? |
|---|---|---|---|---|
| 10:04 | replacement_proposed | ord-1042 | Да | Нет |
| 10:05 | replacement_proposed | ord-1337 | Да | Да |
| 10:06 | replacement_accepted | ord-1042 | Нет | Да |
| 10:08 | replacement_accepted | ord-1337 | Нет | Нет |
В 10:06 обработчик меняет только строку ord-1042, а ord-1337 остаётся в
списке. Событиям одного заказа не обязательно приходить подряд. Каждое событие
несёт свой ключ, и ключ приводит обработчик к нужной памяти, сколько бы событий
других заказов ни пришло между ними.
Как выбрать ключ
Заголовок раздела «Как выбрать ключ»В каждом событии есть несколько идентификаторов, и каждый сгруппировал бы события по-своему. Идентификатор события обозначает одну запись, идентификатор заказа связывает записи одного заказа, а идентификатор магазина — все заказы одного магазина. Вот что каждый вариант сделал бы со списком ожидающих:
| Ключ | Какие события делят память | Результат |
|---|---|---|
event_id | Никакие: у каждого события своя строка | Ваше согласие не доберётся до «да», которое оставило предложение, и ваш заказ останется в списке |
store_id | Все события всех заказов в store-418 | Ваше согласие поменяет ответ магазина на «нет» и уберёт из списка заодно и ord-1337 |
order_id | Все события одного заказа | Ответ каждого заказа меняется только от его собственных событий |
Правильный ключ зависит от вопроса. «Ждёт ли этот заказ ответа?» — вопрос об
одном заказе, поэтому ключ — order_id. «Сколько заказов в этом магазине ждут
ответа?» — вопрос о магазине, поэтому его ключ — store_id, а память — счётчик.
Счётчик растёт, когда заказ попадает в список, и уменьшается, когда заказ из
него уходит. Одна система может отвечать на оба вопроса, у каждого свой ключ.
Ключ должен ещё и различать разные объекты. В нашем сервисе каждый номер заказа
уникален во всех магазинах. Если бы каждый магазин нумеровал заказы сам, у двух
магазинов мог бы оказаться заказ ord-1042, и их события попали бы в одну
строку. Тогда ключ пришлось бы составить из двух полей: store_id и order_id
вместе.
Чего ключ не решает
Заголовок раздела «Чего ключ не решает»Ключ определяет, какую память меняет событие. Порядок, в котором приходят
события, он не определяет.
Записи могут приходить не в том порядке,
в котором происходили описанные действия.
Если запись о предложении, сделанном в 10:04, задержится, запись о вашем
согласии в 10:06 может дойти до обработчика первой. Оба события всё равно
попадут в строку ord-1042. Согласие поменяет её на «нет», хотя там и так было
«нет». Затем опоздавшее предложение поменяет её на «да», и ваш заказ попадёт в
список уже после вашего ответа. Сотрудники магазина увидят, что сборщик ждёт
ответа, который вы уже дали.
В событиях записано время, когда всё произошло: 10:04 у предложения, 10:06 у согласия. Один из подходов — задерживать поступившие события и обрабатывать их в порядке возникновения, по записанному времени, а не по порядку получения. Но как долго обработчику их задерживать, если более раннее событие может быть ещё в пути?
Что запомнить
Заголовок раздела «Что запомнить»- Ключ — значение, по которому определяется, какие события относятся друг к другу и какую память меняет событие.
- Обработчик хранит отдельное состояние для каждого ключа, поэтому события многих заказов могут приходить вперемешку, не смешивая их память.
- Ключ следует из вопроса: по заказу, по магазину или по сочетанию полей, если одно поле не различает объекты.
- Ключ собирает события одного заказа вместе, но не выстраивает их в том порядке, в котором всё произошло.
Источники
Заголовок раздела «Источники»© 2026 Grig Alex · CC BY-SA 4.0
