Скорее всего - да, второй вариант: можно положиться на менеджер транзакций Кафки, т.к. внутри Кафки есть ACID-гарантии при работе с несколькими топиками.
Пользу описанного здесь подхода я вкратце вижу так: получить хорошую защиту от неконсистентности за небольшую плату.
Плата состоит в том, что вы настраиваете transaction manager'ы и развешиваете @Transactional - это несложно. А в результате получаете, что ошибки в прикладной логике не ломают консистентность вашего приложения - а это основная доля ошибок/сбоев в приложениях. Остается непокрытым риск системных проблем - электричество выключилось, сеть отпала и т.д., но это маловероятные риски, тем более, что для поломки консистентности они должны произойти в очень маленький промежуток времени между коммитами транзакции БД и Кафки.
Суммарно, допустим, вы получаете 99,99% гарантии консистентности при небольших трудозатратах - очень неплохо, особенно по сравнению с полным отсутствием контроля )
Transactional outbox + idempotent consumer - это более существенные трудозатраты, хотя конечно не rocket science. Про eventual consistency планирую сделать отдельную статью.
Хранить consumer offsets в потребителе (и управлять ими в своем прикладном коде) - это значит брать на себя реализацию существенного объема функционала, который уже реализован в Кафке. Амбициозная задача.
Почему же странный ? "Получить сообщение + обновить БД" - это элементарный (но при этом вполне реальный) сценарий, на котором проще всего исследовать проблему. И он конечно не специфичен для BPM.
"Прочитать + обновить БД + отправить" - это просто чуть более сложный сценарий, и при этом можно усложнять и далее, комбинируя всё больше взаимодействий - но это вряд ли что-то добавит к сути статьи, скорее наоборот - замаскирует её за второстепенными деталями. То же самое на мой взгляд относится и к retries и dead letters - поддержка всего этого есть в Spring for Apache Kafka, но это выходит за рамки статьи, это вопросы, достойные отдельного рассмотрения. Тем более достойна отдельного рассмотрения тема с "остановкой consumer'а", т.к. тут возникнет куча вопросов с ребалансировкой, порядком обработки сообщений и т.д.
В целом, я считаю, в небольшой статье лучше сфокусироваться на одном вопросе и рассмотреть какой-то простейший (но живой) случай, чтобы понять принцип, и уже дальше этот принцип (с соответствующими расширениями и дополнениями, конечно) использовать в более сложных сценариях.
Если я правильно понимаю, то вы описываете такую ситуацию:
Начата транзакция Kafka
Начата транзакция БД
Вычитали сообщение из кафки
Обновили данные в БД
Коммит транзакции БД
<<выключилось электричество>>
<<сообщение из кафки не вычитано (т.е. оффсет не закомитчен) >>
<<после включения электричества то же самое сообщение попадет на повторную обработку>>
Если так, то да, система останется в неконсистентном состоянии, т.к. данные в БД уже обновлены, а то же самое сообщение в кафке пойдет на повторную обработку. Именно про это я и пишу и именно эту ситуацию а моделирую через параметр receive-transactions-faults-num (задайте его > 0 и увидите этот эффект.)
Забегая вперед, скажу, что лечить эту конкретную проблему можно например таким образом: запоминать в БД ключи уже обработанных сообщений и отбрасывать их при повторном получении - паттерн Idempotent Consumer (https://microservices.io/patterns/communication-style/idempotent-consumer.html). Но это усложнение решения и это точно out of scope данной статьи. Если все сложится хорошо - опишем это в отдельной статье.
Согласен, как только заказчик решает, что ему нужно что-то, чего нет в коробке - low code начинает превращаться в lot of code. С этим можно бороться только одним способом - убеждением заказчика отказаться от нестандартного функционала - и на практике это далеко не всегда получается.
Что касается lock in на подрядчика - не совсем понимаю, что вы имеете в виду. Все доработки делаются в рамках проекта, они полностью открыты заказчику, и он (т.е. его IT) при желании могут ими полностью владеть.
В соседней теме задавались вопросом - почему так экстренно и втихую приняли закон о просмотре запрещенки. Вот поэтому.
Спасибо, у нас есть.
Хорошая тема для исследования и статьи на Хабре )
Что касается деталей реализации, то есть большой дизайн-документ на эту тему - "Exactly Once Delivery and Transactional Messaging in Kafka", https://docs.google.com/document/d/11Jqy_GjUGtdXJK94XGsEIK7CP1SnQGdp2eF0wSw9ra8/edit#heading=h.xq0ee1vnpz4o
Скорее всего - да, второй вариант: можно положиться на менеджер транзакций Кафки, т.к. внутри Кафки есть ACID-гарантии при работе с несколькими топиками.
Хороший вопрос )
Пользу описанного здесь подхода я вкратце вижу так: получить хорошую защиту от неконсистентности за небольшую плату.
Плата состоит в том, что вы настраиваете transaction manager'ы и развешиваете @Transactional - это несложно. А в результате получаете, что ошибки в прикладной логике не ломают консистентность вашего приложения - а это основная доля ошибок/сбоев в приложениях. Остается непокрытым риск системных проблем - электричество выключилось, сеть отпала и т.д., но это маловероятные риски, тем более, что для поломки консистентности они должны произойти в очень маленький промежуток времени между коммитами транзакции БД и Кафки.
Суммарно, допустим, вы получаете 99,99% гарантии консистентности при небольших трудозатратах - очень неплохо, особенно по сравнению с полным отсутствием контроля )
Transactional outbox + idempotent consumer - это более существенные трудозатраты, хотя конечно не rocket science. Про eventual consistency планирую сделать отдельную статью.
Хранить consumer offsets в потребителе (и управлять ими в своем прикладном коде) - это значит брать на себя реализацию существенного объема функционала, который уже реализован в Кафке. Амбициозная задача.
Почему же странный ? "Получить сообщение + обновить БД" - это элементарный (но при этом вполне реальный) сценарий, на котором проще всего исследовать проблему. И он конечно не специфичен для BPM.
"Прочитать + обновить БД + отправить" - это просто чуть более сложный сценарий, и при этом можно усложнять и далее, комбинируя всё больше взаимодействий - но это вряд ли что-то добавит к сути статьи, скорее наоборот - замаскирует её за второстепенными деталями. То же самое на мой взгляд относится и к retries и dead letters - поддержка всего этого есть в Spring for Apache Kafka, но это выходит за рамки статьи, это вопросы, достойные отдельного рассмотрения. Тем более достойна отдельного рассмотрения тема с "остановкой consumer'а", т.к. тут возникнет куча вопросов с ребалансировкой, порядком обработки сообщений и т.д.
В целом, я считаю, в небольшой статье лучше сфокусироваться на одном вопросе и рассмотреть какой-то простейший (но живой) случай, чтобы понять принцип, и уже дальше этот принцип (с соответствующими расширениями и дополнениями, конечно) использовать в более сложных сценариях.
Если я правильно понимаю, то вы описываете такую ситуацию:
Начата транзакция Kafka
Начата транзакция БД
Вычитали сообщение из кафки
Обновили данные в БД
Коммит транзакции БД
<<выключилось электричество>>
<<сообщение из кафки не вычитано (т.е. оффсет не закомитчен) >>
<<после включения электричества то же самое сообщение попадет на повторную обработку>>
Если так, то да, система останется в неконсистентном состоянии, т.к. данные в БД уже обновлены, а то же самое сообщение в кафке пойдет на повторную обработку. Именно про это я и пишу и именно эту ситуацию а моделирую через параметр receive-transactions-faults-num (задайте его > 0 и увидите этот эффект.)
Забегая вперед, скажу, что лечить эту конкретную проблему можно например таким образом: запоминать в БД ключи уже обработанных сообщений и отбрасывать их при повторном получении - паттерн Idempotent Consumer (https://microservices.io/patterns/communication-style/idempotent-consumer.html). Но это усложнение решения и это точно out of scope данной статьи. Если все сложится хорошо - опишем это в отдельной статье.
Согласен, как только заказчик решает, что ему нужно что-то, чего нет в коробке - low code начинает превращаться в lot of code. С этим можно бороться только одним способом - убеждением заказчика отказаться от нестандартного функционала - и на практике это далеко не всегда получается.
Что касается lock in на подрядчика - не совсем понимаю, что вы имеете в виду. Все доработки делаются в рамках проекта, они полностью открыты заказчику, и он (т.е. его IT) при желании могут ими полностью владеть.