Карьера аналитика
@analytics_career
Двухфазный коммит (2PC, Two-Phase Commit)
В рамках своих недавних задач появилась необходимость спроектировать систему (а точнее доработать существующую) используя принцип двухфазного коммита. Всё началось с того, что обнаружили проблему с несогласованностью данных (data inconsistency) в том процессе, когда мастер-система посылала запрос на сохранение определенных сущностей в две другие подсистемы. И с точки зрения бизнеса эти системы были обязаны либо обе сохранить свои сущности, либо откатить изменения, если хотя бы в одной из них что-то пошло не так.
Однако, у нас это не было предусмотрено, потому что изначально мы делали распределенную систему, в которой эти объекты не должны были зависеть друг от друга - но что-то пошло не так, как это часто бывает =)
Поэтому пришлось в спешке придумывать различные варианты решения данной проблемы, пока на проде не столкнулись с последствиями. Выбор был между SAGA-паттерном, оркестрацией или хореографией (об это я где-то давно рассказывал в канале) и двухфазным коммитом. Выбрали последнее, т.к. посчитали это более надежным решением, потому что SAGA-паттерн подразумевает следующее:
Мастер-система отправила событие в два топика подсистем о необходимости сохранить данные;
Одна из подсистем ответила успехом и отправила уже свои события своим потребителям, а вторая ответила ошибкой;
Мастер-система это обработала и отправила компенсационное сообщение в подсистему, которая ответила успехом;
Подсистема обработала, удалила\откатила изменения и отправила сообщение об откате транзакции своим потребителям.
И проблема подхода в нашем случае в том, что т.к. у нас безумная нагрузка и большое количество сообщений, то между 2 и 4 пунктом может пройти достаточное количество времени и всё это время у потребителей будет некорректная информация.
Поэтому от такого подхода отказались и пришли к 2PC.
Если упрощенно, то он работает следующим образом:
Мастер-система отправляет сначала события в подсистемы, начиная распределенную транзакцию. Подсистемы вычитывают эти сообщения, проверяют все бизнесовые валидации\штуки, которые должны проверить, но пока не публикуют результаты своих действий, держа эти события "в уме" (а по факту в отдельных табличках, в которых эти объекты хранятся до следующего шага);
Начинается Фаза Голосования: мастер-система отправляет команду-опрос, спрашивая: "Ребята, готовы ли вы окончательно закоммититься под этими сообщениям? Выполните все необходимые проверки\действия, чтобы в этом убедиться";
Мастер-система собирает ответы подсистем и приступает к Фазе Решения:
Если все участники ответили "Да", то мастер-система сохраняет к себе эти ответы (на случая какого-то сбоя, чтобы потом эту транзакцию можно было восстановить) и отправляет команду commit подсистемам;
Подсистемы окончательно переносят данные из временных хранилищ в основные таблицы и публикуют эти события, отправляя их своим потребителям. Также отвечают мастер-системе успехом, что данные закоммичены;
Если хотя бы один участник ответил "Нет", то мастер-система сохраняет к себе эти ответы и отправляет команду rollback тем подсистемам, которые ответили "Да".
Подсистемы откатывают изменения, удаляют данные из временных хранилищ.
У этого подхода есть свои недостатки, потому что достаточно сильно усложняется логика и увеличивается время обработки каждой транзакции. Однако, т.к приоритет был именно на обязательном одновременном сохранении транзакции в двух подсистемах, то пришлось этим жертвовать.
Вообще было очень интересно с разработчиками\архитекторами брейнштормить эту задачу, было придумано множество супер-интересных решений, которые тоже могли бы подойти, но в силу их минусов пришлось от них отказаться.
Я изначально предложил именно SAGA-паттерн, но потом посчитали нагрузку, потенциальные проблемы с неконсистентными данными у потребителей и продолжили штормовать эту тему, пока не пришли к единому варианту.
Приходилось ли вам на практике использовать какие-нибудь из перечисленных выше паттернов?
В рамках своих недавних задач появилась необходимость спроектировать систему (а точнее доработать существующую) используя принцип двухфазного коммита. Всё началось с того, что обнаружили проблему с несогласованностью данных (data inconsistency) в том процессе, когда мастер-система посылала запрос на сохранение определенных сущностей в две другие подсистемы. И с точки зрения бизнеса эти системы были обязаны либо обе сохранить свои сущности, либо откатить изменения, если хотя бы в одной из них что-то пошло не так.
Однако, у нас это не было предусмотрено, потому что изначально мы делали распределенную систему, в которой эти объекты не должны были зависеть друг от друга - но что-то пошло не так, как это часто бывает =)
Поэтому пришлось в спешке придумывать различные варианты решения данной проблемы, пока на проде не столкнулись с последствиями. Выбор был между SAGA-паттерном, оркестрацией или хореографией (об это я где-то давно рассказывал в канале) и двухфазным коммитом. Выбрали последнее, т.к. посчитали это более надежным решением, потому что SAGA-паттерн подразумевает следующее:
Мастер-система отправила событие в два топика подсистем о необходимости сохранить данные;
Одна из подсистем ответила успехом и отправила уже свои события своим потребителям, а вторая ответила ошибкой;
Мастер-система это обработала и отправила компенсационное сообщение в подсистему, которая ответила успехом;
Подсистема обработала, удалила\откатила изменения и отправила сообщение об откате транзакции своим потребителям.
И проблема подхода в нашем случае в том, что т.к. у нас безумная нагрузка и большое количество сообщений, то между 2 и 4 пунктом может пройти достаточное количество времени и всё это время у потребителей будет некорректная информация.
Поэтому от такого подхода отказались и пришли к 2PC.
Если упрощенно, то он работает следующим образом:
Мастер-система отправляет сначала события в подсистемы, начиная распределенную транзакцию. Подсистемы вычитывают эти сообщения, проверяют все бизнесовые валидации\штуки, которые должны проверить, но пока не публикуют результаты своих действий, держа эти события "в уме" (а по факту в отдельных табличках, в которых эти объекты хранятся до следующего шага);
Начинается Фаза Голосования: мастер-система отправляет команду-опрос, спрашивая: "Ребята, готовы ли вы окончательно закоммититься под этими сообщениям? Выполните все необходимые проверки\действия, чтобы в этом убедиться";
Мастер-система собирает ответы подсистем и приступает к Фазе Решения:
Если все участники ответили "Да", то мастер-система сохраняет к себе эти ответы (на случая какого-то сбоя, чтобы потом эту транзакцию можно было восстановить) и отправляет команду commit подсистемам;
Подсистемы окончательно переносят данные из временных хранилищ в основные таблицы и публикуют эти события, отправляя их своим потребителям. Также отвечают мастер-системе успехом, что данные закоммичены;
Если хотя бы один участник ответил "Нет", то мастер-система сохраняет к себе эти ответы и отправляет команду rollback тем подсистемам, которые ответили "Да".
Подсистемы откатывают изменения, удаляют данные из временных хранилищ.
У этого подхода есть свои недостатки, потому что достаточно сильно усложняется логика и увеличивается время обработки каждой транзакции. Однако, т.к приоритет был именно на обязательном одновременном сохранении транзакции в двух подсистемах, то пришлось этим жертвовать.
Вообще было очень интересно с разработчиками\архитекторами брейнштормить эту задачу, было придумано множество супер-интересных решений, которые тоже могли бы подойти, но в силу их минусов пришлось от них отказаться.
Я изначально предложил именно SAGA-паттерн, но потом посчитали нагрузку, потенциальные проблемы с неконсистентными данными у потребителей и продолжили штормовать эту тему, пока не пришли к единому варианту.
Приходилось ли вам на практике использовать какие-нибудь из перечисленных выше паттернов?
🔥 15
❤ 9
👍 5
5 15 2K