Re[24]: Помогите правильно спроектировать микросервисное при
От: · Великобритания  
Дата: 11.02.26 11:07
Оценка: -1
Здравствуйте, gandjustas, Вы писали:

G>>>·>А откуда возникла такая задача "транзакционно обновить состояние корзины с резервов на складе"? По-моему, ты путаешь цель и средство.

G>>>Конкретно это из озона.
G>·>Это ты описываешь конкретное решение некой задачи. Сама задача-то в чём?
G>Не в курсе чем озон занимается?
Судя по твоим заявлениям: транзакционно обновляют состояние корзины с резервов на складе.

G>В основном товары со склада продает. Вот и надо продать не больше чем есть.

Для этого нет необходимости транзакционно обновлять состояние корзины с резервов на складе.

G>·>Для решения этой задачи нет необходимости обновлять корзину и склад в одной транзакции. Достаточно их обновить последовательно — вначале склад, потом корзину, в разных транзакциях.

G>Тогда у нас нет никакого выигрыша от того, что это разные транзакции в разных базах. В сумме это будет работать медленнее чем в одной.
Зато будет работать хотя бы.

G>Более того, возможен сценарий когда первая выполнилась, а вторая отвалилась, просто по таймауту. Тогда товар на складе забронирован, а статус корзины не поменялся. Нужно писать код для отката.

Это эквивалентно ситуации: клиент наполнил корзину и ушел плюнув, потому что левая пятка зачесалась. Код отката брони на складе ты будешь писать в любом случае.

G>Идемпотентной такую операцию уже не сделаешь и автоповтор со стороны клиента тоже.

Почему? Присваиваешь заявке на бронирование uuid и долбишь склад до посинения.

G>Короче сплошные недостатки и ноль преимуществ

Угу-угу.

G>·>Да не важно. Подставь любую другую очень "нужную" фичу, которая есть в svn но нет в git.

G>Так в том и дело, что у СВНа не было преимуществ перед Гит. У Гита был один недостаток — сложность использования, до сих пор два из трех программистов не умеют в гит.
Многие носились с номерами ревизий. Коммит 23414 выглядит на порядок лучше, чем 21f0c36f3cef976789d9c65c16198a7c14f7b272. А ещё отдельные чекауты подветок, локи, явные переименования, и т.п. Многие заявляют "нам нужно".

G>·>Суть моей аналогии, что твоё "нам нужно [xxx]" ты исходишь не из постановки бизнес-задачи, а из конкретного решения в виде монолитной архитектуры и гигантской всемогущей субд в виде одного процесса.

G>СУБД умеет ровно то, что у умеет — Атомарные изолированные транзакции, сохраняющие согласованность данных и гарантирующие надежность.
G>Любые реализации таких гарантий вручную дают менее надежный и менее производительный код, который не имеет преимуществ. Никакая теория и философия еще ни разу никому помогли сделать лучше транзакций в БД там, где транзакции решают проблему.
Теория простая: CAP-теорема.

G>>>·>Да не важно. СУБД это просто такой готовый процесс.

G>>>Угу, процесс который умеет это делать, в отличие от твоего процесса, который по умолчанию не умеет и надо прям поприседать чтобы умел.
G>·>Речь о другом — процесс не один. Можно иметь две субд, в каждой свои локальные транзакции.
G>И что это даст? Неатомарное, несогласованное, неизолированное изменение данных? В чем смысл?
Чтоб работало.

G>>>>>Как это связано? Если они все приходят в итоге на одни сервер? бд например, то задача решаема.

G>Один кластер — один мастер для записи и 15 реплик для чтения. Все транзакции меняющие данные ходят на один сервер.
Ок, почитал. Цитатки:
"continue to migrate, shardable, write-heavy workloads to sharded systems as Azure Cosmos DB".
"no longer allow adding new tables to the current PostgreSQL deployment".
"we're not ruling out sharding PostreSQL in the future"
"move complex join logic to the application layer"
"caching layer"

Хорошо подытожено: "It's Scaling PostgresNoSQL, not Scaling PostgreSQL, as this is full of NoSQL solutions: eventually consistent, cascading read replicas, sharding for write-heavy workloads, offload to purpose-built CosmosDB, lazy writes, cache limiting, no new create/alter table, schemaless, avoid complex multi-table joins..."
Но в главном-то ты прав: "все приходят в итоге на одни сервер".

G>·>Он может просто лечь, и без нагрузки.

G>Сам? Просто так? Реплик нет? Админов нет? Какой смысл это рассматривать как реалистичный сценарий?
G>Ну даже допустим что у вас сервер может лечь, вы его надежность оцениваете как число X в интервале (0;1) — оба конца не включены.
G>Если рассмотреть сценарий выше с покупкой товаров в ОЗОН и разнесением транзакций на два сервера, то общая надежность будет равна X^2, что строго меньше Х. То есть надежность двух серверов ниже.
Ты, видимо, сервер и сервис путаешь. Один сервис может работать как несколько инстансов, нет требования монолитности. И внезапно надёжность сервиса выше, если он работает на нескольких серверах.

G>>>Этой проблемы просто не будет, мы никогда не продадим больше чем есть на складе если резервирование сделаем транзакционно.

G>·>Я не понимаю как "пользователь плюёт" соотносится с "не продадим больше".
G>Это относится к недостаткам решения. Так как "пользователь плюет" это потеря денег. Потому что дальше пользователь идет на другой маркетплейс и к тебе возможно уже не возвращается никогда
"резервирование сделаем транзакционно" не решает проблему "пользователь плюет".

G>>>Ту самую, которую ты непонятно как хочешь решить.

G>·>Это какую?
G>Чтобы пользователь придя за своим товаром получил его.
Для этого не требуется обновлять корзину и склад в одной транзакции.

G>·>Почему? Напомню контекст: МСА.

G>Выше все описал. Без МСА получается лучше.
Не получается. Проще — да, но по многим другим параметрам — хуже.

G>>>Ага, и в этом случае статус может стать "зарезервирован", а остаток на складе не уменьшится, и ты будешь разговаривать с недовольными покупателями.

G>·>Пока сервис склада не вернёт ответ "резервация прошла успешно" мы не обновляем статус заказа в сервисе заказов.
G>Тогда в этом нет никакого смысла, потому что недостатки есть, а преимуществ нет.
Можно продолжать работать с корзиной, например, позволять добавлять товары ещё, обновлять адрес доставки, применять скидки и купоны и т.п.

G>Напомню что ОпенАИ не использует распределенные транзакции и живет на одном кластере, то есть все записи приходят в один мастер.

Угу-угу.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 11.02.2026 12:54 · . Предыдущая версия . Еще …
Отредактировано 11.02.2026 12:49 · . Предыдущая версия .
Отредактировано 11.02.2026 12:05 · . Предыдущая версия .
Re[25]: Помогите правильно спроектировать микросервисное при
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 12.02.26 00:14
Оценка:
Здравствуйте, ·, Вы писали:

·>Здравствуйте, gandjustas, Вы писали:


G>>>>·>А откуда возникла такая задача "транзакционно обновить состояние корзины с резервов на складе"? По-моему, ты путаешь цель и средство.

G>>>>Конкретно это из озона.
G>>·>Это ты описываешь конкретное решение некой задачи. Сама задача-то в чём?
G>>Не в курсе чем озон занимается?
·>Судя по твоим заявлениям: транзакционно обновляют состояние корзины с резервов на складе.
Это часть того, что они делают.

G>>В основном товары со склада продает. Вот и надо продать не больше чем есть.

·>Для этого нет необходимости транзакционно обновлять состояние корзины с резервов на складе.
Ты не понимаешь суть задачи

две сущности Order (id, status, lines(product_id, q)) и Stock (product_id, reserverd, limit). При смене статуса в Order мы меняем состояние резервов в Stock.
При подтверждении заказа — увеличиваем reserved в stock для каждого line в order. А при отмене уменьшаем.

Если мы это не делаем в одной транзакции, то как ты потом уменьшишь, если статус не удалось сменить?
Сохранишь копию order в то же базе в той же тразакции, да?

Ты наверное начнешь разговор что так делать не надо. Но озону надо. Потому что разница limit-reserverd отображается в интерфейсе приложения как "сколько осталось" и это отображается прямо в результатах поиска, то есть надо быстро получать это число для любого количества товаров.


G>>·>Для решения этой задачи нет необходимости обновлять корзину и склад в одной транзакции. Достаточно их обновить последовательно — вначале склад, потом корзину, в разных транзакциях.

G>>Тогда у нас нет никакого выигрыша от того, что это разные транзакции в разных базах. В сумме это будет работать медленнее чем в одной.
·>Зато будет работать хотя бы.
Не лучше, чем в одной базе. Прям строго математически не лучше.

G>>Более того, возможен сценарий когда первая выполнилась, а вторая отвалилась, просто по таймауту. Тогда товар на складе забронирован, а статус корзины не поменялся. Нужно писать код для отката.

·>Это эквивалентно ситуации: клиент наполнил корзину и ушел плюнув, потому что левая пятка зачесалась. Код отката брони на складе ты будешь писать в любом случае.
Не эквивалентно и не придется такое писать. Это твои фантазии


G>>Идемпотентной такую операцию уже не сделаешь и автоповтор со стороны клиента тоже.

·>Почему? Присваиваешь заявке на бронирование uuid и долбишь склад до посинения.
То ест мало того, что для обработки отмены ты вынужден будешь сохранить почти весь order в в той же транзакции, что и обновление остатков, так еще и дополнишь его полем ключа идемпотентности


G>>·>Да не важно. Подставь любую другую очень "нужную" фичу, которая есть в svn но нет в git.

G>>Так в том и дело, что у СВНа не было преимуществ перед Гит. У Гита был один недостаток — сложность использования, до сих пор два из трех программистов не умеют в гит.
·>Многие носились с номерами ревизий. Коммит 23414 выглядит на порядок лучше, чем 21f0c36f3cef976789d9c65c16198a7c14f7b272. А ещё отдельные чекауты подветок, локи, явные переименования, и т.п. Многие заявляют "нам нужно".
Я уверен что и сейчас svn найдет сторонников, потому что он все еще в разы проще git. Аргументы будут самые неразумные.


G>>·>Суть моей аналогии, что твоё "нам нужно [xxx]" ты исходишь не из постановки бизнес-задачи, а из конкретного решения в виде монолитной архитектуры и гигантской всемогущей субд в виде одного процесса.

G>>СУБД умеет ровно то, что у умеет — Атомарные изолированные транзакции, сохраняющие согласованность данных и гарантирующие надежность.
G>>Любые реализации таких гарантий вручную дают менее надежный и менее производительный код, который не имеет преимуществ. Никакая теория и философия еще ни разу никому помогли сделать лучше транзакций в БД там, где транзакции решают проблему.
·>Теория простая: CAP-теорема.
Пока ты пишешь на один сервер тебя cap вообще не интересует.

G>>>>·>Да не важно. СУБД это просто такой готовый процесс.

G>>>>Угу, процесс который умеет это делать, в отличие от твоего процесса, который по умолчанию не умеет и надо прям поприседать чтобы умел.
G>>·>Речь о другом — процесс не один. Можно иметь две субд, в каждой свои локальные транзакции.
G>>И что это даст? Неатомарное, несогласованное, неизолированное изменение данных? В чем смысл?
·>Чтоб работало.
Только оно не рабоает.

G>>>>>>Как это связано? Если они все приходят в итоге на одни сервер? бд например, то задача решаема.

G>>Один кластер — один мастер для записи и 15 реплик для чтения. Все транзакции меняющие данные ходят на один сервер.
·>Ок, почитал. Цитатки:
·>"continue to migrate, shardable, write-heavy workloads to sharded systems as Azure Cosmos DB".
·>"no longer allow adding new tables to the current PostgreSQL deployment".
·>"we're not ruling out sharding PostreSQL in the future"
·>"move complex join logic to the application layer"
·>"caching layer"
И? они все еще на Postgres с одним мастером, и все работает
Как-то работает, и CAP не мешает.

А все что они рассматривают никак не противоречит сказанному выше.

·>Хорошо подытожено: "It's Scaling PostgresNoSQL, not Scaling PostgreSQL, as this is full of NoSQL solutions: eventually consistent, cascading read replicas, sharding for write-heavy workloads, offload to purpose-built CosmosDB, lazy writes, cache limiting, no new create/alter table, schemaless, avoid complex multi-table joins..."

·>Но в главном-то ты прав: "все приходят в итоге на одни сервер".
Там где не нужна целостность всегда можно вынести. Но мы же рассматриваем задачу где она нужна.

G>>·>Он может просто лечь, и без нагрузки.

G>>Сам? Просто так? Реплик нет? Админов нет? Какой смысл это рассматривать как реалистичный сценарий?
G>>Ну даже допустим что у вас сервер может лечь, вы его надежность оцениваете как число X в интервале (0;1) — оба конца не включены.
G>>Если рассмотреть сценарий выше с покупкой товаров в ОЗОН и разнесением транзакций на два сервера, то общая надежность будет равна X^2, что строго меньше Х. То есть надежность двух серверов ниже.
·>Ты, видимо, сервер и сервис путаешь. Один сервис может работать как несколько инстансов, нет требования монолитности. И внезапно надёжность сервиса выше, если он работает на нескольких серверах.
Это ты путаешь stateless и stateful. В stateless сервисе ты можешь поднять несколько инстансов на разных серверах и если один перестанет отвечать другие смогут ответить.
Но когда мы рассматриваем stateful (а база данных это statful сервис), то при нескольких экземплярах мы наталкиваемся на CAP ограничения — мы можем или терять консистентность (что запрещено по условиям задачи) или доступность. Причем потерю доступности можно посчитать. Доступность системы из двух stateful узлов равна произведению доступности серверов, которая меньше доступности одного сервера. Априори считаем все серверы имеют одинаковую доступность.
Поэтому чтобы не заниматься сложной схемой отказоустойчивости и распределенными транзакциями выбирают обычную master-slave репликацию, когда все записи приходят в одну ноду.

G>>>>Этой проблемы просто не будет, мы никогда не продадим больше чем есть на складе если резервирование сделаем транзакционно.

G>>·>Я не понимаю как "пользователь плюёт" соотносится с "не продадим больше".
G>>Это относится к недостаткам решения. Так как "пользователь плюет" это потеря денег. Потому что дальше пользователь идет на другой маркетплейс и к тебе возможно уже не возвращается никогда
·>"резервирование сделаем транзакционно" не решает проблему "пользователь плюет".
Конечно решает, потому что пользователь после резервации на складе точно получит свой заказ.

G>>>>Ту самую, которую ты непонятно как хочешь решить.

G>>·>Это какую?
G>>Чтобы пользователь придя за своим товаром получил его.
·>Для этого не требуется обновлять корзину и склад в одной транзакции.
Я уже выше описал почему требуется. Не повторяй эту глупость уже

G>>·>Почему? Напомню контекст: МСА.

G>>Выше все описал. Без МСА получается лучше.
·>Не получается. Проще — да, но по многим другим параметрам — хуже.
Ты делаешь утвреждения, но даже не пытаешься из доказывать. Думаю просто потому что у тебя нет объективных аргументов.

G>>>>Ага, и в этом случае статус может стать "зарезервирован", а остаток на складе не уменьшится, и ты будешь разговаривать с недовольными покупателями.

G>>·>Пока сервис склада не вернёт ответ "резервация прошла успешно" мы не обновляем статус заказа в сервисе заказов.
G>>Тогда в этом нет никакого смысла, потому что недостатки есть, а преимуществ нет.
·>Можно продолжать работать с корзиной, например, позволять добавлять товары ещё, обновлять адрес доставки, применять скидки и купоны и т.п.
Лол, а зачем?
Re[26]: Помогите правильно спроектировать микросервисное при
От: · Великобритания  
Дата: 12.02.26 12:48
Оценка: -1
Здравствуйте, gandjustas, Вы писали:


G>·>Судя по твоим заявлениям: транзакционно обновляют состояние корзины с резервов на складе.

G>Это часть того, что они делают.
Ещё раз. Это не задача, а конкретное решение. С таким же смыслом можно заявлять, что они, например, логи пишут и байты по проводам передают.

G>>>В основном товары со склада продает. Вот и надо продать не больше чем есть.

G>·>Для этого нет необходимости транзакционно обновлять состояние корзины с резервов на складе.
G>Ты не понимаешь суть задачи
G>две сущности Order (id, status, lines(product_id, q)) и Stock (product_id, reserverd, limit). При смене статуса в Order мы меняем состояние резервов в Stock.
G>При подтверждении заказа — увеличиваем reserved в stock для каждого line в order. А при отмене уменьшаем.
G>Если мы это не делаем в одной транзакции, то как ты потом уменьшишь, если статус не удалось сменить?
Так же, как если клиент набрал кучу товаров, они зарезервировались, но не смог оплатить. Резерв нужно откатывать.

G>Сохранишь копию order в то же базе в той же тразакции, да?

G>Ты наверное начнешь разговор что так делать не надо. Но озону надо. Потому что разница limit-reserverd отображается в интерфейсе приложения как "сколько осталось" и это отображается прямо в результатах поиска, то есть надо быстро получать это число для любого количества товаров.
Сохранять копии? Конечно, не надо.

G>·>Зато будет работать хотя бы.

G>Не лучше, чем в одной базе. Прям строго математически не лучше.
В крупных магазинах такое вообще работать не будет.

G>·>Это эквивалентно ситуации: клиент наполнил корзину и ушел плюнув, потому что левая пятка зачесалась. Код отката брони на складе ты будешь писать в любом случае.

G>Не эквивалентно и не придется такое писать. Это твои фантазии
Как не придётся? Что делать, когда корзина создана, товары зарезервированы, а пользователь закрыл браузер и ушел с концами?

G>>>Идемпотентной такую операцию уже не сделаешь и автоповтор со стороны клиента тоже.

G>·>Почему? Присваиваешь заявке на бронирование uuid и долбишь склад до посинения.
G>То ест мало того, что для обработки отмены ты вынужден будешь сохранить почти весь order в в той же транзакции, что и обновление остатков, так еще и дополнишь его полем ключа идемпотентности
Зачем его сохранять? Заявка — это сообщение. Ты вообще что-ли не понимаешь как делают идемпотентность?

G>>>Так в том и дело, что у СВНа не было преимуществ перед Гит. У Гита был один недостаток — сложность использования, до сих пор два из трех программистов не умеют в гит.

G>·>Многие носились с номерами ревизий. Коммит 23414 выглядит на порядок лучше, чем 21f0c36f3cef976789d9c65c16198a7c14f7b272. А ещё отдельные чекауты подветок, локи, явные переименования, и т.п. Многие заявляют "нам нужно".
G>Я уверен что и сейчас svn найдет сторонников, потому что он все еще в разы проще git. Аргументы будут самые неразумные.
А совсем проще всего — вообще не использовать системы контроля версий. И что? Как и всегда в жизни: у любой, даже самой сложной задачи есть очевидное, простое, понятное, неправильное решение.

G>>>СУБД умеет ровно то, что у умеет — Атомарные изолированные транзакции, сохраняющие согласованность данных и гарантирующие надежность.

G>>>Любые реализации таких гарантий вручную дают менее надежный и менее производительный код, который не имеет преимуществ. Никакая теория и философия еще ни разу никому помогли сделать лучше транзакций в БД там, где транзакции решают проблему.
G>·>Теория простая: CAP-теорема.
G>Пока ты пишешь на один сервер тебя cap вообще не интересует.
Это плохого архитектора вообще cap не интересует. Вот правда теореме пофиг, интересует она кого-то или нет, она работает всегда.

G>>>·>Речь о другом — процесс не один. Можно иметь две субд, в каждой свои локальные транзакции.

G>>>И что это даст? Неатомарное, несогласованное, неизолированное изменение данных? В чем смысл?
G>·>Чтоб работало.
G>Только оно не рабоает.
Ну ты хочешь чтобы я тут описывал как строятся МСА системы? Мне очень лень, но можешь почитать, например, тут: https://medium.com/@CodeWithTech/the-saga-design-pattern-coordinating-long-running-transactions-in-distributed-systems-edbc9b9a9116

G>И? они все еще на Postgres с одним мастером, и все работает

В смысле? У них postgres в режиме deprecated. Т.к. дорого всё распилить, т.к. надо переделывать всё. Сказано же: "no longer allow adding new tables". Кто-то гениальный запилил им монолит, вот теперь страдают.

G>·>Но в главном-то ты прав: "все приходят в итоге на одни сервер".

G>Там где не нужна целостность всегда можно вынести. Но мы же рассматриваем задачу где она нужна.
Ну у них не получилось. Все эти многослойные кеши и реплики — это уже отказ от целостности.

G>·>Ты, видимо, сервер и сервис путаешь. Один сервис может работать как несколько инстансов, нет требования монолитности. И внезапно надёжность сервиса выше, если он работает на нескольких серверах.

G>Это ты путаешь stateless и stateful. В stateless сервисе ты можешь поднять несколько инстансов на разных серверах и если один перестанет отвечать другие смогут ответить.
G>Но когда мы рассматриваем stateful (а база данных это statful сервис), то при нескольких экземплярах мы наталкиваемся на CAP ограничения — мы можем или терять консистентность (что запрещено по условиям задачи) или доступность.
В задаче корзина-склад — достаточно eventual consistency.

G>Причем потерю доступности можно посчитать. Доступность системы из двух stateful узлов равна произведению доступности серверов, которая меньше доступности одного сервера. Априори считаем все серверы имеют одинаковую доступность.

G>Поэтому чтобы не заниматься сложной схемой отказоустойчивости и распределенными транзакциями выбирают обычную master-slave репликацию, когда все записи приходят в одну ноду.
Такое требуется в очень редких задачах. И там обычно используют какой-нибудь event sourcing, а не acid субд.

G>>>Это относится к недостаткам решения. Так как "пользователь плюет" это потеря денег. Потому что дальше пользователь идет на другой маркетплейс и к тебе возможно уже не возвращается никогда

G>·>"резервирование сделаем транзакционно" не решает проблему "пользователь плюет".
G>Конечно решает, потому что пользователь после резервации на складе точно получит свой заказ.
"пользователь плюёт" — это означает что он не ничего хочет оплачивать и уж тем более получать свой заказ. Но зарезервированные товары хочет получить совершенно другой пользователь.

G>>>Чтобы пользователь придя за своим товаром получил его.

G>·>Для этого не требуется обновлять корзину и склад в одной транзакции.
G>Я уже выше описал почему требуется. Не повторяй эту глупость уже
Ещё раз. Если выполнять вначале транзакцию резервации, потом другую транзакцию для смены статуса заказа — то мы гарантируем, что товар будет в наличии после оплаты заказа.
Существует только проблема того, что товар зарезервирован, но не оплачен. Нужен ещё процесс отмены резерва. И твоя транзакция ничем не помогает, никакую проблему она не решает.

G>·>Можно продолжать работать с корзиной, например, позволять добавлять товары ещё, обновлять адрес доставки, применять скидки и купоны и т.п.

G>Лол, а зачем?
Ага. Раз такого в Озоне нет, то это никому не нужно.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 12.02.2026 12:55 · . Предыдущая версия .
Re[27]: Помогите правильно спроектировать микросервисное при
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 12.02.26 22:39
Оценка:
Здравствуйте, ·, Вы писали:



G>>>>В основном товары со склада продает. Вот и надо продать не больше чем есть.

G>>·>Для этого нет необходимости транзакционно обновлять состояние корзины с резервов на складе.
G>>Ты не понимаешь суть задачи
G>>две сущности Order (id, status, lines(product_id, q)) и Stock (product_id, reserverd, limit). При смене статуса в Order мы меняем состояние резервов в Stock.
G>>При подтверждении заказа — увеличиваем reserved в stock для каждого line в order. А при отмене уменьшаем.
G>>Если мы это не делаем в одной транзакции, то как ты потом уменьшишь, если статус не удалось сменить?
·>Так же, как если клиент набрал кучу товаров, они зарезервировались, но не смог оплатить. Резерв нужно откатывать.
Нужно, это другой сценарий. Далеко не то же самое, что откат незавершенной резервации.


G>>Сохранишь копию order в то же базе в той же тразакции, да?

G>>Ты наверное начнешь разговор что так делать не надо. Но озону надо. Потому что разница limit-reserverd отображается в интерфейсе приложения как "сколько осталось" и это отображается прямо в результатах поиска, то есть надо быстро получать это число для любого количества товаров.
·>Сохранять копии? Конечно, не надо.
А что тогда делать для отката незавершенной резервации?

G>>·>Зато будет работать хотя бы.

G>>Не лучше, чем в одной базе. Прям строго математически не лучше.
·>В крупных магазинах такое вообще работать не будет.
Ты можешь как-то обосновать свои слова?

G>>·>Это эквивалентно ситуации: клиент наполнил корзину и ушел плюнув, потому что левая пятка зачесалась. Код отката брони на складе ты будешь писать в любом случае.

G>>Не эквивалентно и не придется такое писать. Это твои фантазии
·>Как не придётся? Что делать, когда корзина создана, товары зарезервированы, а пользователь закрыл браузер и ушел с концами?
Это другой сценарий, мы его сейчас не рассматриваем.
Не надо придумывать новые кейсы, придумай как сделать хорошо один. Потом будем другие рассматривать.
Пока у тебя ничего внятного не получилось.

G>>>>Идемпотентной такую операцию уже не сделаешь и автоповтор со стороны клиента тоже.

G>>·>Почему? Присваиваешь заявке на бронирование uuid и долбишь склад до посинения.
G>>То ест мало того, что для обработки отмены ты вынужден будешь сохранить почти весь order в в той же транзакции, что и обновление остатков, так еще и дополнишь его полем ключа идемпотентности
·>Зачем его сохранять? Заявка — это сообщение. Ты вообще что-ли не понимаешь как делают идемпотентность?
Я понимаю как делают идемпотентность. Я не понимаю как ты её хочешь сделать.
Можешь привести пример кода?


G>>>>Так в том и дело, что у СВНа не было преимуществ перед Гит. У Гита был один недостаток — сложность использования, до сих пор два из трех программистов не умеют в гит.

G>>·>Многие носились с номерами ревизий. Коммит 23414 выглядит на порядок лучше, чем 21f0c36f3cef976789d9c65c16198a7c14f7b272. А ещё отдельные чекауты подветок, локи, явные переименования, и т.п. Многие заявляют "нам нужно".
G>>Я уверен что и сейчас svn найдет сторонников, потому что он все еще в разы проще git. Аргументы будут самые неразумные.
·>А совсем проще всего — вообще не использовать системы контроля версий.
Альтернатива какая? передавать архивами и мерить вручную?



G>>>>·>Речь о другом — процесс не один. Можно иметь две субд, в каждой свои локальные транзакции.

G>>>>И что это даст? Неатомарное, несогласованное, неизолированное изменение данных? В чем смысл?
G>>·>Чтоб работало.
G>>Только оно не рабоает.
·>Ну ты хочешь чтобы я тут описывал как строятся МСА системы? Мне очень лень, но можешь почитать, например, тут: https://medium.com/@CodeWithTech/the-saga-design-pattern-coordinating-long-running-transactions-in-distributed-systems-edbc9b9a9116
Не надо ссылок, просто приведи пример кода как бы ты сделал резервацию заказа.


G>>И? они все еще на Postgres с одним мастером, и все работает

·>В смысле? У них postgres в режиме deprecated. Т.к. дорого всё распилить, т.к. надо переделывать всё. Сказано же: "no longer allow adding new tables". Кто-то гениальный запилил им монолит, вот теперь страдают.
Да уж, страдают....

G>>·>Но в главном-то ты прав: "все приходят в итоге на одни сервер".

G>>Там где не нужна целостность всегда можно вынести. Но мы же рассматриваем задачу где она нужна.
·>Ну у них не получилось. Все эти многослойные кеши и реплики — это уже отказ от целостности.
Если ты не пишешь данные, на основе результатов чтения из отстающей реплики, то нарушения целостности нет.


G>>·>Ты, видимо, сервер и сервис путаешь. Один сервис может работать как несколько инстансов, нет требования монолитности. И внезапно надёжность сервиса выше, если он работает на нескольких серверах.

G>>Это ты путаешь stateless и stateful. В stateless сервисе ты можешь поднять несколько инстансов на разных серверах и если один перестанет отвечать другие смогут ответить.
G>>Но когда мы рассматриваем stateful (а база данных это statful сервис), то при нескольких экземплярах мы наталкиваемся на CAP ограничения — мы можем или терять консистентность (что запрещено по условиям задачи) или доступность.
·>В задаче корзина-склад — достаточно eventual consistency.
Покажи пример кода


G>>Причем потерю доступности можно посчитать. Доступность системы из двух stateful узлов равна произведению доступности серверов, которая меньше доступности одного сервера. Априори считаем все серверы имеют одинаковую доступность.

G>>Поэтому чтобы не заниматься сложной схемой отказоустойчивости и распределенными транзакциями выбирают обычную master-slave репликацию, когда все записи приходят в одну ноду.
·>Такое требуется в очень редких задачах. И там обычно используют какой-нибудь event sourcing, а не acid субд.
Именно, а в более простых случаях вполне достаточно одного мастера. Вот у OpenAI простой случай. Я правда не представляю у кого он не простой.

G>>>>Чтобы пользователь придя за своим товаром получил его.

G>>·>Для этого не требуется обновлять корзину и склад в одной транзакции.
G>>Я уже выше описал почему требуется. Не повторяй эту глупость уже
·>Ещё раз. Если выполнять вначале транзакцию резервации, потом другую транзакцию для смены статуса заказа — то мы гарантируем, что товар будет в наличии после оплаты заказа.
·>Существует только проблема того, что товар зарезервирован, но не оплачен. Нужен ещё процесс отмены резерва. И твоя транзакция ничем не помогает, никакую проблему она не решает.
Да ты сначала сделай процесс резервирования надежным, потом про оплату поговорим.

G>>·>Можно продолжать работать с корзиной, например, позволять добавлять товары ещё, обновлять адрес доставки, применять скидки и купоны и т.п.

G>>Лол, а зачем?
·>Ага. Раз такого в Озоне нет, то это никому не нужно.
Так мы сейчас про конкретную задачу говорим, как в озоне.
Re[26]: Помогите правильно спроектировать микросервисное при
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.02.26 09:52
Оценка: 1 (1)
Здравствуйте, gandjustas, Вы писали:

G>Сохранишь копию order в то же базе в той же тразакции, да?

Техническое решение этой задачи для микросервисов уже обсуждалось. Да, оно в каком-то смысле "хуже", чем единая ACID-транзакция — у заказа появляется некое промежуточное состояние, которого не было в исходной реализации.
Но давайте прекратим делать вид, что этого решения нет, т.к. это состояние — эфемерное, и фактически время его жизни равно времени даунтайма сервиса склада с точки зрения сервиса корзинки.

G>Ты наверное начнешь разговор что так делать не надо. Но озону надо. Потому что разница limit-reserverd отображается в интерфейсе приложения как "сколько осталось" и это отображается прямо в результатах поиска, то есть надо быстро получать это число для любого количества товаров.

Микросервисы никак не мешают быстрому показу remaining — скорее наоборот, т.к. RDBMS склада вообще не обрабатывает никакой нагрузки, кроме вот этого вот get remaining и reserve stock / unreserve stock / top-up stock.
Кроме того, её можно шардить — т.к. сами товары друг на друга никак не завязаны, можно держать первые 10000 позиций в одном сервере, вторые 10000 — в другом, и так далее.

Не, я в курсе, что как только мы начнём джойнить эту информацию с какими-нибудь запросами по другой части домена (типа "робот-пылесос в пределах 20000 рублей с доставкой до послезавтра и средним баллом по отзывам не ниже 4.9"), то МСА со страшной силой сольёт монолиту. Вот только если мы всё же уперлись в потолок монолита, то возможность получить 2х перформанс ценой распиливания его на 8х частей может оказаться единственным выбором.

G>Не лучше, чем в одной базе. Прям строго математически не лучше.

Да, но с учётом нюансов, указанных выше. Пока мы не упёрлись в пределы монолита и при отсутствии административных причин, монолит будет строго математически лучше.

G>>>Более того, возможен сценарий когда первая выполнилась, а вторая отвалилась, просто по таймауту. Тогда товар на складе забронирован, а статус корзины не поменялся. Нужно писать код для отката.

G>·>Это эквивалентно ситуации: клиент наполнил корзину и ушел плюнув, потому что левая пятка зачесалась. Код отката брони на складе ты будешь писать в любом случае.
G>Не эквивалентно и не придется такое писать. Это твои фантазии
Нет, откат писать не надо. Надо писать "накат". И это можно сделать один раз в инфраструктуре worflow-engine. Типа вот мы поднялись после сбоя и видим, что корзинка №42342342 была отправлена на резервирование, а результата резервирования нет. Значит, либо мы в прошлый раз не достучались до сервера (получили сonnection timeout), либо достучались да он упал до начала резервирования (отдал нам 5хх), либо упал после окончания резервирования (и отдал нам 5хх или connection reset by peer), либо всё нам отдал, да мы расплескали по пути и упали до коммита в локальную БД.
Во всех случаях мы тупо идём и повторяем резерв. При необходимости — сколько угодно раз, пока нам таки не удастся записать к себе "успех" либо "фейл". На практике, длинные даунтаймы тут бывают не чаще, чем даунтаймы у монолита. Только у монолита лежит вообще всё, а у МСА можно хотя бы в корзинку что-то складывать да отзывы читать.

G>То ест мало того, что для обработки отмены ты вынужден будешь сохранить почти весь order в в той же транзакции, что и обновление остатков, так еще и дополнишь его полем ключа идемпотентности

Да, объекты, пересекающие границы сервисов, в МСА должны храниться на обеих сторонах. Это не обязательно одни и те же объекты, но у них должна быть общая проекция. В данном случае сторону склада не интересуют никакие подробности про способы доставки товара, розничные цены, или там демографию покупателя, но вот список артикулов и количеств, снабжённый уникальным ID, ей необходим. Ровно для того, чтобы когда к нему в следующий раз стукнутся с просьбой зарезервировать корзинку №42342342 он мог не делать повторный резерв, а сразу отдать 200 ok.

G>·>Теория простая: CAP-теорема.

G>Пока ты пишешь на один сервер тебя cap вообще не интересует.
Совершенно верно.

G>Только оно не рабоает.

Это слишком сильное утверждение в рамках данной дискуссии

G>Но когда мы рассматриваем stateful (а база данных это statful сервис), то при нескольких экземплярах мы наталкиваемся на CAP ограничения — мы можем или терять консистентность (что запрещено по условиям задачи) или доступность. Причем потерю доступности можно посчитать. Доступность системы из двух stateful узлов равна произведению доступности серверов, которая меньше доступности одного сервера. Априори считаем все серверы имеют одинаковую доступность.

Зато доступность строго согласованной системы из трёх stateful узлов уже выше, одного узла — при условии, что надёжность связей между ними выше определённого предела
CAP-теорема как раз и намекает на то, что если каналы не 100% надёжны (P), то получить 100% доступность невозможно даже для 100% надёжных узлов. Но этот тривиальный факт никак не помогает инженерному дизайну — потому что как вы будете поднимать доступность вашего единственного узла за пределы его нормативных способностей?
Вот картинка для доступности строго согласованного кластера из трёх узлов с доступностью Anode, связанных каналами с доступностью Alink:

Белый контур — это зона, где эта доступность выше доступности каждого из компонентов.

G>·>"резервирование сделаем транзакционно" не решает проблему "пользователь плюет".

G>Конечно решает, потому что пользователь после резервации на складе точно получит свой заказ.
В нашем случае пользователь после резервации на складе тоже точно получит свой заказ. Вся разница — в том, что если будет сбой системы во время заказа, то пользователь монолита до окончания сбоя будет получать 502, а пользователь МСА имеет шанс в это время увидеть спиннер "заказ резервируется....".
В любом случае результат резервирования станет известен только по окончанию сбоя — и точно так же, как в монолитном случае можно получить как подтверждение успеха, так и "извините, но другой покупатель успел зарезервировать товар до вас".

G>·>Для этого не требуется обновлять корзину и склад в одной транзакции.

G>Я уже выше описал почему требуется. Не повторяй эту глупость уже
При всём уважении — это не глупость, а вполне себе математическая реальность. Я выше написал, как именно это работает.
G>Лол, а зачем?
Затем, что в реальном приложении — сотни бизнес-сценариев. Остановка на техобслуживание одного из сервисов задержит только те сценарии, которые проходят через него. И если этот сервис достаточно простой и маленький, то его рестарт не будет занимать десятки минут. Таким образом, perceived availability может оказаться значительно выше, чем у монолита. Ну вот, абстрактно, мы на пять минут отключили банковское ядро, которое собственно проводит платежи. Если всё сделано по уму, то пользователи это увидят только как "странно, я вроде по СБП деньги отправил, а у получателя телефон что-то не вибрирует". Если чуть хуже — то как "переводы пока недоступны, приходите позже". Если ещё хуже — то как "Непредвиденная ошибка. Перезапустите приложение или зайдите позже". И в любом из этих случаев люди, которые смотрят какую-нибудь там аналитику расходов, или остатки по вкладам, или условия кредитов/страховок/етк не заметят вообще ничего. С их точки зрения никакого сбоя не было. А если нам нужно сделать то же самое в монолите, то опускать нужно примерно всё, и всё будет лежать сразу у всех пользователей.

Не факт, что это прямо плохо. Не факт, что это прямо настолько плохо, что стоит многократного роста вычислительной и когнитивной нагрузки, сопровождающего переход от монолита к (микро)-сервисам. Но для полноты картины имеет смысл рассматривать и такие сценарии, не пытаясь искусственно обесценить такие требования или там сочинять недостатки модели, которых в реальности нет.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[27]: Помогите правильно спроектировать микросервисное при
От: · Великобритания  
Дата: 13.02.26 10:12
Оценка:
Здравствуйте, Sinclair, Вы писали:


G>>Не лучше, чем в одной базе. Прям строго математически не лучше.

S>Да, но с учётом нюансов, указанных выше. Пока мы не упёрлись в пределы монолита и при отсутствии административных причин, монолит будет строго математически лучше.
Стоит ещё уточнить, что "математически лучше" — это плохой критерий для выбора решения. Нужно ещё не забывать о физике. Было бы математически лучше, если б я взмахнул руками и полетел, но физику с гравитацией это не устраивает, разрешает только строго вниз летать.
Так и тут, математически лучше чтобы всё работало в одной центральной точке. Но пользователи, склад, корзина, товары, платежи, курьеры — это всё разные физически разделённые сущности.

ЗЫЖ Спасибо за хороший ответ. А то я уже устал ему отвечать
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 13.02.2026 10:24 · . Предыдущая версия .
Re[28]: Помогите правильно спроектировать микросервисное при
От: · Великобритания  
Дата: 13.02.26 12:03
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>>>Если мы это не делаем в одной транзакции, то как ты потом уменьшишь, если статус не удалось сменить?

G>·>Так же, как если клиент набрал кучу товаров, они зарезервировались, но не смог оплатить. Резерв нужно откатывать.
G>Нужно, это другой сценарий. Далеко не то же самое,
В каком месте это не то же самое? Опять же. Ты рассматривай не одно конкретное решение одного конкретного сценария "транзакционно обновлять состояние", а изначальную бизнес-задачу: "продавать товары со склада клиентам". И выясняется что сценариев несколько, и они должны работать все.

G>что откат незавершенной резервации.

Резервация — это локальная атомарная транзакция Склада. Она либо завершена (все позиции доступны на складе и всё сработало), либо нет (чего-то не хватает, чего-то упало, етс).
Неатомарным является согласование _статуса_ Корзины со _статусом_ Резервации. И таковым оно является не потому что мы не затолкали всё на один центральный сервер, а потому что это разные физически независимые бизнес-сущности.

G>·>Сохранять копии? Конечно, не надо.

G>А что тогда делать для отката незавершенной резервации?
Для отката незавершенной резервации — ровно то же, что и для "если клиент набрал кучу товаров, они зарезервировались, но не смог оплатить."

G>·>В крупных магазинах такое вообще работать не будет.

G>Ты можешь как-то обосновать свои слова?
Могу, но лень. Можешь поискать как устроены всякие Амазоны с Ебеями. Или вообще погляди как какими-нибудь акциями какие-нибудь биржи торгуют.

G>·>Как не придётся? Что делать, когда корзина создана, товары зарезервированы, а пользователь закрыл браузер и ушел с концами?

G>Это другой сценарий, мы его сейчас не рассматриваем.
Этот сценарий является ответом на твой вопрос: "А что тогда делать для отката незавершенной резервации?".

G>Не надо придумывать новые кейсы, придумай как сделать хорошо один. Потом будем другие рассматривать.

G>Пока у тебя ничего внятного не получилось.
Мде. См. ответ от Sinclair.

G>·>Зачем его сохранять? Заявка — это сообщение. Ты вообще что-ли не понимаешь как делают идемпотентность?

G>Я понимаю как делают идемпотентность. Я не понимаю как ты её хочешь сделать.
G>Можешь привести пример кода?
Пример кода чего?

G>>>·>Многие носились с номерами ревизий. Коммит 23414 выглядит на порядок лучше, чем 21f0c36f3cef976789d9c65c16198a7c14f7b272. А ещё отдельные чекауты подветок, локи, явные переименования, и т.п. Многие заявляют "нам нужно".

G>>>Я уверен что и сейчас svn найдет сторонников, потому что он все еще в разы проще git. Аргументы будут самые неразумные.
G>·>А совсем проще всего — вообще не использовать системы контроля версий.
G>Альтернатива какая? передавать архивами и мерить вручную?
Типа того. Ведь это — самое простое. А мержить — совсем слишком сложно, ведь в разы проще договориться, кто какой файл когда меняет.

G>>>·>Чтоб работало.

G>>>Только оно не рабоает.
G>·>Ну ты хочешь чтобы я тут описывал как строятся МСА системы? Мне очень лень, но можешь почитать, например, тут: https://medium.com/@CodeWithTech/the-saga-design-pattern-coordinating-long-running-transactions-in-distributed-systems-edbc9b9a9116
G>Не надо ссылок, просто приведи пример кода как бы ты сделал резервацию заказа.
Ну вот там по ссылке как раз примеры кода.

G>>>И? они все еще на Postgres с одним мастером, и все работает

G>·>В смысле? У них postgres в режиме deprecated. Т.к. дорого всё распилить, т.к. надо переделывать всё. Сказано же: "no longer allow adding new tables". Кто-то гениальный запилил им монолит, вот теперь страдают.
G>Да уж, страдают....
Ну да, ну да. На самом деле они наслаждаются SEV-ами и outages. Ты статью-то читал? https://openai.com/index/scaling-postgresql/

G>>>Там где не нужна целостность всегда можно вынести. Но мы же рассматриваем задачу где она нужна.

G>·>Ну у них не получилось. Все эти многослойные кеши и реплики — это уже отказ от целостности.
G>Если ты не пишешь данные, на основе результатов чтения из отстающей реплики, то нарушения целостности нет.
Я, честно говоря, не знаю как у них что там устроено. Не буду сочинять.

G>·>В задаче корзина-склад — достаточно eventual consistency.

G>Покажи пример кода
Какого именно кода? Там по ссылке выше есть какие-то примеры.

G>·>Такое требуется в очень редких задачах. И там обычно используют какой-нибудь event sourcing, а не acid субд.

G>Именно, а в более простых случаях вполне достаточно одного мастера. Вот у OpenAI простой случай. Я правда не представляю у кого он не простой.
Если бы им было достаточно, они бы не начали переезжать на Cosmos DB.

G>Да ты сначала сделай процесс резервирования надежным, потом про оплату поговорим.

См. ответ от Sinclair.

G>>>·>Можно продолжать работать с корзиной, например, позволять добавлять товары ещё, обновлять адрес доставки, применять скидки и купоны и т.п.

G>>>Лол, а зачем?
G>·>Ага. Раз такого в Озоне нет, то это никому не нужно.
G>Так мы сейчас про конкретную задачу говорим, как в озоне.
Ты заявил "Тогда в этом нет никакого смысла". Конечно, если весь озон состоял из ровно одного сценария и решал ровно одну задачу, то смысла нет. Но, по-моему, "ранзакционно обновлять состояние корзины с резервов на складе" — это не единственное, чем занимается озон.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 13.02.2026 12:24 · . Предыдущая версия . Еще …
Отредактировано 13.02.2026 12:21 · . Предыдущая версия .
Отредактировано 13.02.2026 12:06 · . Предыдущая версия .
Re[27]: Помогите правильно спроектировать микросервисное при
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 13.02.26 14:38
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, gandjustas, Вы писали:


G>>Сохранишь копию order в то же базе в той же тразакции, да?

S>Техническое решение этой задачи для микросервисов уже обсуждалось. Да, оно в каком-то смысле "хуже", чем единая ACID-транзакция — у заказа появляется некое промежуточное состояние, которого не было в исходной реализации.
S>Но давайте прекратим делать вид, что этого решения нет, т.к. это состояние — эфемерное, и фактически время его жизни равно времени даунтайма сервиса склада с точки зрения сервиса корзинки.

ИМХО ACID-транзакция, изобретенная на другом уровне абстракции — все равно транзакция. Если говорить то том, какие транзакции стоит использовать — самопальные или предоставляемые БД, то любой вменяемый разработчик выберет второй вариант.
Единственная причина применять самопал — когда нет возможности применить транзакции в БД.


Чтобы говорить об одном и том же давай зафиксируем детали о которых шла речь:
— Я предлагаю состояние заказа менять в одной транзакции с обновлением остатков. Заказы и остатки естественно должны быть в одной базе
— МСА предлагает сделать две базы, где заказы лежат в одной, а заказы в другой.
Написать код вида:
1. обнови остатки в базе А
2. обнови статус заказа в базе Б
3. если появилась ошибка, то откати обновление в базе А

Если код падает по между шагами 2 и 3, то в базе остается несогласованное состояние.
Значит вместе самим кодом резервирования заказа надо написать еще фоновую задачу, которая откатывает незавершенные резервы.
Я проделал аналогичное в рамках статьи на хабре, результаты неутешительные — https://habr.com/ru/articles/963120/ см раздел "рукопашные транзакции".


Но у нас транзакции не изолированные, то есть межу 1 и 2 может вклиниться изменение, которое обновит заказ.
Это значит что для корректного кода отказа как в фоне, так и в п3 надо сохранять "слепок" заказа на шаге 1.
Этот слепок — это в чистом виде wal log.



G>>Ты наверное начнешь разговор что так делать не надо. Но озону надо. Потому что разница limit-reserverd отображается в интерфейсе приложения как "сколько осталось" и это отображается прямо в результатах поиска, то есть надо быстро получать это число для любого количества товаров.

S>Микросервисы никак не мешают быстрому показу remaining — скорее наоборот, т.к. RDBMS склада вообще не обрабатывает никакой нагрузки, кроме вот этого вот get remaining и reserve stock / unreserve stock / top-up stock.
Получение остатков это запрос к одной таблице. Ему никакая МСА не помешает.

S>Кроме того, её можно шардить — т.к. сами товары друг на друга никак не завязаны, можно держать первые 10000 позиций в одном сервере, вторые 10000 — в другом, и так далее.

Шардить можно и без МСА. Это вообще ортогональные вещи.

S>Не, я в курсе, что как только мы начнём джойнить эту информацию с какими-нибудь запросами по другой части домена (типа "робот-пылесос в пределах 20000 рублей с доставкой до послезавтра и средним баллом по отзывам не ниже 4.9"), то МСА со страшной силой сольёт монолиту. Вот только если мы всё же уперлись в потолок монолита, то возможность получить 2х перформанс ценой распиливания его на 8х частей может оказаться единственным выбором.

Мы же упираемся не в монолит, а в производительность одного сервера БД. Тут есть несколько решений:
1) Кэши для чтения, чтобы нагрузка на БД не прилетала вообще.
2) Чтение из реплик, чтобы нагрузка на мастер не прилетала там где допустимо отдавать слегка устаревшие данные.
3) Партицирование таблиц — это как шардирование, только в пределах одного сервера БД, чтобы нагрузку на запись распределять по разным дискам.
И только когда все возможности исчерпаны — делать шардирование.
ИМХО в ни у одного веб-сайта или приложения нет такой нагрузки чтобы шардирование было оправдано.

Правда это пока мы живем в рамках монолитной базы. Как только мы начинаем её разделять на сервисы (подбазы), то у нас появляются дополнительные данные и процессы, необходимые для поддержания целостности. Выше как раз пример такого. И все это жрет ресурсы.
При достаточном количестве микросервисов система упирается в "потолок" одного сервера очень быстро.


G>>Не лучше, чем в одной базе. Прям строго математически не лучше.

S>Да, но с учётом нюансов, указанных выше. Пока мы не упёрлись в пределы монолита и при отсутствии административных причин, монолит будет строго математически лучше.
Этот потолок сильно выше, чем кажется. При правильном подходе к проектированию до него доберутся единицы, а остальные от сложности проиграют только.

По моему опыту "потолок монолита" не в нагрузке, а тупо в размере команды. Когда у тебя 25 человек еще худо-бедно можно пилить монолит с общей кодовой базой. А если команда становится больше, то начинается деление, которое проходит ровно по границам подразделений. А если подразделения между собой не дружат и не имеют хорошего техлида над ними, то микросервисы помогают избежать бардака.

G>>>>Более того, возможен сценарий когда первая выполнилась, а вторая отвалилась, просто по таймауту. Тогда товар на складе забронирован, а статус корзины не поменялся. Нужно писать код для отката.

G>>·>Это эквивалентно ситуации: клиент наполнил корзину и ушел плюнув, потому что левая пятка зачесалась. Код отката брони на складе ты будешь писать в любом случае.
G>>Не эквивалентно и не придется такое писать. Это твои фантазии
S>Нет, откат писать не надо. Надо писать "накат". И это можно сделать один раз в инфраструктуре worflow-engine.
Допустим

S>Типа вот мы поднялись после сбоя и видим, что корзинка №42342342 была отправлена на резервирование, а результата резервирования нет.

А как мы узнаем что его нет?

S>Значит, либо мы в прошлый раз не достучались до сервера (получили сonnection timeout), либо достучались да он упал до начала резервирования (отдал нам 5хх), либо упал после окончания резервирования (и отдал нам 5хх или connection reset by peer), либо всё нам отдал, да мы расплескали по пути и упали до коммита в локальную БД.

S>Во всех случаях мы тупо идём и повторяем резерв. При необходимости — сколько угодно раз, пока нам таки не удастся записать к себе "успех" либо "фейл". На практике, длинные даунтаймы тут бывают не чаще, чем даунтаймы у монолита. Только у монолита лежит вообще всё, а у МСА можно хотя бы в корзинку что-то складывать да отзывы читать.
А пользователь все это время ждет?
А если он не дождался и ушел?
А если связь между пользователем и приложением пропала?
А если пользователь ушлый и пока в одной вкладе крутится ожидание открыл сайт в другой вкладке и пошел что-то менять?


G>>То ест мало того, что для обработки отмены ты вынужден будешь сохранить почти весь order в в той же транзакции, что и обновление остатков, так еще и дополнишь его полем ключа идемпотентности

S>Да, объекты, пересекающие границы сервисов, в МСА должны храниться на обеих сторонах. Это не обязательно одни и те же объекты, но у них должна быть общая проекция. В данном случае сторону склада не интересуют никакие подробности про способы доставки товара, розничные цены, или там демографию покупателя, но вот список артикулов и количеств, снабжённый уникальным ID, ей необходим. Ровно для того, чтобы когда к нему в следующий раз стукнутся с просьбой зарезервировать корзинку №42342342 он мог не делать повторный резерв, а сразу отдать 200 ok.
Я понимаю, что при высокоуровневом взгляде кажется что это все просто, написать три-четыре строки в воркфлоу и будет хорошо. А на практике для обеспечения целостности нужны десятки строк кода и дополнительные данные хранить (что увеличивает нагрузку какбы).
И самое главное — ради чего это все? Чтобы не упереться в мифический "потолок монолита". С МСА этот потолок окажется очень низко.


S>Вот картинка для доступности строго согласованного кластера из трёх узлов с доступностью Anode, связанных каналами с доступностью Alink:

S>Белый контур — это зона, где эта доступность выше доступности каждого из компонентов.
Это в каком контексте?
Насколько понимаю картинка эта для случая когда:
1) Есть несколько экземпляров ОДНОЙ И ТОЙ ЖЕ базы
2) Клиент подключается к ЛЮБОМУ экземпляру и может менять ЛЮБЫЕ данные
То есть мультиматер-кластер.

В нашем случае вообще никаких кластеров нет. Мы просто в рамках одного процесса обращаемся к двум сервисам на двух разных серверах. Операция может завершиться успешно если обе операции завершатся успешно.
Каждая из этих баз может представлять из себя мкльтимастер-кластер, а может одни сервер, а может шардированную базу, это не имеет значения.


G>>·>"резервирование сделаем транзакционно" не решает проблему "пользователь плюет".

G>>Конечно решает, потому что пользователь после резервации на складе точно получит свой заказ.
S>В нашем случае пользователь после резервации на складе тоже точно получит свой заказ. Вся разница — в том, что если будет сбой системы во время заказа, то пользователь монолита до окончания сбоя будет получать 502, а пользователь МСА имеет шанс в это время увидеть спиннер "заказ резервируется....".
Это вопрос реализации фронта. Мы при любой архитектуре можем вынести повтор именно на фронт.

S>В любом случае результат резервирования станет известен только по окончанию сбоя — и точно так же, как в монолитном случае можно получить как подтверждение успеха, так и "извините, но другой покупатель успел зарезервировать товар до вас".

Тогда вопрос — если не видно разницы, то зачем платить больше?
Я скинул ссылку на статью выше, там рукопашные транзакции почти в два раза уронили производительность.

G>>·>Для этого не требуется обновлять корзину и склад в одной транзакции.

G>>Я уже выше описал почему требуется. Не повторяй эту глупость уже
S>При всём уважении — это не глупость, а вполне себе математическая реальность. Я выше написал, как именно это работает.
Дьявол как всегда в деталях.

G>>Лол, а зачем?

S>Затем, что в реальном приложении — сотни бизнес-сценариев.
Я предлагаю на одном сконцентрироваться. Это же реальный сценарий.
Когда с ним закончим сможем посмотреть как эти подходы масштабировать.

S>Остановка на техобслуживание одного из сервисов задержит только те сценарии, которые проходят через него.

Мы же рассматриваем сценарий когда у нас сценарий зависит от доступности двух серверов. На одно из них, а сразу двух. Их доступность равна произведению доступности обоих. А она будет меньше, чем доступность одного.

S>И если этот сервис достаточно простой и маленький, то его рестарт не будет занимать десятки минут. Таким образом, perceived availability может оказаться значительно выше, чем у монолита. Ну вот, абстрактно, мы на пять минут отключили банковское ядро, которое собственно проводит платежи. Если всё сделано по уму, то пользователи это увидят только как "странно, я вроде по СБП деньги отправил, а у получателя телефон что-то не вибрирует". Если чуть хуже — то как "переводы пока недоступны, приходите позже". Если ещё хуже — то как "Непредвиденная ошибка. Перезапустите приложение или зайдите позже". И в любом из этих случаев люди, которые смотрят какую-нибудь там аналитику расходов, или остатки по вкладам, или условия кредитов/страховок/етк не заметят вообще ничего. С их точки зрения никакого сбоя не было. А если нам нужно сделать то же самое в монолите, то опускать нужно примерно всё, и всё будет лежать сразу у всех пользователей.

Мы о чем говорим? О серверах приложений или о субд? СУБД в продах стоят в HA кластерах и спокойно выдерживают остановку одного из серверов. От силы 15-20 сек ожидания переезда мастера если сервак упал неожиданно.
Приложения можно нарезать на десятки отдельных модулей и запускать отдельно: в отдельных процессах, в модулях одного процесса — как удобно. Все что написано выше для них верно.
Re[28]: Помогите правильно спроектировать микросервисное при
От: · Великобритания  
Дата: 13.02.26 15:56
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Чтобы говорить об одном и том же давай зафиксируем детали о которых шла речь:

G>- Я предлагаю состояние заказа менять в одной транзакции с обновлением остатков. Заказы и остатки естественно должны быть в одной базе
G>- МСА предлагает сделать две базы, где заказы лежат в одной, а заказы в другой.
G>Написать код вида:
G> 1. обнови остатки в базе А
G> 2. обнови статус заказа в базе Б
G> 3. если появилась ошибка, то откати обновление в базе А

G>Если код падает по между шагами 2 и 3, то в базе остается несогласованное состояние.

G>Значит вместе самим кодом резервирования заказа надо написать еще фоновую задачу, которая откатывает незавершенные резервы.
Ты упускаешь мою мысль, что этот код — не особенность МСА-реализации, а реальность бизнес-задачи. Просто в твоём монолитном решении ты игнорируешь эту проблему. С точки зрения бизнеса _изначальная_ задача выглядит так:
1. Пользователь создаёт заказ
2. Склад резервирует заказ
3. Пользователь завершает заказ

И вот когда ты опишешь задачу так, то совершенно ВНЕЗАПНО получается, что "надо написать еще фоновую задачу, которая откатывает незавершенные резервы" в любом случае, неважно — МСА у тебя или монолит.
МСА просто это сделал явным.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[28]: Помогите правильно спроектировать микросервисное при
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.02.26 06:32
Оценка: +1
Здравствуйте, gandjustas, Вы писали:

G>ИМХО ACID-транзакция, изобретенная на другом уровне абстракции — все равно транзакция. Если говорить то том, какие транзакции стоит использовать — самопальные или предоставляемые БД, то любой вменяемый разработчик выберет второй вариант.

Нет, сделать ACID-транзакцию поверх ненадёжного соединения не получится.
G>Единственная причина применять самопал — когда нет возможности применить транзакции в БД.
Всё верно.

G>- Я предлагаю состояние заказа менять в одной транзакции с обновлением остатков. Заказы и остатки естественно должны быть в одной базе

G>- МСА предлагает сделать две базы, где заказы лежат в одной, а заказы в другой.
G>Написать код вида:
G> 1. обнови остатки в базе А
G> 2. обнови статус заказа в базе Б
G> 3. если появилась ошибка, то откати обновление в базе А
Нет, МСА такого не предлагает.
МСА предлагает использование Representational State Transfer. Работает он так:
1. Заказ в базе Б находится в статусе "черновик".
2. По команде "зарезервировать заказ" заказ атомарно переходит в статус "резервируется". Из этого статуса его вручную вывести нельзя, и изменить состав заказа в этом статусе тоже нельзя.
3. Сервисом заказов делается попытка выполнить идемпотентную операцию "создать резерв" в сервисе А.
3.1. Если сервис А отвечает отказом, то сервис Б атомарно возвращает заказ в статус "черновик" и пишет в историю заказа "резервирование не удалось".
3.2. Если сервис А отвечает подтверждением, то сервис Б атомарно перемещает заказ в статус "зарезервировано", и для него становятся доступны следующие операции стейт-машины.
3.3. Если сервису Б не удается обработать ответ от сервиса А по любой из причин, перечисленных в моём предыдущем посте, то заказ остаётся в статусе "резервируется".

G>Если код падает по между шагами 2 и 3, то в базе остается несогласованное состояние.

Несогласованное состояние в REST возникает только в тот момент, когда мы не получили ответ на запрос "распространить изменение". Причём мы знаем о том, что оно несогласованное — и можем принять меры, чтобы эта несогласованность не вышла нам боком.

G>Значит вместе самим кодом резервирования заказа надо написать еще фоновую задачу, которая откатывает незавершенные резервы.

Нет, никакой "фоновой задачи отката" писать не нужно. А что нужно — так это писать движок стейт-машины, который вполне универсален. Любители бойлерплейта могут развернуться и написать отдельные "фоновые задачи" для каждого такого под-сценария, но современные технологии вполне позволяют раз и навсегда написать код, который в каждом сервисе процессит очередь исходящих запросов и долбит их до тех пор, пока не получит от удалённой стороны внятный ответ. Этот внятный ответ отдаётся в код "продолжения", который и переключает состояние машины в следующий стейт.

G>Я проделал аналогичное в рамках статьи на хабре, результаты неутешительные — https://habr.com/ru/articles/963120/ см раздел "рукопашные транзакции".

Я бы не сказал, что аналогичное. Там вы экспериментируете с рукопашными транзакциями в единой СУБД. Здесь речь идёт о реализации распространения изменений в ненадёжной среде.

G>Но у нас транзакции не изолированные, то есть межу 1 и 2 может вклиниться изменение, которое обновит заказ.

G>Это значит что для корректного кода отказа как в фоне, так и в п3 надо сохранять "слепок" заказа на шаге 1.
G>Этот слепок — это в чистом виде wal log.
Нет, так делать не надо. Основное заблуждение здесь — возможность надёжно определить ситуацию "возникла ошибка". Нет, у вас запросто может так получиться, что "пассивный" сервис в следующее состояние переехал, а "активный" об этом не узнал. Изменения должны распространяться ровно в одном направлении.
Схема начинает становиться более интересной в том случае, когда в транзакции участвует больше 1 пассивного сервиса.
Сам по себе REST даёт только идемпотентность, дающую нам возможность детерминированно двигаться "вперёд" во времени. И если у нас сервис 1 делает успешный вызов сервиса 2, а затем — неуспешный вызов сервиса 3, то мы зависаем в некотором "бесполезном" состоянии, из которого может не оказаться вообще никакого выхода. Ну, как если купить несдаваемые билеты, и обнаружить, что мест в гостиницах в городе назначения уже нет.
К счастью, в большинстве практических случаев всё же сторонние сервисы предоставляют возможности "сторнировать" проведённое изменение. Зарезервированный товар можно снять с резерва, проведённый по карте платёж можно отменить. И даже покупка авиабилетов очень часто всего лишь ставит их в резерв, хоть и на не очень длинное время. Но всё же это время заведомо больше, чем время рестарта типичных сервисов, поэтому нам его хватит для завершения сценария даже в таком сложном случае.
А если мы говорим об МСА, где все участники сценария — наши же собственные сервисы, то мы их сразу так и проектируем, чтобы иметь возможность безболезненной отмены, если какая-то из частей сценария напоролась на неразрешимую ситуацию.

G>Получение остатков это запрос к одной таблице. Ему никакая МСА не помешает.

Именно. А в монолите у вас тот же самый ресурс (CPU и шина) обрабатывают не только запрос к этой таблице, а ещё 2400 таблиц вашей развесистой ентерпрайз-БД.
И пока там кто-то начисляет бонусы сотрудникам, остатки на складах начинают лагать

G>Шардить можно и без МСА. Это вообще ортогональные вещи.

Не совсем ортогональные. Шарды — это и есть "микросервисы для бедных".

G>Мы же упираемся не в монолит, а в производительность одного сервера БД. Тут есть несколько решений:

Всё верно.
G>Правда это пока мы живем в рамках монолитной базы. Как только мы начинаем её разделять на сервисы (подбазы), то у нас появляются дополнительные данные и процессы, необходимые для поддержания целостности. Выше как раз пример такого. И все это жрет ресурсы.
Да, верно, жрёт.
G>При достаточном количестве микросервисов система упирается в "потолок" одного сервера очень быстро.
Вот это утверждение я не понял. Вы собираетесь все микросервисы гонять на одном узле?

G>Этот потолок сильно выше, чем кажется. При правильном подходе к проектированию до него доберутся единицы, а остальные от сложности проиграют только.

С этим согласен.

G>По моему опыту "потолок монолита" не в нагрузке, а тупо в размере команды. Когда у тебя 25 человек еще худо-бедно можно пилить монолит с общей кодовой базой. А если команда становится больше, то начинается деление, которое проходит ровно по границам подразделений. А если подразделения между собой не дружат и не имеют хорошего техлида над ними, то микросервисы помогают избежать бардака.

+1. Особенно ярко это проявляется в международных компаниях, где "над ними" не то, что хорошего — вообще никакого техлида может не быть.

G>Допустим


S>>Типа вот мы поднялись после сбоя и видим, что корзинка №42342342 была отправлена на резервирование, а результата резервирования нет.

G>А как мы узнаем что его нет?
Смотрим в свою базу и видим: статус — "резервирование начато".

G>А пользователь все это время ждет?

Конечно.
G>А если он не дождался и ушел?
Произойдёт ровно то же самое, что будет в случае, если у нас "пропал" монолит — ну там, временный сбой, перезагрузка сервиса. Или пропала связь между приложением пользователя и монолитом.
Или даже связь на месте, но одна из позиций на складе сейчас заблокирована другой транзакцией, в рамках которой происходит какое-то замедленное взаимодействие. Так что мы не получаем ни аборта, ни коммита, а просто ждём.
G>А если связь между пользователем и приложением пропала?
То он перезапускает приложение и восстанавливает картину мира.
G>А если пользователь ушлый и пока в одной вкладе крутится ожидание открыл сайт в другой вкладке и пошел что-то менять?
Если разработчики сервиса обладают мало-мальской квалификацией, то ничего плохого не произойдёт. Нельзя же, в самом деле, строить свой код на предположении о том, что с каждым заказом в один момент времени работает ровно одна транзакция.

S>>Да, объекты, пересекающие границы сервисов, в МСА должны храниться на обеих сторонах. Это не обязательно одни и те же объекты, но у них должна быть общая проекция. В данном случае сторону склада не интересуют никакие подробности про способы доставки товара, розничные цены, или там демографию покупателя, но вот список артикулов и количеств, снабжённый уникальным ID, ей необходим. Ровно для того, чтобы когда к нему в следующий раз стукнутся с просьбой зарезервировать корзинку №42342342 он мог не делать повторный резерв, а сразу отдать 200 ok.

G>Я понимаю, что при высокоуровневом взгляде кажется что это все просто, написать три-четыре строки в воркфлоу и будет хорошо. А на практике для обеспечения целостности нужны десятки строк кода и дополнительные данные хранить (что увеличивает нагрузку какбы).
Ну да, около двухсот строк кода. Один раз на всю систему. Не так уж и плохо
G>И самое главное — ради чего это все? Чтобы не упереться в мифический "потолок монолита". С МСА этот потолок окажется очень низко.

G>Это в каком контексте?

G>Насколько понимаю картинка эта для случая когда:
G>1) Есть несколько экземпляров ОДНОЙ И ТОЙ ЖЕ базы
G>2) Клиент подключается к ЛЮБОМУ экземпляру и может менять ЛЮБЫЕ данные
G>То есть мультиматер-кластер.
Да, совершенно верно.

G>В нашем случае вообще никаких кластеров нет. Мы просто в рамках одного процесса обращаемся к двум сервисам на двух разных серверах. Операция может завершиться успешно если обе операции завершатся успешно.

G>Каждая из этих баз может представлять из себя мкльтимастер-кластер, а может одни сервер, а может шардированную базу, это не имеет значения.
Тут картина на самом деле сильно сложнее. Детальный расчёт perceived availability потребует знания соотношения частот сценариев с различным количеством узлов.

G>>>·>"резервирование сделаем транзакционно" не решает проблему "пользователь плюет".

G>>>Конечно решает, потому что пользователь после резервации на складе точно получит свой заказ.
S>>В нашем случае пользователь после резервации на складе тоже точно получит свой заказ. Вся разница — в том, что если будет сбой системы во время заказа, то пользователь монолита до окончания сбоя будет получать 502, а пользователь МСА имеет шанс в это время увидеть спиннер "заказ резервируется....".
G>Это вопрос реализации фронта. Мы при любой архитектуре можем вынести повтор именно на фронт.
Фронт, с т.з. REST — это точно такой же узел сети. По-хорошему, он проектируется по тем же принципам; но благодаря наличию пользователя можно упростить реализацию. Хочешь повтор — жмёшь F5.

G>Тогда вопрос — если не видно разницы, то зачем платить больше?

Затем, что можно делать систему, которая выдерживает много пользователей. Если чо, антропики — не очень хороший пример. У них сценарии все крайне тривиальные, поэтому масштабироваться не трудно.
G>Я скинул ссылку на статью выше, там рукопашные транзакции почти в два раза уронили производительность.
В МСА коэффициент на таких транзакциях будет хуже, чем в два раза.
G>Дьявол как всегда в деталях.
Именно.

G>Я предлагаю на одном сконцентрироваться. Это же реальный сценарий.

G>Когда с ним закончим сможем посмотреть как эти подходы масштабировать.

S>>Остановка на техобслуживание одного из сервисов задержит только те сценарии, которые проходят через него.

G>Мы же рассматриваем сценарий когда у нас сценарий зависит от доступности двух серверов. На одно из них, а сразу двух. Их доступность равна произведению доступности обоих. А она будет меньше, чем доступность одного.

G>Мы о чем говорим? О серверах приложений или о субд?

Мы говорим о сервисе. Как у него там внутри устроено деление между СУБД и апп-серверами — это дело сервиса; пользователи всего этого не видят.
G>Приложения можно нарезать на десятки отдельных модулей и запускать отдельно: в отдельных процессах, в модулях одного процесса — как удобно. Все что написано выше для них верно.
Чтобы приложения можно было нарезать, между ними должны быть какие-то границы. Как вы передадите одну транзакцию между несколькими модулями в разных процессах? В МСА вы можете накатить апдейт на "модуль ценообразования", не останавливая модули корзинки и склада. В монолите вам придётся делать общий даунтайм. И количество серверов СУБД в HA-кластере вас не спасёт от того, что апдейт схемы плохо совместим с data-level блокировками. То есть "на ходу" его провести возможно далеко не всегда; безопасный способ — переключать базу в монопольный режим.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 14.02.2026 18:37 Sinclair . Предыдущая версия .
Re[29]: Помогите правильно спроектировать микросервисное при
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 16.02.26 10:23
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, gandjustas, Вы писали:


G>>ИМХО ACID-транзакция, изобретенная на другом уровне абстракции — все равно транзакция. Если говорить то том, какие транзакции стоит использовать — самопальные или предоставляемые БД, то любой вменяемый разработчик выберет второй вариант.

S>Нет, сделать ACID-транзакцию поверх ненадёжного соединения не получится.
У нас все соединения ненадежные. Мы на практике не рассматриваем случай когда фейл происходит между коммитом и ответом о коммите от базы.


S>МСА предлагает использование Representational State Transfer. Работает он так:

S>1. Заказ в базе Б находится в статусе "черновик".
S>2. По команде "зарезервировать заказ" заказ атомарно переходит в статус "резервируется". Из этого статуса его вручную вывести нельзя, и изменить состав заказа в этом статусе тоже нельзя.
S>3. Сервисом заказов делается попытка выполнить идемпотентную операцию "создать резерв" в сервисе А.
S>3.1. Если сервис А отвечает отказом, то сервис Б атомарно возвращает заказ в статус "черновик" и пишет в историю заказа "резервирование не удалось".
S>3.2. Если сервис А отвечает подтверждением, то сервис Б атомарно перемещает заказ в статус "зарезервировано", и для него становятся доступны следующие операции стейт-машины.
S>3.3. Если сервису Б не удается обработать ответ от сервиса А по любой из причин, перечисленных в моём предыдущем посте, то заказ остаётся в статусе "резервируется".
А когда получает ответ пользователь?
Ему же надо дать ответ в моменте и показать окно оплаты.
И что будет если приложение упадет в пункте 3.2 между ответом А и изменением статуса заказа в Б?

Я в принципе знаю правильный ответ: он называется WAL, а на более высоком уровне абстракции — transactional outbox. Данные для выполнения транзакции сначала атомарно пишутся в (одно, иначе атомарности не будет) долговременное хранилище, а потом пытаются примениться к конкретным таблицам.
Это и называется "рукопашные транзакции". Мы рассматриваем сценарий, когда без них можно обойтись.


G>>Если код падает по между шагами 2 и 3, то в базе остается несогласованное состояние.

S>Несогласованное состояние в REST возникает только в тот момент, когда мы не получили ответ на запрос "распространить изменение". Причём мы знаем о том, что оно несогласованное — и можем принять меры, чтобы эта несогласованность не вышла нам боком.
Несогласованное состояние в примере выше возникнет если заказ на складе будет забронирован в сервисе А, а на сервисе Б не изменен статус.


G>>Значит вместе самим кодом резервирования заказа надо написать еще фоновую задачу, которая откатывает незавершенные резервы.

S>Нет, никакой "фоновой задачи отката" писать не нужно.
А что насчет фейла, что я описал выше?


G>>Но у нас транзакции не изолированные, то есть межу 1 и 2 может вклиниться изменение, которое обновит заказ.

G>>Это значит что для корректного кода отказа как в фоне, так и в п3 надо сохранять "слепок" заказа на шаге 1.
G>>Этот слепок — это в чистом виде wal log.
S>Нет, так делать не надо.
А как надо?
В твоем примере выше проблема та же самая. Процесс на шаге 3 может прерваться между обращениями к базам А и Б.
Это будет ошибка — неконсистентное состояние, которое нужно будет или откатить до исходного или каким-то образом докатить до финального. Причем делать это уже фоновым процессом, потому что прерывание, вероятнее всего, произойдет по причине того, что пользователь ушел\связь прервалась. Фоновому процессу надо где-то брать данные о той операции, что надо откатить\докатить, это и будет wal или аналог.

S>Основное заблуждение здесь — возможность надёжно определить ситуацию "возникла ошибка". Нет, у вас запросто может так получиться, что "пассивный" сервис в следующее состояние переехал, а "активный" об этом не узнал. Изменения должны распространяться ровно в одном направлении.

Необходимости на уровне кода определять "были ли ошибка" конечно нет. Но код который откатывает\докатывает транзакцию все равно нужен.


S>А если мы говорим об МСА, где все участники сценария — наши же собственные сервисы, то мы их сразу так и проектируем, чтобы иметь возможность безболезненной отмены, если какая-то из частей сценария напоролась на неразрешимую ситуацию.

Так давайте на одном простом примере разберемся как оно должно быть? Я вот до сих пор не понимаю как сделать надежное резервирование заказов в рамках МСА с разделением сервисов на "заказ" и "склад", чтобы хотя бы по надежности не уступало одной базе. По трудозатратам однозначно в разы проиграет и по быстродействию\масштабируемости тоже.


G>>Получение остатков это запрос к одной таблице. Ему никакая МСА не помешает.

S>Именно. А в монолите у вас тот же самый ресурс (CPU и шина) обрабатывают не только запрос к этой таблице, а ещё 2400 таблиц вашей развесистой ентерпрайз-БД.
S>И пока там кто-то начисляет бонусы сотрудникам, остатки на складах начинают лагать
Ну нет конечно. readable реплики никто не отменял.

Почему-то стандартная софистика апологетов МСА, будто монолитная база это всегда одни сервер без масштабирования.
В реальности у каждого энтерпрайз-БД сервера есть реплики, минимум две, иногда больше. Реплики могут быть разной степени синхронности, когда нам можно отдавать слегка устаревшие данные, например для расчета премий.
Поэтому нагрузка на чтение в монолитной базе масштабируется не хуже чем в МСА. На практике даже в разы лучше, так как джоины и индексы есть.
А нагрузка на запись масштабируется дисками и партицированием.


G>>Шардить можно и без МСА. Это вообще ортогональные вещи.

S>Не совсем ортогональные. Шарды — это и есть "микросервисы для бедных".
Нет конечно. шарды это когда мы одну монолитную базу разделяем на несколько инстансов по ключу шардирования. По сути тоже самое партицирование, только с разнесением партиций между серверами.
Причём если при патицировании можно делить только нагруженные таблицы, то при шардировании придется делить всё. Да еще и придумывать механизм реплицирования изменений в общих таблицах.

К шаридованию стоит прибегать тогда, когда нагрузка на CPU при записи (перестроении индексов) уже превышает ресурсы мастера. Но до такой нагрузки 99,999% проектов не доживет никогда.


G>>При достаточном количестве микросервисов система упирается в "потолок" одного сервера очень быстро.

S>Вот это утверждение я не понял. Вы собираетесь все микросервисы гонять на одном узле?
А на скольких узлах надо гонять пока пользователей нет?
Чаще всего так и происходит, что система изначально построенная по МСА гоняется на одном сервере БД в разных базах, а по мере роста нагрузки поднимаются новые серваки. При этом потребность в масштабировании при МСА возникает гораздо раньше, чем в монолитной базе.

S>>>Типа вот мы поднялись после сбоя и видим, что корзинка №42342342 была отправлена на резервирование, а результата резервирования нет.

G>>А как мы узнаем что его нет?
S>Смотрим в свою базу и видим: статус — "резервирование начато".
G>>А если он не дождался и ушел?
S>Произойдёт ровно то же самое, что будет в случае, если у нас "пропал" монолит — ну там, временный сбой, перезагрузка сервиса. Или пропала связь между приложением пользователя и монолитом.
Вот тут неверно.
Если пропала связь в момент между "обновлены остатки на складе" и "обновлен статус заказа", то в МСА получаем неконсистентное состояние, которое еще и непонятно как откатывать\докатывать. В случае монобазы мы получаем отмену транзакции и возврат к исходному состоянию будто ничего не было. Причем бесплатно с точки зрения трудозатрат.

S>Если разработчики сервиса обладают мало-мальской квалификацией, то ничего плохого не произойдёт. Нельзя же, в самом деле, строить свой код на предположении о том, что с каждым заказом в один момент времени работает ровно одна транзакция.

Фишка в том, что I в ACID говорит нам, что мы можем написать код, который предполагает что работает одна транзакция, она или выполнится до конца как будто она была одна в этот момент времени или откатится если такое не получилось.
Но когда мы границы транзакции в одной базе пересекаем никаких гарантий больше нет и все надо делать руками.

G>>Я понимаю, что при высокоуровневом взгляде кажется что это все просто, написать три-четыре строки в воркфлоу и будет хорошо. А на практике для обеспечения целостности нужны десятки строк кода и дополнительные данные хранить (что увеличивает нагрузку какбы).

S>Ну да, около двухсот строк кода. Один раз на всю систему. Не так уж и плохо
Это на один сценарий, где требуется изменение более чем в одной базе. На каждый сценарий будет свой код отката\доката, который вряд ли получится обобщить.


G>>В нашем случае вообще никаких кластеров нет. Мы просто в рамках одного процесса обращаемся к двум сервисам на двух разных серверах. Операция может завершиться успешно если обе операции завершатся успешно.

G>>Каждая из этих баз может представлять из себя мкльтимастер-кластер, а может одни сервер, а может шардированную базу, это не имеет значения.
S>Тут картина на самом деле сильно сложнее. Детальный расчёт perceived availability потребует знания соотношения частот сценариев с различным количеством узлов.
У нас пока один сценарий, давайте с ним разберемся.
Опять типичная софистика — вместо рассмотрения одного кейса сразу начинаем "масштабировать", как-будто недостаток в одном месте чем-то перекроется в других местах. Не перекроется, недостатки будут размножаться во всех остальных сценариях тоже.

S>Фронт, с т.з. REST — это точно такой же узел сети. По-хорошему, он проектируется по тем же принципам; но благодаря наличию пользователя можно упростить реализацию. Хочешь повтор — жмёшь F5.

С точки зрения РЕСТ — да, а с точки зрения архитектуры — нет. На фронте нет никакой транзакционности и отката. Выполнение сценария на фронте может прекратиться в любой момент и не продолжиться вообще никогда, и это не является ошибкой.
Поэтому мы должны на стороне сервера делать откат\докат фоновым процессом.

G>>Я предлагаю на одном сконцентрироваться. Это же реальный сценарий.

G>>Когда с ним закончим сможем посмотреть как эти подходы масштабировать.

S>>>Остановка на техобслуживание одного из сервисов задержит только те сценарии, которые проходят через него.

G>>Мы же рассматриваем сценарий когда у нас сценарий зависит от доступности двух серверов. На одно из них, а сразу двух. Их доступность равна произведению доступности обоих. А она будет меньше, чем доступность одного.

G>>Приложения можно нарезать на десятки отдельных модулей и запускать отдельно: в отдельных процессах, в модулях одного процесса — как удобно. Все что написано выше для них верно.

S>Чтобы приложения можно было нарезать, между ними должны быть какие-то границы.
Границы могут быть разными, в том числе чисто логическими.
Допустим есть прям монолитное приложение, которое весь код запускает в одном процессе. Никакого состояния между запросами в памяти процесса не хранится, все сохраняется в монобазу.
Можем ли мы горизонтально масштабировать приложение, запуская несколько экземпляров на разных серверах? Если нет, то почему?


S>Как вы передадите одну транзакцию между несколькими модулями в разных процессах?

Зачем нам разные процессы?

S>В МСА вы можете накатить апдейт на "модуль ценообразования", не останавливая модули корзинки и склада. В монолите вам придётся делать общий даунтайм.

В монолите я могу сделать rolling update в кластере кубера, обновляя приложение вообще без остановки обслуживания.
С фича-флагами я могу вообще разные версии "модуля ценообразования" запускать даже для разных пользователей.
С хорошей модульностью я могу в части экземпляров запускать часть модулей, а в других этого не делать.
Имхо не надо подстраивать архитектуру под инфраструктурные задачи.

S>И количество серверов СУБД в HA-кластере вас не спасёт от того, что апдейт схемы плохо совместим с data-level блокировками.

А как тут разделение поможет? Если вам нужен access exclusive на таблицу для обновления, то какая разница сколько всего у вас баз?

S>То есть "на ходу" его провести возможно далеко не всегда; безопасный способ — переключать базу в монопольный режим.

не так уж много операций требуют монопольного режима для всей базы, а для отдельных таблиц разделение не поможет.
Re[29]: Помогите правильно спроектировать микросервисное при
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 16.02.26 10:51
Оценка:
Здравствуйте, ·, Вы писали:

·>Ты упускаешь мою мысль, что этот код — не особенность МСА-реализации, а реальность бизнес-задачи.

Что означает "реальность бизнес-задачи" ?
Каким образом бизнес-задача приводит к нас к нескольким базам?

·>Просто в твоём монолитном решении ты игнорируешь эту проблему.

О какой проблеме идет речь?

·>С точки зрения бизнеса _изначальная_ задача выглядит так:

·>1. Пользователь создаёт заказ
·>2. Склад резервирует заказ
·>3. Пользователь завершает заказ
И что? Нам поэтому нужно сделать отдельные базы для Заказа, Склада и Пользователя, чтобы соответствовало существительным?
А как в 1С конфигурации УНФ это все в одной базе существует? Оно как-то неправильно работает?
В 1С вообще это все решалось в рамках одной базы задолго до появления термина "микросервисы". В чем они неправы?


·>И вот когда ты опишешь задачу так, то совершенно ВНЕЗАПНО получается, что "надо написать еще фоновую задачу, которая откатывает незавершенные резервы" в любом случае, неважно — МСА у тебя или монолит.

Важно. В монолите это все не нужно. Монобаза тебе обеспечивает атомарность, вообще всегда.

·>МСА просто это сделал явным.

МСА перекладывает на разработчика то, что в монолите делается средствами СУБД.
Re[30]: Помогите правильно спроектировать микросервисное при
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.02.26 12:47
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>У нас все соединения ненадежные. Мы на практике не рассматриваем случай когда фейл происходит между коммитом и ответом о коммите от базы.

Не совсем так. Если речь идёт о традиционном клиенте, неважно — толстом там или тонком, то стейт такого клиента в любом случае эфемерный.
И если у нас при нажатии кнопки "сабмит" в таком клиенте вылетает какая-нибудь "неожиданная ошибка", то рекомендованное поведение в общем случае сводится к "перезапустите клиента".
То есть мы выбрасываем наше эфемерное состояние и загружаем его обратно из нашей БД.
А уже в ней у нас есть все гарантии ACID-ity. То есть заказ, который мы отправляли на резервирование, в любом случае либо целиком зарезервирован, либо целиком незарезервирован.
И работает это благодаря тому, что вся транзакция исполняется в контексте монолитной СУБД, у которой нет возможности получить partition.
И только когда мы начинаем исполнять эту транзакцию на нескольких узлах, которые связаны ненадёжными каналами и имеют шансы нарваться на partition, в дело вступает Брюер со своей CAP теоремой.

S>>МСА предлагает использование Representational State Transfer. Работает он так:

S>>1. Заказ в базе Б находится в статусе "черновик".
S>>2. По команде "зарезервировать заказ" заказ атомарно переходит в статус "резервируется". Из этого статуса его вручную вывести нельзя, и изменить состав заказа в этом статусе тоже нельзя.
S>>3. Сервисом заказов делается попытка выполнить идемпотентную операцию "создать резерв" в сервисе А.
S>>3.1. Если сервис А отвечает отказом, то сервис Б атомарно возвращает заказ в статус "черновик" и пишет в историю заказа "резервирование не удалось".
S>>3.2. Если сервис А отвечает подтверждением, то сервис Б атомарно перемещает заказ в статус "зарезервировано", и для него становятся доступны следующие операции стейт-машины.
S>>3.3. Если сервису Б не удается обработать ответ от сервиса А по любой из причин, перечисленных в моём предыдущем посте, то заказ остаётся в статусе "резервируется".
G>А когда получает ответ пользователь?
после 3.1/3.2
G>Ему же надо дать ответ в моменте и показать окно оплаты.
Окно оплаты мы показываем только в п. 3.2.
G>И что будет если приложение упадет в пункте 3.2 между ответом А и изменением статуса заказа в Б?
Тут нам придётся немножко погадать о том, что такое "приложение" в этом сценарии. Ну, допустим, это тонкий стейтлесс-клиент, всё "падение" которого — это браузер, который внезапно устал ждать разрешения пользователя на установку апдейта и самопроизвольно перезапустился. Пользователь видит ту же страницу того же заказа, статус которого приложение перезапрашивает у Б через API и видит соответствующий статус "товары в резерве, произведите оплату до ЧЧ:ММ ДД.ММ.ГГГГ".

G>Я в принципе знаю правильный ответ: он называется WAL, а на более высоком уровне абстракции — transactional outbox. Данные для выполнения транзакции сначала атомарно пишутся в (одно, иначе атомарности не будет) долговременное хранилище, а потом пытаются примениться к конкретным таблицам.

Да, в некоторых случаях мы можем попытаться нарулить что-то вроде WAL. Но не всегда, т.к. под капотом у WAL — элементарные таблицы, для которых чётко и однозначно определены операции чтения и записи.
Это и даёт нам железобетонную уверенность в том, что после рестарта мы можем откатить неоконченные транзакции и накатить оконченные.
Когда мы проектируем в терминах сервисов, такой гарантии у нас в общем случае нет.

G>>>Если код падает по между шагами 2 и 3, то в базе остается несогласованное состояние.

S>>Несогласованное состояние в REST возникает только в тот момент, когда мы не получили ответ на запрос "распространить изменение". Причём мы знаем о том, что оно несогласованное — и можем принять меры, чтобы эта несогласованность не вышла нам боком.
G>Несогласованное состояние в примере выше возникнет если заказ на складе будет забронирован в сервисе А, а на сервисе Б не изменен статус.
Вы говорите то же самое, что и я, немного другими словами. Потому, что "в сервисе Б не изменён статус" == "сервис Б не смог получить ответ на запрос о распространении изменения".
Как только он сможет получить этот ответ, статус заказа сразу же и изменится. Без каких-либо рукопашных действий со стороны пользователя или администратора.

G>А что насчет фейла, что я описал выше?

Не уверен, что я правильно понял сценарий фейла, который вы описали.
Я описал ВСЕ сценарии, которые возможны в рассматриваемой ситуации.

G>В твоем примере выше проблема та же самая. Процесс на шаге 3 может прерваться между обращениями к базам А и Б.

G>Это будет ошибка — неконсистентное состояние, которое нужно будет или откатить до исходного или каким-то образом докатить до финального.
Я же написал, как именно докатить. У нас в сервисе Б есть заказ(ы) в состоянии "резервируется". Всё, что нам нужно знать про эти заказы для завершения резервирования, лежит прямо в базе Б и никак не зависит от прихода или ухода клиента. "Фоновый поток" стейт машины берёт каждый такой заказ (на самом деле — remote request task, т.к. этому коду всё равно, идёт ли речь о заказах, или доставках, или рассылках, или ещё о чём-то) из очереди и пытается выполнить заказанную идемпотентную операцию. В нашем случае — "создать резерв для заказа Х" в сервисе А. Если у сервиса А уже есть резерв для этого заказа — он отвечает 200 Ok, давая возможность сервису Б записать в его базу результат операции, то есть переключить заказ Х в состояние "зарезервирован, ожидает оплаты". Если у сервиса А нет резерва для этого заказа — он попробует его выполнить, и ответит либо 200 Ok, либо 4хх если резерв выполнить невозможно. В обоих случаях это даёт сервису Б шанс записать в свой стейт новый статус заказа.
Если у нас каждый 10й запрос от Б к А заканчивается "потерей результата" (неважно по чьей вине — или А в это время перезапускают, или Б, или сеть между ними ложится из-за игр админов), с вероятностью 90% мы получим результат за 1 обращение, с вероятностью 99% — за 2, с вероятностью 99.9% — за три и так далее. В реальности надёжность системы из сети и двух сервисов настолько высока, что в 95-й процентиль укладываются запросы с 1 попытки.

G>Причем делать это уже фоновым процессом, потому что прерывание, вероятнее всего, произойдет по причине того, что пользователь ушел\связь прервалась.

Пользователь здесь играет только роль инициатора операции. Ему не надо "следить" за системой. Вот в вашей системе что будет, если связь с пользователем прервалась между тем, как транзакция в БД закоммитилась, и ему было показано окно оплаты?

G>Фоновому процессу надо где-то брать данные о той операции, что надо откатить\докатить, это и будет wal или аналог.

В каком-то смысле да, "где-то" их нужно брать. В большинстве случаев эти данные и так присутствуют в том сервисе, из которого инициируется операция; но иногда нужно добавлять в него дополнительную информацию для обеспечения идемпотентности.

G>Необходимости на уровне кода определять "были ли ошибка" конечно нет. Но код который откатывает\докатывает транзакцию все равно нужен.

Да, и он пишется и отлаживается примерно 1 раз на систему.

G>Так давайте на одном простом примере разберемся как оно должно быть? Я вот до сих пор не понимаю как сделать надежное резервирование заказов в рамках МСА с разделением сервисов на "заказ" и "склад", чтобы хотя бы по надежности не уступало одной базе. По трудозатратам однозначно в разы проиграет и по быстродействию\масштабируемости тоже.

Я вроде уже очень-очень-очень подробно расписал, как оно делается, чтобы по надёжности не уступать одной базе. По быстродействию там в среднем будет проигрыш; на максималках можно и выиграть.

S>>И пока там кто-то начисляет бонусы сотрудникам, остатки на складах начинают лагать

G>Ну нет конечно. readable реплики никто не отменял.
Readable реплики быстро превращаются в OLAP, а за ним паровозом едет ETL, и это то, что нынешние эксплуатанты делать не хотят.

G>А нагрузка на запись масштабируется дисками и партицированием.

Если бы всё было так просто, то никто бы не заморачивался никакой архитектурой, кроме монолита. Я согласен с тем, что монолит является предпочтительным решением для старта. И МСА зачастую выступает именно как оверкилл решение, в котором самая рациональная часть — это "если наш сервис не выстрелит, то response time в 100 миллисекунд вместо 5 никого не расстроит. А если выстрелит, то у нас не будет времени на вдумчивое партиционирование и настройку репликации. Народ повалит с реддита так, что нам надо будет за двое суток его отмасштабировать от 200 пользователей до двух миллионов; и если в МСА мы просто навалим туда ресурсов из амазона, то в монолите мы просто ляжем, и второй раз к нам никто не пойдёт".

G>Нет конечно. шарды это когда мы одну монолитную базу разделяем на несколько инстансов по ключу шардирования. По сути тоже самое партицирование, только с разнесением партиций между серверами.

G>Причём если при патицировании можно делить только нагруженные таблицы, то при шардировании придется делить всё. Да еще и придумывать механизм реплицирования изменений в общих таблицах.
Именно. А в микросервисах ничего реплицировать не надо: шардируем нагруженные таблицы, а общие слабонагруженные таблицы так и остаются там, где они остаются.
S>>Вот это утверждение я не понял. Вы собираетесь все микросервисы гонять на одном узле?
G>Чаще всего так и происходит, что система изначально построенная по МСА гоняется на одном сервере БД в разных базах, а по мере роста нагрузки поднимаются новые серваки. При этом потребность в масштабировании при МСА возникает гораздо раньше, чем в монолитной базе.
Да, в этом смысле вы совершенно правы. В целом, конечно, "высокоабстрактные" архитектуры (к коим относится и МСА) как раз позволяют лёгким движением руки поднять количество серверов со 100 до 1000, чтобы обработать нагрузку, с которой монолит бы справился и на 1 сервере

G>Если пропала связь в момент между "обновлены остатки на складе" и "обновлен статус заказа", то в МСА получаем неконсистентное состояние, которое еще и непонятно как откатывать\докатывать.

Нет там ничего непонятного. Мы данные для любой внешней операции берём не из эфемерного контекста, а из своей локальной БД.
Всё, это означает, что у нас цикл do res = try_reserve(...) while (is_transient_error(res)) прекрасно переживает перезапуск нашего приложения.

G>В случае монобазы мы получаем отмену транзакции и возврат к исходному состоянию будто ничего не было. Причем бесплатно с точки зрения трудозатрат.

Да, совершенно верно. А если нам совсем повезло, то вообще вся транзакция, включая пользовательскую переписку и обработку платежей, лежит в пределах этой одной монобазы, и мы можем делать кросс-сервисный ACID.
Жаль, что такое встречается исчезающе редко.

G>Фишка в том, что I в ACID говорит нам, что мы можем написать код, который предполагает что работает одна транзакция, она или выполнится до конца как будто она была одна в этот момент времени или откатится если такое не получилось.

А то. Но вы же помните, что эта буква работает на полную катушку только при уровне изоляции serializable?
Положа руку на сердце: какая часть транзакций в типовой разрабатываемой вами системе работает с таким уровнем?

G>Но когда мы границы транзакции в одной базе пересекаем никаких гарантий больше нет и все надо делать руками.

Да, что делает ценными специалистов, которые знают, как быть в таком случае

G>Это на один сценарий, где требуется изменение более чем в одной базе. На каждый сценарий будет свой код отката\доката, который вряд ли получится обобщить.

Специфичный для сценария код отката и доката легко встраивается в универсальный механизм state machine, который пишется и отлаживается один раз.

G>С точки зрения РЕСТ — да, а с точки зрения архитектуры — нет. На фронте нет никакой транзакционности и отката. Выполнение сценария на фронте может прекратиться в любой момент и не продолжиться вообще никогда, и это не является ошибкой.

G>Поэтому мы должны на стороне сервера делать откат\докат фоновым процессом.
Он не фоновый, этот процесс — основной. Взаимодействие с клиентом становится асинхронным: то есть я не "резервирую заказ", я ставлю задачу "зарезервируй заказ" в очередь. Очередь разгребается не тем же потоком, который был инициирован в ответ на мой запрос. И делается это в первую очередь для того, чтобы не надо было перезапускать всего клиента каждый раз, как связь с сервером моргнёт.


G>>>Приложения можно нарезать на десятки отдельных модулей и запускать отдельно: в отдельных процессах, в модулях одного процесса — как удобно. Все что написано выше для них верно.

S>>Чтобы приложения можно было нарезать, между ними должны быть какие-то границы.
G>Границы могут быть разными, в том числе чисто логическими.
G>Допустим есть прям монолитное приложение, которое весь код запускает в одном процессе. Никакого состояния между запросами в памяти процесса не хранится, все сохраняется в монобазу.
G>Можем ли мы горизонтально масштабировать приложение, запуская несколько экземпляров на разных серверах? Если нет, то почему?
Это сильно зависит от того, как это приложение было спроектировано. Большинство известных мне монолитных приложений крайне плохо относятся к такой идее. Ну, вот 1С: попробуйте запустить несколько экземпляров на разных серверах, подключив их к одной монобазе. Внезапно оказывается, что нужно прямо как-то заранее бить разработчиков палкой, чтобы они не делали каких-нибудь фоновых процессов, полагающихся на свою эксклюзивность, или client-side locks.
А когда мы таки научили приложение не рассчитывать на монопольный доступ к базе, то оказывается, что узкое место — вовсе не код в App Server (особенно если мы не стали писать его в стиле рич-DDD, а свели к нормальной анемик-модели). Поэтому дополнительные экземпляры начинают дольше простаивать в ожидании БД, и всё.
Дальнейшее распиливание, например на read-реплики и master-реплику потребует либо обучать пользователей "не запускайте этот отчёт в мастер-приложении, идите на специальный адрес, где можно делать отчёты. Но там нельзя резервировать заказы", либо допиливать приложение, чтобы оно умело ходить в разные базы за разными сценариями. То есть опять не получается решить вопрос без трудозатрат.


G>Зачем нам разные процессы?

Вы только что написали:

Приложения можно нарезать на десятки отдельных модулей и запускать отдельно: в отдельных процессах, в модулях одного процесса — как удобно.

G>В монолите я могу сделать rolling update в кластере кубера, обновляя приложение вообще без остановки обслуживания.
Это интересно. Я так не умею. Есть какой-то авторитетный источник, который описывает механизм этого чуда для невежд вроде меня?
Потому что мне непонятно, как это у нас делаются роллинг апдейты, задействующие схему базы, с учётом того, что она у нас в единственном экземпляре.

G>С фича-флагами я могу вообще разные версии "модуля ценообразования" запускать даже для разных пользователей.

G>С хорошей модульностью я могу в части экземпляров запускать часть модулей, а в других этого не делать.
G>Имхо не надо подстраивать архитектуру под инфраструктурные задачи.
Ну, может быть.

S>>И количество серверов СУБД в HA-кластере вас не спасёт от того, что апдейт схемы плохо совместим с data-level блокировками.

G>А как тут разделение поможет? Если вам нужен access exclusive на таблицу для обновления, то какая разница сколько всего у вас баз?
Большая. Эксклюзивность-то мне нужна не на все базы, а только на вот эту конкретную, в которой я меняю схему. А остальные как ехали, так и едут — их вообще это обновление не касается.

S>>То есть "на ходу" его провести возможно далеко не всегда; безопасный способ — переключать базу в монопольный режим.

G>не так уж много операций требуют монопольного режима для всей базы, а для отдельных таблиц разделение не поможет.
Смена схемы — и есть та операция, которая требует этого монопольного режима. К ней же относятся все эти партиционирования и прочие волшебные меры по оптимизации.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[30]: Помогите правильно спроектировать микросервисное при
От: · Великобритания  
Дата: 16.02.26 12:54
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>·>Ты упускаешь мою мысль, что этот код — не особенность МСА-реализации, а реальность бизнес-задачи.

G>Что означает "реальность бизнес-задачи" ?
G>Каким образом бизнес-задача приводит к нас к нескольким базам?
Реальные физические акторы физически разделены и физически невозможно обеспечить 100% надёжную коммуникацию между ними. Поэтому в CAP будет присутствовать компонента P, хочется тебе того или нет.

G>·>Просто в твоём монолитном решении ты игнорируешь эту проблему.

G>О какой проблеме идет речь?
"задачу, которая откатывает незавершенные резервы".

G>·>1. Пользователь создаёт заказ

G>·>2. Склад резервирует заказ
G>·>3. Пользователь завершает заказ
G>И что? Нам поэтому нужно сделать отдельные базы для Заказа, Склада и Пользователя, чтобы соответствовало существительным?
Не чтобы соответствовало существительным, а чтобы сабж.

G>А как в 1С конфигурации УНФ это все в одной базе существует? Оно как-то неправильно работает?

G>В 1С вообще это все решалось в рамках одной базы задолго до появления термина "микросервисы". В чем они неправы?
Хреново 1С работает. Не знаю как оно изменилось с тех пор, но раньше это был просто ад — все ломятся через RDP на один гигасервер и простейшие операции работают минуты. И если что-то где-то упало, то всё и сразу лежит.

G>·>И вот когда ты опишешь задачу так, то совершенно ВНЕЗАПНО получается, что "надо написать еще фоновую задачу, которая откатывает незавершенные резервы" в любом случае, неважно — МСА у тебя или монолит.

G>Важно. В монолите это все не нужно. Монобаза тебе обеспечивает атомарность, вообще всегда.
Если после 2-го пункта произойдёт какая-либо ошибка, как тебе поможет твоя атомаронсть?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[30]: Помогите правильно спроектировать микросервисное при
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.02.26 12:58
Оценка: +1
Здравствуйте, gandjustas, Вы писали:
G>В 1С вообще это все решалось в рамках одной базы задолго до появления термина "микросервисы". В чем они неправы?
Хм. "Задолго до появления" — это, я так понимаю, лет 10-15 тому? В 1с это решалось очень просто: ферма из 11 серверов с трудом тащила 100 одновременных пользователей.
Это как-то мало похоже на историю успешного масштабирования монолита.
Читаем https://forum.infostart.ru/forum67/topic154761/ и наслаждаемся.

G>·>И вот когда ты опишешь задачу так, то совершенно ВНЕЗАПНО получается, что "надо написать еще фоновую задачу, которая откатывает незавершенные резервы" в любом случае, неважно — МСА у тебя или монолит.

G>Важно. В монолите это все не нужно. Монобаза тебе обеспечивает атомарность, вообще всегда.
Ну давайте всё же ограничимся здравым смыслом. Емейл вам монобаза атомарно отправит? Деньги с карточки атомарно спишет в той же транзакции, что и резерв товара?
В партнёрский сервис заказ на доставку атомарно добавит?
Это я не к тому, что МСА решает все проблемы и делает волосы мягими и шелковистыми, а к тому, что преувеличивать способности монобазы тоже не стоит.
Если разработчик не владеет техникой интеграции упомянутых мной сценариев, то приложение так и не заработает даже в случае монолита.
А если владеет, то у него ровно те же технические решения будут работать и при интеграции со "своими" сервисами, будь они хоть микро-, хоть макро-.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[31]: Помогите правильно спроектировать микросервисное при
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 16.02.26 14:56
Оценка:
Здравствуйте, Sinclair, Вы писали:


S>>>МСА предлагает использование Representational State Transfer. Работает он так:

S>>>1. Заказ в базе Б находится в статусе "черновик".
S>>>2. По команде "зарезервировать заказ" заказ атомарно переходит в статус "резервируется". Из этого статуса его вручную вывести нельзя, и изменить состав заказа в этом статусе тоже нельзя.
S>>>3. Сервисом заказов делается попытка выполнить идемпотентную операцию "создать резерв" в сервисе А.
S>>>3.1. Если сервис А отвечает отказом, то сервис Б атомарно возвращает заказ в статус "черновик" и пишет в историю заказа "резервирование не удалось".
S>>>3.2. Если сервис А отвечает подтверждением, то сервис Б атомарно перемещает заказ в статус "зарезервировано", и для него становятся доступны следующие операции стейт-машины.
S>>>3.3. Если сервису Б не удается обработать ответ от сервиса А по любой из причин, перечисленных в моём предыдущем посте, то заказ остаётся в статусе "резервируется".
G>>И что будет если приложение упадет в пункте 3.2 между ответом А и изменением статуса заказа в Б?
S>Тут нам придётся немножко погадать о том, что такое "приложение" в этом сценарии.
У нас мобильное приложение или браузер, обращается через REST API к серверу, а сервер в свою очередь обращается к БД. Типичная трехзвенная архитектура.

S>Ну, допустим, это тонкий стейтлесс-клиент, всё "падение" которого — это браузер, который внезапно устал ждать разрешения пользователя на установку апдейта и самопроизвольно перезапустился.

Все проще, пользователь просто закрыл страницу когда не дождался ответа или интернет ему выключили. В любом случае сервер увидел потерю соединения и запустил отмену через CancellationToken, который из контроллера прокидывается во всю БЛ.
И это событие по счастливой случайности происходит ровно в момент между тем как А ответил подтверждением, а на сервер Б еще не отправлена команда на изменение статуса заказа.
Даже если мы эту отправку на серверы А и Б делаем на сервере, а не клиенте, то проблема никуда не девается. Процесс на сервере тоже также может упасть между.


S>Пользователь видит ту же страницу того же заказа, статус которого приложение перезапрашивает у Б через API и видит соответствующий статус "товары в резерве, произведите оплату до ЧЧ:ММ ДД.ММ.ГГГГ".

В описанном выше сценарии пользователь перезагрузивший страницу\приложение увидит что заказ все еще в статусе "резервируется".


G>>Я в принципе знаю правильный ответ: он называется WAL, а на более высоком уровне абстракции — transactional outbox. Данные для выполнения транзакции сначала атомарно пишутся в (одно, иначе атомарности не будет) долговременное хранилище, а потом пытаются примениться к конкретным таблицам.

S>Да, в некоторых случаях мы можем попытаться нарулить что-то вроде WAL. Но не всегда, т.к. под капотом у WAL — элементарные таблицы, для которых чётко и однозначно определены операции чтения и записи.
Что именно будет сохранено в WAL зависит в итоге от реализации хранилища, к которому мы будем этот самый WAL применять.

S>Это и даёт нам железобетонную уверенность в том, что после рестарта мы можем откатить неоконченные транзакции и накатить оконченные.

S>Когда мы проектируем в терминах сервисов, такой гарантии у нас в общем случае нет.
Вот мы и выяснили, что в БД есть WAL, что дает нам гарантию, а в МСА для гарантии надо свой WAL изобрести, и то не факт что получится, ибо там другие хранилища.

G>>>>Если код падает по между шагами 2 и 3, то в базе остается несогласованное состояние.

S>>>Несогласованное состояние в REST возникает только в тот момент, когда мы не получили ответ на запрос "распространить изменение". Причём мы знаем о том, что оно несогласованное — и можем принять меры, чтобы эта несогласованность не вышла нам боком.
G>>Несогласованное состояние в примере выше возникнет если заказ на складе будет забронирован в сервисе А, а на сервисе Б не изменен статус.
S>Вы говорите то же самое, что и я, немного другими словами. Потому, что "в сервисе Б не изменён статус" == "сервис Б не смог получить ответ на запрос о распространении изменения".
S>Как только он сможет получить этот ответ, статус заказа сразу же и изменится. Без каких-либо рукопашных действий со стороны пользователя или администратора.
От кого он этот ответ должен получить? Какой процесс должен сработать?


Мне кажется дальнейшее пока рано обсуждать, потому что мы пока не достигли единого понимания сценария фейла для "транзакции" из двух таблиц. Когда поймем эти сценарии и поймем как их можно компенсировать за счет кода можно обсуждать все остальное.
Re[31]: Помогите правильно спроектировать микросервисное при
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 16.02.26 15:06
Оценка:
Здравствуйте, ·, Вы писали:

·>Здравствуйте, gandjustas, Вы писали:


G>>·>Ты упускаешь мою мысль, что этот код — не особенность МСА-реализации, а реальность бизнес-задачи.

G>>Что означает "реальность бизнес-задачи" ?
G>>Каким образом бизнес-задача приводит к нас к нескольким базам?
·>Реальные физические акторы физически разделены и физически невозможно обеспечить 100% надёжную коммуникацию между ними.
Как связаны реальны акторы и информационная система?

·>Поэтому в CAP будет присутствовать компонента P, хочется тебе того или нет.

Как связаны реальные акторы и CAP?

G>>·>Просто в твоём монолитном решении ты игнорируешь эту проблему.

G>>О какой проблеме идет речь?
·>"задачу, которая откатывает незавершенные резервы".
Эта проблема решается транзакциями в БД.
Если ты уверен что нет, то наверное можешь показать пример как СУБД с ACID гарантиями не откатывает незавершенные транзакции.

G>>·>1. Пользователь создаёт заказ

G>>·>2. Склад резервирует заказ
G>>·>3. Пользователь завершает заказ
G>>И что? Нам поэтому нужно сделать отдельные базы для Заказа, Склада и Пользователя, чтобы соответствовало существительным?
·>Не чтобы соответствовало существительным, а чтобы сабж.
А зачем это? Самоцель сделать микросервисы? Потребителю пофиг сколько у вас сервисов.

G>>А как в 1С конфигурации УНФ это все в одной базе существует? Оно как-то неправильно работает?

G>>В 1С вообще это все решалось в рамках одной базы задолго до появления термина "микросервисы". В чем они неправы?
·>Хреново 1С работает. Не знаю как оно изменилось с тех пор, но раньше это был просто ад — все ломятся через RDP на один гигасервер и простейшие операции работают минуты. И если что-то где-то упало, то всё и сразу лежит.
Показывает уровень понимания 1С...
Давай проще, я в статье описал пример кода, который в одной базе сохраняет заказы и меняет остатки транзакционно https://habr.com/ru/articles/955714/ Без RDP работает.
Можешь прям в коде показать где там проблемы, которые я игнорирую?


G>>·>И вот когда ты опишешь задачу так, то совершенно ВНЕЗАПНО получается, что "надо написать еще фоновую задачу, которая откатывает незавершенные резервы" в любом случае, неважно — МСА у тебя или монолит.

G>>Важно. В монолите это все не нужно. Монобаза тебе обеспечивает атомарность, вообще всегда.
·>Если после 2-го пункта произойдёт какая-либо ошибка, как тебе поможет твоя атомаронсть?
Про какой второй пункт речь?
Re[31]: Помогите правильно спроектировать микросервисное при
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 16.02.26 16:19
Оценка:
Здравствуйте, Sinclair, Вы писали:


S>>>И пока там кто-то начисляет бонусы сотрудникам, остатки на складах начинают лагать

G>>Ну нет конечно. readable реплики никто не отменял.
S>Readable реплики быстро превращаются в OLAP, а за ним паровозом едет ETL, и это то, что нынешние эксплуатанты делать не хотят.
И в чем проблема? Поднимите реплику отдельную и делайте на ней olap.
Если лаг вообще не интересует и расчеты делаются по историческим данным, то можно в отдельный олап переливать данные по ночам (периоды низкой активности), из реплики.
Монобаза этому никак не мешает.

G>>А нагрузка на запись масштабируется дисками и партицированием.

S>Если бы всё было так просто, то никто бы не заморачивался никакой архитектурой, кроме монолита.
Мода влияет на архитектуру гораздо сильнее, чем любые технические факторы. Увы.
А есть еще и фактор управления большими командами.

S>Я согласен с тем, что монолит является предпочтительным решением для старта.

И для нальнейшего развития, пока команда может масштабироваться.

S>И МСА зачастую выступает именно как оверкилл решение, в котором самая рациональная часть — это "если наш сервис не выстрелит, то response time в 100 миллисекунд вместо 5 никого не расстроит. А если выстрелит, то у нас не будет времени на вдумчивое партиционирование и настройку репликации.

То есть ресурсы запилить МСА были, а репликацию и партицирование нет? Это как?
Репликация требует примерно ноль затрат на разработку, надо только написать селектор нужной реплика в зависимости от сценария.
Партицирование требует усилий чуть больше, но слава богу оно нужно далеко не сразу. Поэтому можно будет к нему плавно и осознано подойдти.
По сути для pg есть готовый модуль автопартицирования. Ему даешь в ход таблицу и ключи и он автоматм управляет всеми партициями. Правда входит этот модуль в PostgresPro Enterprise, но если вы дожили до необходимости партицирования, то денег на него хватит. Там еще будет и BiHa прилагаться, и мультмастер кластеры и много чего еще хорошего. И только когда ресурсы в одного мастера уже не будут закрывать все потребности, тогда можно и пробовать шардировать базу.




S>Народ повалит с реддита так, что нам надо будет за двое суток его отмасштабировать от 200 пользователей до двух миллионов; и если в МСА мы просто навалим туда ресурсов из амазона, то в монолите мы просто ляжем, и второй раз к нам никто не пойдёт".

А что мешает навалить ресурсов на монолит?
Я в яндексе могу постгрес поднять с 4 ядер до 96 в 4 клика. И также пропорционально прирастет память на сервере. До 2 млн активных пользователей выдержит я думаю.

G>>Нет конечно. шарды это когда мы одну монолитную базу разделяем на несколько инстансов по ключу шардирования. По сути тоже самое партицирование, только с разнесением партиций между серверами.

G>>Причём если при патицировании можно делить только нагруженные таблицы, то при шардировании придется делить всё. Да еще и придумывать механизм реплицирования изменений в общих таблицах.
S>Именно. А в микросервисах ничего реплицировать не надо: шардируем нагруженные таблицы, а общие слабонагруженные таблицы так и остаются там, где они остаются.
Так и без МСА тоже самое будет. Только ресурсов будет меньше жрать на то же самое количество пользователей.
А если дойдет до шардирования, то затраты на репликацию общих таблиц окажутся в разы меньше чем на обеспечение кросс-сервисной коммуникации.

S>>>Вот это утверждение я не понял. Вы собираетесь все микросервисы гонять на одном узле?

G>>Чаще всего так и происходит, что система изначально построенная по МСА гоняется на одном сервере БД в разных базах, а по мере роста нагрузки поднимаются новые серваки. При этом потребность в масштабировании при МСА возникает гораздо раньше, чем в монолитной базе.
S>Да, в этом смысле вы совершенно правы. В целом, конечно, "высокоабстрактные" архитектуры (к коим относится и МСА) как раз позволяют лёгким движением руки поднять количество серверов со 100 до 1000, чтобы обработать нагрузку, с которой монолит бы справился и на 1 сервере

G>>Фишка в том, что I в ACID говорит нам, что мы можем написать код, который предполагает что работает одна транзакция, она или выполнится до конца как будто она была одна в этот момент времени или откатится если такое не получилось.

S>А то. Но вы же помните, что эта буква работает на полную катушку только при уровне изоляции serializable?
S>Положа руку на сердце: какая часть транзакций в типовой разрабатываемой вами системе работает с таким уровнем?
Ровно та часть, которой это требуется. Мне к сожалению сейчас проект достался с МСА и кучей багов неконсистентности. За последние 3 месяца была пара сценариев, которые я непосредственно ревьювил, где нужен был уровень serializable, в остальных справлялись read committed.

G>>Но когда мы границы транзакции в одной базе пересекаем никаких гарантий больше нет и все надо делать руками.

S>Да, что делает ценными специалистов, которые знают, как быть в таком случае
По сути выход один — делать wal. Иногда его называют transactional outbox, что сути не меняет.


G>>Зачем нам разные процессы?

S>Вы только что написали:
S>

S>Приложения можно нарезать на десятки отдельных модулей и запускать отдельно: в отдельных процессах, в модулях одного процесса — как удобно.

Мы нарезаем на процессы вещи на связанные, оставляю слвязанную (транзакционную) логику в рамках одного.
Это же наше решение как делить систему на модули и как запускать их в отдельных процессах.

G>>В монолите я могу сделать rolling update в кластере кубера, обновляя приложение вообще без остановки обслуживания.

S>Это интересно. Я так не умею. Есть какой-то авторитетный источник, который описывает механизм этого чуда для невежд вроде меня?
https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/


S>Потому что мне непонятно, как это у нас делаются роллинг апдейты, задействующие схему базы, с учётом того, что она у нас в единственном экземпляре.

Достаточно придерживаться простых правил:
1) В схему только добавляем таблицы и колонки (кроме access exclusive на таблицу ничего не надо).
2) Удаляем таблицы колонки только если если они нигде не используются в проде (на момент создания ветки из мастера нет ни одной ссылки из БЛ). такие апдейты накатываем в периоды низкой активности.


S>>>И количество серверов СУБД в HA-кластере вас не спасёт от того, что апдейт схемы плохо совместим с data-level блокировками.

G>>А как тут разделение поможет? Если вам нужен access exclusive на таблицу для обновления, то какая разница сколько всего у вас баз?
S>Большая. Эксклюзивность-то мне нужна не на все базы, а только на вот эту конкретную, в которой я меняю схему. А остальные как ехали, так и едут — их вообще это обновление не касается.
Если миграции ограничить только добавлением колонок, таблиц и индексов, то лочить всю базу им не надо. Им надо лочить отдельные таблицы.

S>>>То есть "на ходу" его провести возможно далеко не всегда; безопасный способ — переключать базу в монопольный режим.

G>>не так уж много операций требуют монопольного режима для всей базы, а для отдельных таблиц разделение не поможет.
S>Смена схемы — и есть та операция, которая требует этого монопольного режима.
Монопольного режима к одной таблице, а не к базе целиком. Не надо путать.


S>К ней же относятся все эти партиционирования и прочие волшебные меры по оптимизации.

Партицирование делается один раз если что.
Re[32]: Помогите правильно спроектировать микросервисное при
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.02.26 16:51
Оценка: +1 :)
Здравствуйте, gandjustas, Вы писали:

G>У нас мобильное приложение или браузер, обращается через REST API к серверу, а сервер в свою очередь обращается к БД. Типичная трехзвенная архитектура.


G>Все проще, пользователь просто закрыл страницу когда не дождался ответа или интернет ему выключили. В любом случае сервер увидел потерю соединения и запустил отмену через CancellationToken, который из контроллера прокидывается во всю БЛ.

Нет конечно, сервер ничего не "увидел", и никакую "отмену" он не запустил.
Сервер может заметить потерю клиента только в тот момент, когда он начинает отправлять клиенту респонс — после того, как все транзакции уже уехали в durable log.
Наоборот делать ни в коем случае нельзя — иначе клиент получит ответ, который может исчезнуть после перезагрузки сервиса.
Более того — сервер может так и не увидеть отвала клиента даже после отправки ответа.
Например, между клиентом и сервером стоит прокси, который в любом случае вычитает ответ до конца и вежливо дождётся закрытия соединения со стороны сервера.

G>И это событие по счастливой случайности происходит ровно в момент между тем как А ответил подтверждением, а на сервер Б еще не отправлена команда на изменение статуса заказа.

Вы по-прежнему пишете какие-то непонятные мне вещи. Никакой "команды на изменение статуса заказа" на сервер Б не отправляется.
В этой архитектуре
  1. Пользователь на странице заказа нажимает кнопку "зарезервировать"
  2. Клиент отправляет в "сервис Б" команду
    PATCH /orders/42 
    
    {"status": "reserved"}

  3. Сервис Б в локальной базе делает
    update orders set status = "reserving" where id = 42

    и немедленно возвращает клиенту 202 Accepted
  4. Клиент, в зависимости от развитости чувства прекрасного у тех лида, либо делает регулярный polling этого ордера через GET /orders/42, либо идёт читать GET /orders/42/events.
  5. Тем временем город засыпает просыпается workflow-процесс, который делает select * from orders where status = "reserving" (с поправкой на engine-specific приседания для того, чтобы сделать аккуратное разгребание этой очереди без лишних блокировок и рейсов)
  6. Обнаружив в базе наш заказ с номером 42, он вычитывает его позиции, и делает PUT на адрес сервиса А:
    PUT /reservations/42/
    
    {
      "items": [ 
       {"product":17, "quantity":10},
       {"product":5, "quantity":1}
      ]
    }

    обратите внимание, что этот процесс вообще ничего не знает о клиенте и пользователе. Его работа — двигать workflow по предписанной траектории, даже если эта траектория пересекает произвольное количество вынужденных и добровольных рестартов любого из участников.
  7. Сервис А в ответ на этот запрос лезет в свою базу и ищет там reservation where id = 42.
    • Если находит, то проверяет, совпадают ли указанные в запросе продукты и количества с reservationItems where orderId = 42. Если совпали — то возвращает 200 Ok с ETag, вычисленным как хеш от всех продуктов и количеств. Если не совпали — возвращает 409 Conflict. (Тут на самом деле чуть сложнее, но в данном конкретном сценарии эту сложность можно пока проигнорировать)
    • Если не находит, то делает
      begin transaction;
      update stock 
        set reserved = reserved + 10,
            available = available - 10 
        where productId = 17;
      update stock 
        set reserved = reserved + 1,
            available = available - 1 
        where productId = 5;
       
      insert into reservations(orderId) values(42);
      insert into reservationItems(orderId, productId, quantity) values(42, 17, 10);
      insert into reservationItems(orderId, productId, quantity) values(42, 5, 1);
      commit transaction

      Эта радость может быть прервана constraint availability check(available >= 0) или ещё чем-нибудь — тогда сервис отдаёт обратно 4хх c понятным клиенту объяснением.
      Конкретный SQL тут не так важен — важна его атомарность. Если мы всё же довели транзакцию до конца и получили в сервисе А положительный ответ от СУБД, то мы в ответ на это отдаём сервису Б
      201 Object Created
      ETag: 0x213123123412312
      
      {
        "items": [ 
         {"product":17, "quantity":10},
         {"product":5, "quantity":1}
        ]
      }
  8. Сервис Б, дождавшись этого ответа, делает в своей базе
    update orders set status = "reserved", reservationETag = 0x213123123412312 where id = 42

  9. Если вдруг так получилось, что сервис Б так и не смог сделать эту запись (питание моргнуло до коммита в базу; сеть моргнула до того, как респонс доехал до сервиса Б), то тот же самый процесс перейдёт к обработке следующего заказа в этом статусе; а потом очередь снова дойдёт до заказа 42 и сервис Б пойдёт по кругу, пока не получит от сервиса А внятного ответа и не сможет запомнить этот ответ.
    Клиент в этом всём никакого участия не принимает: надёжность его связи с сервером на порядок хуже надёжности сети между сервисами А и Б.
  10. В зависимости от вкусов разработчика, упомянутых выше, та же транзакция, которая пишет в orders, может заодно писать и в orderEvents. Тогда клиент, если он ещё не ушёл, узнает об этом по появлению новых данных в GET /orders/42/events.
  11. Иначе, на очередном витке GET /orders/42, он увидит "status":"reserved" и покажет пользователю окошко для реквизитов оплаты.
G>Даже если мы эту отправку на серверы А и Б делаем на сервере, а не клиенте, то проблема никуда не девается. Процесс на сервере тоже также может упасть между.
Нет никаких "между".

G>В описанном выше сценарии пользователь перезагрузивший страницу\приложение увидит что заказ все еще в статусе "резервируется".

Можно и так написать, но зачем делать плохое решение, когда есть хорошее?

G>Что именно будет сохранено в WAL зависит в итоге от реализации хранилища, к которому мы будем этот самый WAL применять.

Я не знаю других вариантов устройства WAL, кроме как "данные до + данные после", т.к. опирается он на идемпотентность записи. Если у нас есть "хранилише", в которое можно делать идемпотентную запись И оно разрешает произвольное количество перезаписей туда/обратно, то можно сделать поверх него WAL. Иначе — нет.

G>От кого он этот ответ должен получить? Какой процесс должен сработать?

От сервиса А, к которому он обращается. Выше — пошаговый алгоритм.

G>Мне кажется дальнейшее пока рано обсуждать, потому что мы пока не достигли единого понимания сценария фейла для "транзакции" из двух таблиц. Когда поймем эти сценарии и поймем как их можно компенсировать за счет кода можно обсуждать все остальное.

Ок, давайте достигнем. Есть какие-то моменты, непонятные в схеме выше?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[33]: Помогите правильно спроектировать микросервисное при
От: TG  
Дата: 16.02.26 19:13
Оценка:
Здравствуйте, Sinclair, Вы писали:

G>>И это событие по счастливой случайности происходит ровно в момент между тем как А ответил подтверждением, а на сервер Б еще не отправлена команда на изменение статуса заказа.

S>Вы по-прежнему пишете какие-то непонятные мне вещи. Никакой "команды на изменение статуса заказа" на сервер Б не отправляется.

Видимо, gandjustas имеет в виду асинхронный способ взаимодействия между сервисами через брокеры.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.