$10.000 за хвилину даунтайму: архітектура, черги та стрімінг у фінтех

Привіт, мене звати Макс Багінський, я Head of Engineering у Solidgate — українській продуктовій фінтех-компанії, що працює в ніші пейментів і допомагає розбудовувати платіжну інфраструктуру для інтернет-бізнесів.

Кожного місяця інфраструктура Solidgate процесить понад 18 мільйонів транзакцій. Відтак $10.000 за хвилину даунтайму — це не просто цифра для маркетингової презентації. Це — реальна вартість, яку можуть втратити наші клієнти за усього лише хвилину падіння Solidgate.

Робота нашої платіжної інфраструктури здається кінцевим споживачам доволі простою: юзер платить за таксі, доставку тощо у застосунку, кошти потрапляють на рахунок компанії, юзеру отримує повідомлення про успішний платіж та послугу. Саме тому, коли ми лише планували розробку нашого нового проєкту Taxer, його структура виглядала також просто:

Водночас коли занурюєшся у роботу інфраструктури, стається багато «якщо»:

  • Що, якщо під час замовлення таксі у клієнта не запроцеситься оплата?
    Він запізниться на бізнес-зустріч та отримає зіпсований день.
  • Що, якщо між системами втратиться транзакція?
    Драйвер або кур’єр не отримає потрібні виплати й втратить зароблені кошти.

Таких «що, якщо» можна придумати безліч. А відтак стабільність роботи системи — ключове для платіжної інфраструктури.

RabbitMQ

Ми давно використовуємо Rabbit у своїх системах, маємо з ним найбільше досвіду і планували використати його в цьому проєкті. І хоч Durability, найкритичніший показник для фінтеху, у Rabbit не ідеальний, ми все ж спробували його в роботі з декількох причин:

​📌 Erlang. Мова програмування, створена телеком-компанією Ericsson. Вона базується на тому, що обладнання ніколи не перезавантажується — це дозволяє системам працювати постійно і підтримувати роботу 24/7.

📌 Proof of fail-safety. Десятки років Erlang може працювати без жодного падіння — це емпіричний досвід роботи девайсів цією мовою впродовж такого періоду. Приклад: ATM AXD301, Uptime 99,9999999% тощо.

📌 Mnesia. Це розподілена система керування базами даних для Erlang. Недоліки — вона не підтримує відновлення у випадку зі Split brain або іншими типами падінь.

Durability RabbitMQ має різні механізми. Серед них publisher confirms, зберігання даних на диску, різні типи черг і стрімінг. Але чому взагалі важливо про це думати?

По-перше, якщо паблішити меседжі в Rabbit без publisher confirms, вони можуть втрачатись у сотнях місць — на це впливає нетворк, нестача місця для збереження даних на диску, або ж банальна відсутність черги.

Проігнорувати publisher confirms можна. Але в такому випадку RabbitMQ не гарантує:

  • доставлення до черги після Exchange;
  • запис на диск для Durable-черг;
  • confirmation всіма репліками в випадку Quorum черг;
  • місце в черзі або на диску та збереження меседжу;
  • коректну обробку спайків за навантаженням, через що консьюмери не встигають обробити дані та паблішер забиває чергу або ж зовсім їх скіпає.

Кворумні черги в Rabbit — доволі новий механізм. Вони вміють реплікувати меседжі по різних нодах і зберігати дані навіть у випадку падіння однієї з нод RabbitMQ. Але в таких чергах є і недоліки — по-перше, вони все ще не вміють розсилати повідомлення різним клієнтам. По-друге, вони не здатні добре масштабуватися — оскільки у нас ноди можуть мати понад мільйон меседжів, падіння і відновлення однієї ноди може займати години. По-третє, кворумні черги все ще не підходять під декількох консьюмерів. Вони не зберігають порядок повідомлень, а нам це дуже критично для проведення платежів.

Split Brain

Попри всі переваги, які має RabbitMQ завдяки fail-safe мові Erlang, інциденти стаються — наприклад, у нас був випадок Split Brain. Split Brain — це стан кластера серверів, де вузли розходяться один з одним і мають IO-конфлікти. Фактично паблішер пише в один кластер, а читач читає з іншого. При цьому паблішер та читач навіть не підозрюють, що не отримують меседжі один одного.

Чи є в RabbitMQ стратегії вирішення Split Brain? Так, проте вони точно не пасують до вимогливої фінтех-індустрії. Чому?

Ignore. Стратегія, в якій система нічого не робить у випадку Split Brain. Infrastructure-інженер вручну рестартить певні ноди, які він вважає неосновними. Чому це проблема — не відбудеться автоматичного відновлення зв‘язку, а інженери самостійно обиратимуть мастер-ноду і будуть перезапускати інші ноди. Якщо черги не кворумні, то welcome to втрата даних. Якщо ж черги існували довгий час в такому стані, то втрата даних гарантована, оскільки повідомлення ніяк не можуть потрапити на обидва кластери.

Pause_minority. Цей інструмент автоматично відстежує, коли щось стається із нетворком, і пропонує рестартнути певні ноди. Проте як і в першому випадку, він не вміє відновлювати меседжі, а значить ми знову ризикуємо втратити важливі дані, і навіть більшу їх кількість.

Autoheal. Автоматичний рестарт нод. Проблема — система може обрати не ту ноду, на яку опублікували найбільше меседжів, і перезапустити ноди, які не мають даних або мають їх менше за інші. Хоча в такому варіанті рестарт нод відбудеться швидше, все ж це не розв’язує суть проблеми. Autoheal більше підходить для відновлення роботи кластеру замість консистентності даних.

Як результат — жодної гарантії збереження даних і коректного відновлення роботи нод.

Ось приклад логів при виникненні Split brain в RabbitMQ.

Streams

Так, в RabbitMQ є стріми. Вони вийшли 2 роки тому — у 2022. Здавалося б, це ідеальний інструмент — у нас є експертиза роботи з Rabbit і потреба реалізації нового проєкту, а стріми можуть забезпечити порядок доставлення та дати можливість партішінінгу. Але після детального аналізу ми їх відкинули. Що ж з ними не так?

  • Сирий функціонал, який досі мало протестований.
  • Відсутність великої кількості компаній, які регулярно працюють зі стрімами.
  • Напівготовий гошний клієнт (а насправді — зовсім не готовий, навіть у 2024-му).
  • Складний сапорт, потреба в оновленні Erlang і лише потім сам RabbitMQ.
  • Все ще RabbitMQ, що потребує правильної підготовки та підтримки, який до того ж не зовсім Durable.

То як це вирішити?

Kafka

Попри те, що наша команда мала найбільше знань саме із RabbitMQ, недоліки були занадто критичними, аби будувати новий і важливий продукт. Так ми звернулись до Kafka і почали шукати рішення, які можна використати в побудові інженерної частини нового продукту.

На відміну від RabbitMQ, Kafka — durable out of the box. Що робить її такою?

  • Zookeeper — на відміну від RabbitMQ, Kafka має інтегровану сторонню, зовнішню систему, що слідкує за здоров’ям кластерів.
  • Kafka пише лог на диск по всіх змінах.
  • Heavy usage of hard drive — при цьому швидкий, майже Sequential writes механізм.
  • Оптимізована і має в сотні разів кращу швидкість запису даних на диск, що наближає її до швидкості доступу до оперативної пам’яті.

Як не втрачати дані

На етапі, коли ми вже попрацювали з Kafka, перевірили роботу стрімів, нод, відключали зв’язок між нодами та переконались, що все працює, постало питання — як налаштовувати роботу системи так, щоб не втрачати дані? Часткова відповідь — WAL, але ж як не втрачати дані в записі?

Гіпотетичні ситуації — що буде, якщо паблішер, який пише в Kafka, перезапуститься? Що буде, якщо ми асинхронно відправлятимемо меседжі в Kafka та отримаємо out of memory? Що буде, якщо метеорит прилетить в дата-центр? Що буде якщо ж ми просто невірно налаштуємо сам клієнт Kafka? Відповідь — ми втратимо дані.

Ось приклад конкретно наших налаштувань Publisher.

З важливого — виставляємо мінімальний BatchTimeout і обов’язково RequiredAcks — система, що «вимагає» від нод підтвердження про отримання даних. Для фінтеху, RequiredAcks — це must-have.

Дані можна втратити ще до запису в Kafka

Для забезпечення стабільності пеймент-процесору та Kafka ми використали патерн Transactional Outbox. Він надає найбільш гнучкий механізм реплею (перевідправки) даних у разі збою, неправильного стану бази даних тощо. Наявність такого логу дозволяє набагато швидше і точніше перестрімити дані в будь-якій із систем. Як ми це організували?

За допомогою Transactional Outbox можемо стрімити дані за допомогою декількох стрімів у різних бакетах. Це дозволяє працювати з великими об’ємами даних у постгресі. У id ми використовуємо ulid — його відрізняє здатність до сортування, що дозволяє відновлювати порядок івентів.

Система, яку ми створили

Врахувавши всі плюси та мінуси систем вище, ми створили наступну архітектуру:

Payment Gate → 2 таблиці з даними → Order Streamer, що читає таблиці → апдейтить метадані → стрімить і записує їх у Kafka.

Стрімер ми розробили самі — це окремий сервіс, майже як Debezium або Kafka Source. Навіщо написали своє? Відповідь проста — стрімер може працювати як бібліотека. Тобто це не окремий сервіс, який треба деплоїти в інфру, а просто бібліотека, що запускається в горутині або треді, вичитує дані із бази даних і може під‘єднуватись до будь-якої структури бази даних або навіть outbox-таблиці, і публікує ці дані в стрім. Так, замість наявних і складних рішень (Debezium), ми написали свій CDC і вмістили його у 200 рядків коду.

Є стандартизоване рішення, яке може бути створене на основі debezium, scheme register, та головне питання у якому — робота з реплікацією даних. Ми ж побудували власне. Дефолтне, як видно нижче, виглядає набагато складніше.

Плюси

Повний лог всіх платежів і порядок їхнього оновлення.

З додаткових систем додається лише Kafka, яка використовується як «транспорт».

Написання стрімеру з лізером. 100-200 строк коду + 500-1000 строк тестів = можливість створювати та керувати стрімами як заманеться, без втручання в DevOps чи підняття нових систем. Стрімінг може відбуватися напряму з сервісу і жити в одній кодовій базі з апкою. Відповідно еволюція апі та стріма відбувається синхронно і не створює проблем у версіонуванні чи релізах.

Buf/Go/DB. Всі системи повторно використовуються й в архітектурі нічого не треба додавати, окрім Kafka.

Мінуси

WAL amplification — 2x. Outbox-таблиця подвоює кількість записів у WAL, тому що її потрібно наповнювати окремо.

Latency — неможливість вичитувати івенти швидко.. Через використання ulid є проблема з комітами транзакцій і резервування ULID, коміти транзакції відбуваються в різний час.

Стрімінг апдейтів з Outbox, на відміну від отримання з WAL, дорожчий.

Але є проблема

Оновлення підписок на продукти потрібно робити швидше. Поточний delay — 2 хвилини, що є проблемою для списань за підпискою, які ми згодом підсадили на стріми. Тому відразу ж почали брейнштормити — як все ж таки прибрати ліміт на час. Де ж проблема?

Записуючи дані в outbox, транзакції закінчуються в різний момент часу. Тобто транзакція, що почалась першою (№ 1) може закінчитись пізніше за транзакцію, що почалась другою (№ 2). Це означає, що вичитувати дані з outbox-таблиці треба з затримкою — дефолтний delay для нашого рішення = 2 хвилини.

Щоб зменшити таймаут вичитки з outbox є банальне рішення — Lamport’s logical clock. Складний термін, який працює на векторах і векторній математиці. Проте ми реалізували простішу його версію — замість ulid взяли auto_increment і саме його використовуємо для ордерингу івентів в Outbox table. Чому? Саме так можна перевірити, що якийсь з івентів був пропущений.

Якщо івенти з id — 1,2,4 — прийшли, то очевидно, що id 3 треба або почекати й дострімити, або ж третя транзакція була роллбекнута та її з часом можна просто скіпнути. У будь-якому випадку, можна простежити рух івентів, дочекатися їх і відновити порядок.

Streamer future ideas

А чи можна зробити ще краще? Звичайно. І доволі легко. А чому б не читати не власний читач WAL напряму, тобто чому б не повторити імплементацію Debezium на Go?

Ось приклад коду, який читає з WAL з Go. Замість debezium можна зробити легковійну бібліотеку у ті самі 200 строк коду і читати з WAL log самого постгресу.

Це доволі банальний шматок коду, який опрацьовує меседжі з WAL-реплікації, які PostgreSQL надсилає лістенерам, прив‘язаним до реплікейшн слотів. Це не складно і, можливо, для цього не потрібен Debezium чи Kafka sink на Java. Слоти реплікації можна автоматично створювати з коду. Але це вже матеріал для наступної статті.

Висновок

Фінтех — не лише вибаглива до технологій ніша, але й цікава своїми non-functional requirements, які вона накладає:

  • Durability.
  • Queue replay.
  • Strong consistency.

Звичні всім нам рішення при проєктуванні відразу піддаються більш скрупульозним перевіркам, хаос-інжинірінгу та читанню сорсів. На прикладі розкладання RabbitMQ, який by design — fail safe, а на практиці не забезпечує потрібної Durability.

Kafka в цьому випадку працює краще — вона дизайнилась, виходячи з проблем Distributed-систем, тож у неї закладались різного роду механізми консенсусу та орієнтованість на використання жорсткого диска.

Детально попрацювавши з обома системами, ми зробили такі висновки:

RabbitMQ:

  • Дуже швидкий і «легкий» (якщо ви знаєте, як з ним працювати).
  • Не підходить для фінтеху: відсутність належної надійності, single active consumer, message ordering тощо.
  • Відсутність підтримки більшості функцій для Go/Python/Node.js, різні клієнти мають різні можливості.
  • Відсутність підтримки стрімінгу.
  • Написаний на Erlang і оновлюється в реальному часі без перезапуску.

Kafka:

  • Надійний з коробки. Можна почитати тут.
  • Дозволяє реплеїти месседжі шляхом оффсетів.
  • Має вбудовані механізми роботи з Schema registry.
  • Краще скейлиться.
  • Важчий в експлуатації, вимагає багато конфігурацій з обох сторін: інфраструктури та коду.
  • Написаний на Java, вимагає перезапуску для оновлення версії.
👍ПодобаєтьсяСподобалось13
До обраногоВ обраному13
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

також цікаво на рахунок перформансу. Зараз у вас 7 RPS, і ви очікуєте, якщо я не помиляюся, 1,2k RPS. В мене RabbitMQ на кластері з 3 нод (8CPU and 32GR RAM each) витримує ~3k RPS (each message ~50kb) з processing latency < 100ms (95 percentile), durable messages (some of them direct some of them routed). Було б цікаво почути який ви робили перформенс тести що Ви вирішили що RabbitMQ не справиться

дякую за цікаву статтю.

А не могли б ви детальніше пройтися по наступним пунктам?

> Сирий функціонал, який досі мало протестований.
цікаво було би почитати деталі

> Все ще RabbitMQ, що потребує правильної підготовки та підтримки, який до того ж не зовсім Durable
цікаво почути деталі і тут. особливо про Durable

p.s.І чому ви думаєте що підтримувати і правильно настроїти Kafka буде легше/проще чим RabbitMQ?

p.p.s цікаво чи розглядали ви Quorum queues як інструмент боротьби з split brain ?

ну завжди е managed kafka
не то щоб це прямо железобетон ( зловити under replicated partitions цілком можливо )

зі статті я чомусь зрозумів що вони розглядають self-managed

Десять доларів за хвилину? Ого це ж 600 баксів за годину... Це звичайно багато, але не настільки, щоб хизуватися на dou.ua.

$10.000

Це в якій локалі написано?

Насправді 18 млн на місяць то дуже мало для фін системи. В банку з 2 млн юзерів в середньому маємо 6 млн транзакцій в день.

Стаття прикольна, але наведені приклади з erlangon, якісь надто поверхневі. Будь яка сучасна архітектура дає 99 для одного сервіса, а далі просто маштабуєш до досягнення 99.ххх скільки треба.

Проблема downtime легко вирішується blue/green релізами
А от знайти erlang про, замість Java, python, .net це прям ніфігова задача. Та і я б навряд погодився проміняти жабу з пітоном на щось нішеве як програміст. Дивно що це не враховано при побудові системи

проблема с erlang це найм — де брати людей, навідь консультантів якщо що хрен знайдеш

От тут Ви «підсвітили» головну проблему нашого найму...
І це проблема в тому, як що відбувається... В тому, що ті, хто виписує вимоги, реально теми не уявляють...

Мови програмування — це лише інструмент.

І смішно виглядало би, коли б, наприклад, в мебельний цех шукали би «сеніор майстра по роботі з рубанком з досвідом 10+», чи на будівництво — «мастера роботи з болгаркою...»

Якось так на будівництво шукають «каменщика», «плиточника», «муляра-штукатура» — тобто релевантність до того, що буде робитися...

Так має бути і в нас. Data engineer, Content engineer, Service engineer...

Так. Наявність в нас Data scientist спровокувало формалізацію у Data engineer...
Але прихильники неправильних підходів зразу до того домалювали python.

Звісно, кам’янщик може працювати лише долотом, а будь-яку меблю можна зробити сокирою...
...але так не роблять. Чому? Бо часто-густо є більш еффективні інструменти....

І от, коли б дата-інженери знали би erlang і, головне, його підходи — проблем на проектах було б в рази менше... Наприклад, я можу стверджувати, що AirFlow розробляли люди, котрі дуже глибко знають erlang.

Загалом, erlang розробляли телефоністи і тому його підходи — основа підходів усіх брокерів as is.

Часто-густо треба щось зі «статичною типизацією», чи, наприклад, для якої рекурсії оптимальніше буде використати шось з нащадків lisp типу clojure...

Звісно, наявність «кодерів» добре полегшує творчу роботу інженерів-програмістів... :)
Типу:
--- намалюй мені двадцять от таких пайпів — звідси і досюди — а я іх закомутую як треба...

І смішно виглядало би, коли б, наприклад, в мебельний цех шукали би «сеніор майстра по роботі з рубанком з досвідом 10+», чи на будівництво — «мастера роботи з болгаркою...»

Якось так на будівництво шукають «каменщика», «плиточника», «муляра-штукатура» — тобто релевантність до того, що буде робитися...

смішна історія но на будівництві шукають конкретно спеціалістів конкретно по технологіях які конкретно маються на увазі під тіма «камєнщік маляр штукатура» бо та самі «релевантність що буде робитися» вона якраз передбачає роботу з технологіями які є тут і зараз

Звісно, кам’янщик може працювати лише долотом, а будь-яку меблю можна зробити сокирою...
...але так не роблять. Чому? Бо часто-густо є більш еффективні інструменти....

і відповідно просто ні хто не очікує від «камєнщіка» що той буде працювати долотом чи що там сокирою чи як умілі дєди чи як він уміє бо «камєнщік» буквально означає «сіньор майстра по роботі з рубанком для камєнщіка 80-го левела»

... а як ні то іди до камєнщіка вчитися у ніх також потребують різноробочі як то виносити сміття бо підмітати сміття може мати окрема машина а от виносити прості роботи таких машин ще поки що просто не ефективно

Звісно, наявність «кодерів» добре полегшує творчу роботу інженерів-програмістів... :)

імєнно но так і було на зарі компютерування як то буквально digital computer vs. human computer

як от чудова ілюстрація з hidden figures до вони потребували more manpower to feed that beast

просто зараз багато з тої окремо ручної роботи вже автоматизовно механізовано віртуалізовано тож буквально

«сіньор майстра по роботі з рубанком для камєнщіка 80-го левела»

вже потребує меншого додаткового manpower для виконання свого роботи питання тут якраз у тім що він так само має знати

в мебельний цех шукали би «сеніор майстра по роботі з рубанком з досвідом 10+», чи на будівництво — «мастера роботи з болгаркою...»

бо це воно буквально і є як саме як саме тут і зараз така робота саме робиться і передбачається що він знає саме те а не ibm 7090 та fortran 2

youtu.be/ID1iFaWgcIE?t=106

youtu.be/AFhlb-PdZjg

Без навантажувальних тестів це все пусті балачки )))

...непогана стаття. ...як для початківця BCM/fintech area.

Так, я можу так писати, бо особисто примав участь в піднятті першого в Україні фінтеха в далекому 2003-му році (то був «Український платіжний сервіс»)... а потім були топ світові фірми «по темі»...

Але, розуміючи, шо то «не є гарно», чому я так роблю?
Бо галузь має свої стандарти, які вже досить давно як склалися...
А якщо хтось заробляючи на тому гроші, жаліє гроші на фахівців по темі...
От оце є недобре...

Але, без образ, просто погодьтеся, що навіть при вашій реально малій кількості транзацкцій, проблеми, ключи оптимізації — однакові для всіх — low latency requirements, ZFT та інше...

Технологічно що ви описали — це так років десять — п’ятнадцять назад так робили...

Але і це ще півбіди. Головне — в статті ви не розкрили основних проблем як RabbitMQ, так і Kafka в цій галузі... Орисані проблеми фахівець з теми вирішує «на ура».

Але. Є більші проблеми, не описані вами.

Чому певні кількість років назад робили перехід з RabbitMQ на Kafka?

Реально, тоді це було питання.

Свого часу перебуваючи у Люксофт, я читав лекції (з праутичними занняттями) по Kafka та Spark.
(Коли маю вільний час, приймаю участь вроботі цих спільнот Apache Foundation, тому це було не важко. )

Так от, часто-густо замовники цих лекцій, такі яу, наприклад, Райфайзен, впряму ставили завдання — довести їхнім фахівцім необхіжність переходу з RabbitMQ на Kafka. А фахівці там дуже не погані! А самі розумієте коли група 30 чоловік фахівців шукають помилку в твоїх словах — все має бути чітко... :)

Тож, головна проблема.
Стандарт «месседж брокера» (не пам’ятаю точну назву) визначає три типу цбого серісу:
— TOS 0×00 — месседж передається продюсером в канал підписки без залежності чи там хто є і чи хтось прийняв його
— TOS 0×01 — месседж має бути хоча б кимось з консюмерів прийнятим («At least one» rule)
— TOS 0×02 — месседж має бути прийнятий конкретним консюмером («exactly one» rule)....

І ось «по простому» — сама архітектура RabbitMQ не дозволяє реалізувати «exactly one», а це точно потрібно для платіжних систем — що BCM, що fintech...

На Kafka це реалізується просто.

Нє, звісно, після RabbitMQ ви можете юзати щось «самопальне» — але коли треба реальне low latency та подібне — це не варіант і в галузі не вітається...

Але попри проблеми самої Kafka (про уе пару слів нижче) — є там і архитектурна проблема.

Як вам правильно писали в одному з коментів,
— RabbitMQ — push based (producer oriented)
— Kafka — pull based (consumer oriented)

Звісно, всі плюси Kafka, особливо її чудова масштабованість — звідси.
Але, як для цієї галузі, і проблема — реальна відсутність черг.
Це не пролблема, коли це FIFO чи LIFO, а якщо треба CTB, HTB чи щось складніше?

І от тут є інший брокер, який є стандартом де-факто для всієї галузі:
Chronicle.

Звісно, про нього треба цілу статтю писати --- в двох словах не скажеш.
Звісно, правильно версія — ліцензійна, хоча опес-соурс пакети теж плюсові...
Топ фінансові установисвіту мають — всі — свої власні фреймворки, які повністю інтегрують його платну версію... — наприклад, в «найбільшому банку світу» такий фреймворк зветься Ambrosia

Але й його опессорс пакейджи від OpenShift — це шось...

Рішення (мінімальне) проблем з Kafka — це Kafka + Chronicle Que...

Хоча сама потужна фіча — в Chronicle Networking який працює на «транспортному рівні моделі OSI»...

...тепер по самій Kafka. Головна проблема «ребалансінг». Зараз є версія без Zookeeper, яка через Quorum Broker вирішує цю проблему, але, по чесному, вона «ще сировата»...

З іншої сторони, «наша коммюніті розробників» Apache Kafka, не без участі «головного спонсора» — Confluent — деклька років пояснює всім що бажано працювати через Kafka sqlDB.
Реально Confluent цвійшов в консорціум SQL і фінальна продакшн версія стандарту SQL2023 вже містить все те, що використовує Kafka sqlDB включно з запитами «emit loop»...

В уніфікації є багато плюсів. Але головне — чим простіше — тим менше варіантів на всілякі проблеми, на кшталт описаних вами...

Бібліотеку Kafka Stream дуже не рекомендовано використовувати... Хіба що у вас Legacy проект і інакше ніяк...

Але. Фонд Apache Foundation дуже цікавий... і приблизно рік назад «набілгли» сусіди по фонду — коммюніті Apache Flink... і зараз розвиток пійшов в напрямці Kafka + Flink + Iceberg... і це ще інша розмова...

І от... фінальне але. Зараз осінь 2024.
Чому ми раніше — років десять і більше назад — обирали RabbitMQ та Kafka, а не їх «старшого брата» Spark? Дійсно, він і краще, і функціональніше, але ...вимагав дуууууже багато ресурсів...

Розвіток пійшов тут в інший бік.
На сьогодні ми маємо AWS EMR, GCP Dataproc, Azure Databriks — дуже дороблені «клоуд» версії Spark з повним обвесом сервісів максимально що тільки було можливість придумати... Навіть варіанти Gen AI та Vertex AI ...але це теж геть інша розмова...

А так... при тім, що маю зараз дуже гарний проект — і не з банківської сфери — радий буду допомогти якщо така допомога потрібна — пишіть в лічку якщо що...
Бо це ми робимо багато правильного для світових фірм... може інколи треба і нашим допомагати... :)

Раз уж вы столько знаете.
Расскажите более развернуто о Ksql DB vs Apache Spark любопытно было бы послушать.
На мой взгляд, последний выглядит сильно лучше из-за своей универсальности прежде всего.

Цікаво, а що краще? Skoda Superb чи Volvo FH Aero?
Таксувати можливо що на першому, що на другому...
Запитання з цієї категорії.

Основний профіт ksqldb:
На якій мові програмують дата-інженери?
...python, java, skala, R, clojure, golang, rust, kotlin, groovy... ... ...
Що по _default знають всі дата-інженери?
SQL

Один файл .sql закинуєте на ksqldb-cli і трансформація готова.
Поріг входження — мінімальний...

Звісно, багато інших «бонусів».

ksqldb —> хоча б це
ksqlDB supports INNER, LEFT OUTER, and FULL OUTER joins between streams.

Звісно, беремо AWS MSK Serverless ... і наш файл трансформації фактично пару рядків в форматі SQL 2023...
Source —> S3, Sink —> RDC/Postgres/ELK...

і отримуємо повнопривідний комбайн по обробці данних з позначкою _minimal_cost.

Apache Spark... так, класно... Тільки скільки коштуватиме?
Якщо правильно пам’ятаю, то там на одну партішн треба 32ГБайт пам’яті...

Нє, звісно, AWS EMR, GCP Dataproc, Azure Databriks — дуже гарні речі. Дуже.
Але... з позначкою — коли таке треба...

Всегда интересно услышать мнение со стороны.

Що по _default знають всі дата-інженери?
SQL

Если заглянуть в дистр Spark-a, а точнее bin каталог вы там обнаружите врапер spark-sql, который позволяет дата инженерам запускать SQL cкрипты, а уж набор встроенных функий там не идет ни в какое сравнение ( по крайне мере на тот момент когда последний раз сталкивался с ksql-db он тихо страдал своим убогим набором функций)
Если у вас старый добрый Hive, то в нем раньше имелась возможность через входящий параметр установить какой тип engine будет использоваться для выполнения SQL запроса tez. spark или mr.

Один файл .sql закинуєте на ksqldb-cli і трансформація готова.
Поріг входження — мінімальний...

а отладка, а СI-CD вокруг этого всего добра?

Звісно, беремо AWS MSK Serverless ... і наш файл трансформації фактично пару рядків в форматі SQL 2023...
Source —> S3, Sink —> RDC/Postgres/ELK...

На одном из проектов нам кровь из носу был нужен Sync коннектор для Вертики. Но все предлагалось было «уныло г...о» с минимальным набором функций умеющее только лить данные в Вертику, и никакого тебе Upsert-a ( MERGE в Вертике присутствует давно, если что), так что несмотря на впечатляющий список коннекторов далеко не факт что их функционал выходит за рамки «аби було».

Есть еще один момент о котором стоит упомянуть.
Инфраструктура — это воотчина DevOps-ов куда вход Дата инженерам просто воспрещен без особого разрешения, таким образом мы оказываемся в непростой ситуации
коннекторами рулят DevOps-ы, а за транформацию данных и внесение изменений Дата инженеры и разделить зоны отвественности ой как непросто.

і отримуємо повнопривідний комбайн по обробці данних з позначкою _minimal_cost.

Как любила повторять моя бабушка «Дешева рибка та погана юшка©» :)
Если с тех пор ничего не поменялось, то Ksql-db — сам engine c коннекторами — это часть Kafka Connect Cluster-a, который явялется самостоятельно инфраструкрурной единицей и сетапится отдельно от оригинального Kafka кластера. Вы можете мне сказать что можно просто запускить ksql-db на одном, на что я вам отвечу что тоже самое можно делать и со Spark-ом.

Apache Spark... так, класно... Тільки скільки коштуватиме?
Якщо правильно пам’ятаю, то там на одну партішн треба 32ГБайт пам’яті...

Это вряд ли.
Default размер партиции в Spark-е 128Мбайт.

И не забываем что уже давно существует возможность запускать Spark как часть инфраструктуры Кубернетиса.

С Ksql есть еще один неприятный моментик, а именно vendor-lock-in.
Как говорится «ничто не вечно под луной» и может так случится что в компании будет принято решение не использовать Kafka, а вместо этого будет использваться что-то другое типа Panda или cкажем Pulsar.
Если по какой-то причине такой «счастиливый момент» будет иметь место, у вас сразу отваливается не один а сразу два этажа инфрструктуры ( Kafka и Kafka Connect) в случае со Spark-ком только один.

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

Спасибо за ваш коммент.

P.S. Если я где-то «перегнул палку» заранее прошу прощения.

по перше, Ви не зрозуміли алегорії, яку я дав на початку...
Ніззя порівнювати Kafka та Spark — від слова зовсім.
...є такий фонд Apache Foundation в якому є велика по чисельності JBoss community.
Apache Kafka, Apache Spark, Apache Flink, Apache IceBerg, Apache Strorm ... та ще багато інших проектів — числом більше 50 — робиться там. І головна вимога Фонду — ці всі проекти не можуть дублювати один одного і розраховані на різні сегменти застосування...

Відпочатку Kafka була «молодшею сестрою» Spark.

От досі, як годинник, 8 років працює проект, який в мене була можливість робити from scratch де вібірково використовуються і Kafka і Spark...

І от питання — 25 офісів та датацентрів овер-глобе...
Чи дійсно є необхідність щоб в кожному була нода Spark?
Реально працююча схема — 25 нод Kafka та Cassandra, і 4 регіональні датацентри з нодами Spark (EMEA, APAM, NA, LATAM) ...і, звісно, не без AWS S3 та обв’язки для «важких та не швидких данних» накшталт скріншотов...

Багато наших фіксів тоді, пізніше з’явилися на клаудах, наприклад Managed Streaming...
а тоді.. наприклад, Skala версія Spring... можливо зараз нас би задовільнила реактивна версія з R2DBC...

Інший кейс. Коли в іншій коммюніті ми починали розробку TensorFlow — то він називався саме TensorFlowForSpark і відпочатку розразовувався для роботи саме зі Spark на 2-му рівні виконання (shuffle) — а мови про Kafka навіть і не було...

по друге

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

Знову велике НІ. The batch streaming only! Реально the pure streaming робить виключно Apache Storm.

З цим пов’язана досить потужна проблема Accidental Complexity — яка є як в Kafka, так і в Spark.

РІшення в банківській сфері — де є гроші, не проблема пропрієтарність та консервативність до принципово нового --- це Chronicle Software...

Інший гарний варіант — Apache Storm як головний стрім-процессінг...
(Файл його інтеграції з Kafka — два рядки коду)
Єдина проблема тут — переконати клієнта та наявність відповідних знавців-tynepsfcnsd Storm.
Знаю проекти, де реально Apache Storm було сабатовано програмістами-розробниками проекту...

Рішення від коммюніті тут — використання Apache Flink...

vendor-lock-in

— ну, використовуйте непропрієтарні версії де цього немає...

Apache Spark... так, класно... Тільки скільки коштуватиме?
Якщо правильно пам’ятаю, то там на одну партішн треба 32ГБайт пам’яті...

Это вряд ли.
Default размер партиции в Spark-е 128Мбайт.

Мова не про розмір партішн... Мова про оптимальній розмір пам’яті в розрахунку на один партішн...

От тут гарна стаття про тюніг цього spark.apache.org/docs/latest/tuning.html
Але, як завжди, головних «нюансів» там не описано...

Apache Spark розраховано на HDFS, HBase, parquet чи інші «оптимізуючі» речі...
Тому, є ньюанс — перехід між стадіями Map, Shuffle, Reduce «базово» йде з записом у файлову систему... Для «нормального тюнінгу» треба вмикати режим RDMA —> тоді все проходить на рівні пам’ять-пам’ять І, відповідно, розрахунки пам’яті на партішн будуть значно інші...

Хоча... не треба нам «звалюватися» в «холі вар»...

Наразі, нажаль, «що краще» залежить від «уподобань клієнта», а не от технічної аргументації.

От про що я пишу в першому коменті. Для нормальної роботиз цим у галузі вироблені «галузеві стандарти», на які можливо зробити посилання. І це більш-менш продуманий «коктейль технологій». Бо, наприклад, вимоги low latency є у всіх.

Ніззя порівнювати Kafka та Spark — від слова зовсім.

Прошу прощения, но я и не сравнивал.
Речь с самого начало шла о сравнении ksqlDB vs Apache Spark ( a если еще точнее Spark Streaming API).
ksqlDB — это составная часть продукта под название Kafka Connect ( возможно сейчас его переименовали) но это не Kafka и он не является частью Кафка broker-a.

Цитата.

Is Kafka Connect separate from Kafka?
Kafka Connect is deployed as its own process (known as a worker), separate from the Kafka brokers.

Картинка если что.
miro.medium.com/...​4u6ez_ZbezLzvWL-xbznQ.png

Таким образом:
— Продукт Kafka Connect a cледовательно и ksqlDB со своими конекторами являются обычными consumer-ми по отношению к Кафке ( и тоже самое в случае Spark Streaming)
— если мы хотим процессить стримы с помощью ksqlDB, нам потребуются дополнительные ресурсы и это НЕ ресурсы Kafka broker-а. Все тоже самое что и в случае Apache Spark. Допускаю что ресурсов на Spark может потребоваться больше, но полагаю плюс-минус тоже самое.

Чи дійсно є необхідність щоб в кожному була нода Spark?

И ровно тот же самый вопрос мы задаем себе в случае использование ksqlDB.
Надеюсь вы не будете утрверждать что-то типа «мы можем просетапить ksqlDB, на строну Кафка брокера и заживем долго и счастливо».

Остальное коментировать не будут по причине отсутствия компетенций или по причине того что эта область выходит за рамки моих интересов.

Update:

Знову велике НІ. The batch streaming only! Реально the pure streaming робить виключно Apache Storm.

Да ну?
continuous mode in Apache Spark 2.3.
www.databricks.com/...​n-apache-spark-2-3-0.html

P.S. Если что, то никогда не сталкивался но читал что такое есть.

Не знаю звідки ви це взяли:

ksqlDB — это составная часть продукта под название Kafka Connect ( возможно сейчас его переименовали) но это не Kafka и он не является частью Кафка broker-a.

ksqldb — це абсолютно інший пакет від kafka-connect.
Його альтернатива — kafka-stream. Але це — бібліотека.

ksqldb-server — це якраз «спеціалізований» kafka-broker, що гарно видно, покопавшися «під капотом» в zookeaper-metadata...

Жодної залежності від кафка-конекта немає...

Ось варіант докер-композа котрий показує всю ньюанси (ссорі за невеликий овертлоад, але тут еічого і не викинеш — все в темі дискусії!):
# Docker compose from bringing up a local ksqlDB cluster and dependencies. # # By default, the cluster has two ksqlDB servers. You can scale the number of ksqlDB nodes in the # cluster by using the docker `--scale` command line arg. # # e.g. for a 4 node cluster run: # > docker-compose up --scale additional-ksqldb-server=3 # # or a 1 node cluster run: # > docker-compose up --scale additional-ksqldb-server=0 # # The default is one `primary-ksqldb-server` and one `additional-ksqdb-server`. The only # difference is that the primary node has a well-known port exposed so clients can connect, where # as the additional nodes use auto-port assignment so that ports don't clash. # # If you wish to run with locally built ksqlDB docker images then: # # 1. Follow the steps in https://github.com/confluentinc/ksql/blob/master/ksqldb-docker/README.md # to build a ksqlDB docker image with local changes. # # 2. Update .env file to use your local images by setting KSQL_IMAGE_BASE=placeholder/ and KSQL_VERSION=local.build.
--- version: '2' services:   zookeeper:     image: confluentinc/cp-zookeeper:${CP_VERSION}     environment:       ZOOKEEPER_CLIENT_PORT: 32181       ZOOKEEPER_TICK_TIME: 2000
  kafka:     image: confluentinc/cp-enterprise-kafka:${CP_VERSION}     ports:       - "29092:29092"     depends_on:       - zookeeper     environment:       KAFKA_BROKER_ID: 1       KAFKA_ZOOKEEPER_CONNECT: zookeeper:32181       KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT       KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT       KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092       KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1       KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1       KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1       KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 100
  schema-registry:     image: confluentinc/cp-schema-registry:${CP_VERSION}     depends_on:       - zookeeper       - kafka     environment:       SCHEMA_REGISTRY_HOST_NAME: schema-registry       SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: zookeeper:32181
  primary-ksqldb-server:     image: ${KSQL_IMAGE_BASE}confluentinc/ksqldb-server:${KSQL_VERSION}     hostname: primary-ksqldb-server     container_name: primary-ksqldb-server     depends_on:       - kafka       - schema-registry     ports:       - "8088:8088"     environment:       KSQL_LISTENERS: http://0.0.0.0:8088       KSQL_BOOTSTRAP_SERVERS: kafka:9092       KSQL_KSQL_SCHEMA_REGISTRY_URL: http://schema-registry:8081       KSQL_KSQL_LOGGING_PROCESSING_STREAM_AUTO_CREATE: "true"       KSQL_KSQL_LOGGING_PROCESSING_TOPIC_AUTO_CREATE: "true"
  additional-ksqldb-server:     image: ${KSQL_IMAGE_BASE}confluentinc/ksqldb-server:${KSQL_VERSION}     hostname: additional-ksqldb-server     depends_on:       - primary-ksqldb-server     ports:       - "8090"     environment:       KSQL_LISTENERS: http://0.0.0.0:8090       KSQL_BOOTSTRAP_SERVERS: kafka:9092       KSQL_KSQL_SCHEMA_REGISTRY_URL: http://schema-registry:8081
  # Access the cli by running:   # > docker-compose exec ksqldb-cli  ksql http://primary-ksqldb-server:8088   ksqldb-cli:     image: ${KSQL_IMAGE_BASE}confluentinc/ksqldb-cli:${KSQL_VERSION}     container_name: ksqldb-cli     depends_on:       - primary-ksqldb-server     entrypoint: /bin/sh     tty: true  

Якщо брати стандартну концепцію ETL, то ksqldb — це як раз T stage, на якому kafka-connect принципово не працює...

Я розумію, що вас збиває... концепція MSR, яка в основі Spark, це зовсі інша концепція від ETL (більш продвинутіша)... і тут S-stage та T-stage — зовсім інші ідеї та відпрацювання...

Наприклад, як я вже писав, TensorFlow не може працювати з T-stage kafka, але розроблявся як раз для S-stage spark.

Update:
Знову велике НІ. The batch streaming only! Реально the pure streaming робить виключно Apache Storm.

Да ну?
continuous mode in Apache Spark 2.3.
www.databricks.com/...​n-apache-spark-2-3-0.html

...зразу оговорюся, що питання «холіварне» — тож можемо тут сперечатися довго...

З іншої сторони, команду Databriks дуже поважаю і добре знаю, де-кого навіть особисто (в 2014 рахом працювали на одному потужному каліфорнійському проекті)
Але, тут більше реклами, ніж реальності...

Як я писав вище — це архітектурне обмеження.
Так, якщо і в ksqldb робити запит з директивою «emit loop» — то вийде «continues streaming» — але це буде той самий «batch streaming», який схож на «pure streaming»... але не. Не він. Про що говорить, що це один з моментів появи пробьлеми під назовю «accidental complexity»...

Так, Databriks підтримує повністтю стандарт SQL2023, то чому б їм не використовувати «emit loop»?

...чому так виходить, що «архітектурне обмеження»?
Насправді це ще більш «холіварне питання».

Коли ми починали роботу над Apache Kafka та Apache Spark (я був там практично від початку) — ми всі дійсно вважали, що мова scala — функціональна.

Але ні. Виявилось, що вона лише частково функціональна.
Власне от як раз тоді, з тим розумінням чому, на базі старої крезі мови lisp було розроблено повністтю функціональну мову clojure, яка працює під JVM і навідь може прямо інтегруватися в код java, scala та python.. На який і був написаний Apache Storm....

А як побачити банчінг того стріма, окрім побреми «accidental complexity»?

Реактивний спрінг з повнопривідною багато поточною «розвязкою» через ELK з великою кількісттю шардів, і робота через R2DBC на postgres...
Запускаємо стрім з databriks, ksqldb та storm

R2DBC гарно працює з батч-стрімінгом — воно ловить те, що стрім йде «порціями» і можеливе налаштування на це..
А от pure streaming від storm воно не розуміє і в певний момент виникає «overflow»

...хм... гарна ідея написати про то статтю з докладними варіантами коду.
Хіба що тільки трози звільнюся по своєму основному проекту — там реальний хайлоад який треба рятувати! :)

А якщо виникне помилка на етапі збереження Offset в БД, як гарантувати що пейменти не потраплять в Stream повторно?

Писати transactional логіку збереження, коли commit відбувається після обробки в базі

Кожного місяця інфраструктура Solidgate процесить понад 18 мільйонів транзакцій.

18000000/30/24/60/60 = 7
7 транзакций в секунду. Это очень, очень немного. И к тому же не имеет вообще никакого отношения к стоимости минуты даунтайма. Во вводных путаница, дальше неинтересно.

Чому? Є всі дані щоб приблизно порахувати середній чек)

Є всі дані щоб приблизно порахувати середній чек)

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

7*60=420 транзакцій за хвилину.
10 000 / 420 = 23$ транзакція.

Судячи з данних наприклад Візи середній чек суттєво вище, що в цілому підтверджує цифри як такі: marketing.dynamicyield.com/...​arks/average-order-value

Здавалось би що таке 7 RPS. Виходить що результуючий об‘єм не такий маленький.

Смотря с чем сравнивать. Ну и можно поднять боян про равномерность загрузки и наличие пиков.

Оптимізована і має в сотні разів кращу швидкість запису даних на диск, що наближає її до швидкості доступу до оперативної пам’яті.

🫠

Мои примечания.

1. Debezium непросто так используют.
Суть в том, что его source connector напрямую читает из системного лога СУБД, который в свою очередь гарантирует последовательность транзакций ( тот самый WAL но для самой СУБД). То есть данные в нем уже отсортированы по времени транзакций и вы можете сконфирировать ваш коннектор так чтобы он фильтровал данные из системного лога которые касаются исключительно интересующих вас таблиц. Ваш «велосипед» с таблицей СУБД с таймаутами этого не умеет и уметь не будет. Еще мне помнится что у Debezium сорс коннектор-ы пишут всегда в топики с одной партицией.

2. Ordering в Кафке возможен только в случае если в топике одна партиция, одна партиция — это проблема для масштабирования при интенсивном стриминге, так что либо производительнгость и масштабирование, либо Ordering и отсутствие последнего и без вариантов.

3. Вы ничего не сказали о том, что представляет из себя Payment gateway и как он пишет в СУБД. Это ваша внутрення разработка или сторонняя? Вам ниже уже упомянули про anti-patern использования СУБД в качестве «приемщика стрима». Вы ничего не сказали о потерях данных в случае если СУБД «ляжет», пусть даже на короткое время и как это резолвится, но много и долго распрострянялись про Кафку.

Без обид, но по-моему архитектура «сыровата» и требует переосмысления.

примечания на примечания
ordering в Кафці в рамках не тілько якщо в топику одна партиція, а в рамках ключа в незалежності від кількості партицій ви можете налаштувати ключ так, щоб все було в правильному порядку, хоча в вас буде багато консьюмерів та партицій. Там нескладний алгоритм, але він мій улюблений, ще з книжки про розподілені системи.

++

Внизу відповідав, є власні бакети і під кожен є сіквенс. Як відновлювати порядок — все є в данних стріму. Ми опираємось на кафку в цьому лише мінімально.

Ще Kafka clients можуть не гарантувати порядок бо працюють вони часто батчами. Вичитування повідомлень може бути батчем і якщо 1 месседж не опрацювався то в обробку може потрапити наступний, але це вже треба вивчати і тюнити клієнт яким користуєтесь для обробки повідомлень.

Макс, видали цей комент :) В ньому все погано, крім того що дійсно треба вивчити.

я краще пошукаю лінк на код клієнта, скину пізніше)

Стикались з цим же: github.com/...​entio/kafka-go/issues/764
Вичитка батчем в пам‘ять, помилка, але fetch працює далі. Треба заводити або «DLQ», або ще правильно конфіжити клієнт та правильно ретраїти.

Так а де issue про «якщо 1 месседж не опрацювався то в обробку може потрапити наступний»?

Ти ж своїм кодом контролюєш чи пропускати меседж чи падати, чи ретраяти, чи комітити офсет чи ні. Клієнт кафки тут немає значення.

То якась люта дічь.

Лол, так почитай до кінця.

While I believe that behavior should be achievable, it’s not the use-case the reader was designed for.
If you have an idea for achieving that behavior while maintaining backwards compatibility feel free to submit a pull request.

Якщо ваш консьюмер скіпає дані, то це проблема вашого консьюмера, а не Кафки.

Є багато кейсів де треба вміти скіпати дані, так само як і багато кейсів де
скипати ні в якому разі не можливо.

але це вже треба вивчати і тюнити клієнт яким користуєтесь для обробки повідомлень.

ніхто ж не звинувачує клієнт, просто на цьому дуже легко спіткнутись, це треба конфіжити. Лінк на issue вище.

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

Я зовсім не розумію де я дав оцінку клієнту і його якості. Якщо в реальності ж описав проблему і сказав що треба правильно тюнити клієнт бо це є ще одним потенційним місцем зламати ордерінг.

По лінку з багрепортом так і написано, що криворучки щось по своєму вирішили накодати.

Шо? У рамках партишена усе читається по порядку

То о чем вы говорите это не Ordering, а Distributed Key, который используется для распределения данных при записи в партиции топика и только. Иными словами данные из одной партиции, записанные в топик позже могут быть вычитаны ранее чем аналогичные данные пришедшие ранее, но находящиеся в другой партиции. Distributed Key ничего с этим не сможет сделать. Вам все равно после Кафки придется эти данные пересобирать и упорядочивать.

Часто для систем достаточно гарантировать ordering в рамках partition key.

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

Закончите мысль. Более развернутый пример можете привести? Особеенно как ключ партицирования обеспечивает последовательность.

Окей.
Маємо аккаунт1 і аккаунт2
Транзакції по аккаунт1 ідуть в партицію 1
Транзакції по аккаунт2 ідуть в партицію 2

Партишин ключ у даному випадку акаунт айдішка.

Є звичайно можливіть що комусь потрібно всі транзакції процесати по порядку створення, але це тоді якась брєдятіна.

Все верно вы написали, только этого может быть недостаточно.

Пример.
У вас и вашей жены(предположим что она есть) общий счет к которому привязаны платежные карты. На счету 100 евро. Вы совершаете покупки независимо.
Вы совершате покупку на 30 евро.
Жена совершает покупку на 80 евро.
Поскольку карты имеют разные номера, то они попадают в разные партиции.
Партиции загружены неравномерно, даже с учетом того что упомянула @Emma Orlova
Консьюмеры висящие на этих партициях тоже могут читать с разной скоростью.
Предположим что данные из партиции в которой хранится инфа о транзакции жены была обработана раньше, следовательно пройдя по цепочке процессинг мы имеем остаток на счете 20 евро и тут другой консьюмен вычитывает данные о транзакции мужа и получаем баланс на счете −10 евро. :)

Да, я знаю что пример немного натянутый, но я постарался его сделать простым.
Подобные примеры в жизни тоже случаются типа «выдали из банкомата больше денег чем есть на счету для дебетной карты и т.д.»

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

Ключем в такому випадку буде номер рахунку, а не номер картки

Ну то есть вы подстроились под этот кейс и ваш ответ совершенно верный.

Давай другой, если этот сломался.

Це вже деталі бізнеслогіки як правильно розділити івенти на партиції.
Смисл взагалі тоді брати кафку якщо тільки одна партиція? Консюмер теж тільки один буде.

Совершенно согласен на 100%, но это уже вопрос к автору. :)

Вчить матеріальну частину.
На топік ассайниться виключно «консюмер груп», яка, загалом, може і не містити консюмерів. До чого тут партішени?

А ваша репліка повний аналог: «нащо мені комп’ютер, коли на ньому тільки один вінчестер?»...

Так чи інакше, партішени, репліки, рулеси, котрі встановлюються — це все варіанти тюнінга системиі геть не залежать від бізнес-логіки.
Хоча бізнес-логіка може визначати " в якій бік" ми маємо тюнити систему.
Але, як правило, варіантів тюнінга завжди більше ніж один...

І що ти хочеш мені довести?
Якщо на партицію більше ніж 1 консюмер то тільки 1 буде її читати.
До чого тут група? Консюмери у групі зазвичай виконують різну бізнеслогіку

Я писала таке, там ти береш хеш від ключа робиш mod за кількістю партишенов і воно тобі рівномірно консистентно розподілить ключі по партишенам, тут більш складна логіка це робити динамічну ребалансировку, тому треба брати партицій з запасом набагато більше ніж потенційних консьюмерів, бо міграція потім буде важкою

тут до речі якщо треба розмазати якийсь апдейт по календарю без додаткової синхронізації теж так можна робити, дивись на день місяця як на бакет і ключі можна рознести рівномірно по кожному дню.
imgur.com/HJ4qkE5

Вам все равно после Кафки придется эти данные пересобирать и упорядочивать.

Для чого?

1. Все так. І ми до цього також йдемо, але не в тему щоб використати Debezium а щоб зробити щось простіше ніж Debezium, не на Java а на Go. Debezium також не гарантує порядок, бо він читає WAL а в WAL транзакції пишуться після коміту, не на старті. Відповідно ордерінг в WAL не гарантується. В PostgreSQL механізм реплікації має ордерінг буфер, але і він не вирішує проблему непослідовних комітів. Перша транзакція стартує в 10:00:00, закінчується в 10:01:00, друга транзакція стартує в 10:00:30, завершується в 10:00:40. В WAL вони попадуть коли будуть комітитись і відповідно порядок вже порушений, для цього використовується ID(ulid/autoincrement) і резервується він на старті транзакції, не на коміті.

Debezium має 2 проблеми:
— За ним складно слідкувати бо всунути метрики в необхідні місця попросту неможливо.
— Ніхто не знає як він працює і чому падає. Відновлення роботи дебезіуму це звичайний рестарт.

2. Тут відповів: dou.ua/...​0318/?from=slider#2875650
3. Наша, Payment gateway генерує дані, не приймає. Падіння бази = зупинка проведення карткових транзакції і подальша їх дистрибуція. Але для цього стріми і завели, історичні дані інші консь‘юмери можуть вичитувати зі стріму. Відмовостійкість платіжного гейту це прям велика окрема історія, яка повинна бути окремою статтею.

Без обид, но по-моему архитектура «сыровата» и требует переосмысления.

ніяких образ, якось пересічемось на FWDays/Dou івентах і обговоримо голосом)

ніяких образ, якось пересічемось на FWDays/Dou івентах і обговоримо голосом)

Вряд ли. Если только он будет проходить в Праге, а не в Киеве. :)

Тоді пишіть в LI, з радістю і онлайн зустрінусь)

Відповідно ордерінг в WAL не гарантується.

А чому саме потрібно організовувати порядок транзауцій за часом початку, а не за часом виконання?

. І ми до цього також йдемо, але не в тему щоб використати Debezium а щоб зробити щось простіше ніж Debezium, не на Java а на Go.

Вместо того чтобы использовать готовые решения, с которыми нужно научиться работать, развлекаетесь написанием своих собственных.
«Написано не на GO» — это хреновый аргумент, которых говорит о том что у вас много времени. Эксплуатация готовых решений обычно более дешева чем создание собственных решений с последующей их доводкой.

Debezium також не гарантує порядок, бо він читає WAL а в WAL транзакції пишуться після коміту, не на старті. Відповідно ордерінг в WAL не гарантується. В PostgreSQL механізм реплікації має ордерінг буфер, але і він не вирішує проблему непослідовних комітів. Перша транзакція стартує в 10:00:00, закінчується в 10:01:00, друга транзакція стартує в 10:00:30, завершується в 10:00:40.

Это ж вы решили использовать СУБД в качестве очереди? WAL в СУБД работает правильно, иначем бы не работало рековери в самой СУБД, просто вы выбрали неудачное решение, и теперь пытаетесь как с этим жить.

Правильно ли я понимаю, что когда вы говорите о времени начала транзакции — это время генерируемое Payment Gateway, а не время комита ивента в таблицу СУБД?

Это ж вы решили использовать СУБД в качестве очереди?

ми вирішили лише організувати append log для відновлення станів в бд.

WAL в СУБД работает правильно,

Я не кажу що він працює невірно. Я лише кажу що транзакції потрапляють в WAL лише після коміту, це факт що вже призводить до перегляду роботи з Debezium.

Правильно ли я понимаю, что когда вы говорите о времени начала транзакции

ми не використовуємо час, це ж звичайна проблема з Clock Skew. В тому прикладі я відобразив чому транзакції в реальному часі можуть не потрапити в WAL в порядку виникнення, а потрапляють в порядку коміту.

Правильно ли я понимаю, что когда вы говорите о времени начала транзакции

ми не використовуємо час, це ж звичайна проблема з Clock Skew. В тому прикладі я відобразив чому транзакції в реальному часі можуть не потрапити в WAL в порядку виникнення, а потрапляють в порядку коміту.

..ха! А це як? Ви щось чули про такі бази данних як KDB+, InfluxDB та т.п.?

Загалом, якщо ви займаєтесь еквайрінгом в мережах VISA-NET та подібних, ваша система має відповідати принципам PCI DSS...

Більш того. Мінімум півроку ви маєте зберігати історію транзакцій — OrderID та всі пов’язанні данні, у тому числі час проведення транзакції — бо на протязі півроку клієнт може звернутися в систему і доводити, що він не здійснював цієї операції і «лист доброї волі» з вимогою до вас «повернути гроші добровільно» може прилетіти «на ура»... :)

Криза «падіння дот-комів» 2000-го року як раз з нерозуміння того і вийшла...

І просто порада: будьте обережні. «Старший брат» в особах контролюючих органів, у тому числі з боку регулятора — НБУ — не спить, а ви вже озвучили багато порушень... А їм що? Їм заробіток...

Ви трішки незрозуміли. Ми не використовуємо час оредрінгу в стрімах, чи не генеруємо час на стороні сервісу якщо на цей час треба орієнтуватись бо це призводить до Clock skew. Дані зберігаємо насправді ще з 2016 року і по аудитам все проходимо.

Вместо того чтобы использовать готовые решения, с которыми нужно научиться работать, развлекаетесь написанием своих собственных.

Іноді необхідно стикнутись з коренем проблеми, вирішити його замість використання вже готового інструменту. Якось треба набувати досвід і вчити команду вирішувати складні проблеми до того як вони стануть не просто складними але ще й критичними. Аргумент фіговий, погоджусь, але можна я б це порівняв як «вирішувати Leetcode» та «вирішувати Leetcode проблеми в реальних задачах». Логіка була такою.

Не всі проблеми вирішуються таким підходом. Треба балансувати.

Еще мне помнится что у Debezium сорс коннектор-ы пишут всегда в топики с одной партицией.

не обов’язково, ми пишемо в декілька партицій. скоріше всього ви це написали через наступний пункт.

Ordering в Кафке возможен только в случае если в топике одна партиция

тут питання, що ви робите з цим ордерингом.
в Shopify є сотні шардів бд, дані будь-якого shop_id (а це багато таблиць) в будь який момент логично існують тільки на одному шарді (физічно все складніше), що усклюднює дизайн CDC), при запису в топік партішен вираховується як

partitionN = hash(table PK) % number_of_partitions

що гарантує впорядкованість даних для кожного окремого шопа. як правило, CDC клієнтів цікавить тільки цей факт впорядкованості per shop.

питання до ОП:

Latency — неможливість вичитувати івенти швидко

як ви плануєте скейлитися з такою швидкістю вичитування даних?

Debezium складний, так, але все можна подолати. наша невеличка команда контрібутить в код і доки Debezium upstream.

в цьому році @tobi наказав зробити p50 від запису івенту в MySQL binlog до попадання в Kafka таким що не перевищує 50ms. зробити навіть краще, тут під кінець презентації є цифри (запис з Google Next’24).
я б рекомендував інвестувати в Debezium якщо хочете вирости в цифрах.

Хочу почути rationale) простий розрахунок чому ви так зробили та чому варто було так будувати.

18 млн транзакцій на місяць, це ~7 транзакцій в секунду.

Побудова скальованого підходу до стрімінгу який буде Durable та Scallable.

Заюзали вже в фінансовій системі, підхід ок працює під 1.2к RPS. І зростання в 10х буде дуже легко забеспечити.

Для мене це виглядає як оверенжиниринг, але в вас дуже чудова стаття вийшла.
Якісно все написано і з ілюстраціями. Дякую вам

Готовий обговорити простіше рішення)

AWS FIFO SQS поддерживает до 300RPS на шард без батчинга(с батчингом в 10 раз больше). Просто SQS без FIFO без гарантий ’exactly-once delivery’ ЕМНИП еще раз 100 больше. Из коробки с почти конфигурированием за полчаса. Стоит копейки.
Обработчик сообщений на лямбдах — можно забыть про memory leaks, мониторинг нагрузки серверов и прочая. Стоить будет(навскидку) месячную зп джуна и валом возможностей для оптимизаций.
Выбор баз данных широчайший. Конфигурируется за пару часов, стоит еще одну зп джуна в месяц опять таки с возможностью оптимизаций.
И все. Никаких головных болей с железом, сеткой, делаями и прочим головняком.

Как у него обстоят дела с дропаньем сообщений?

Дропаньем из очереди без причины.

Никак. В смысле доставка гарантируется. Конкретные гарантии доставки(exactly once, at least once, exactly once handled) зависят от конфигурации очереди и конфигурации/кода обработчиков сообщений. В последнем можно накосячить и начать терять сообщения. А можно следовать мануалу и свести риск потери к очень близкому к нулю. Про потерю сообщений по причине самой очереди я ни разу не слышал и не видел на амазоновском хелс дашборде. Чатжпт со мной согласен, он тоже не в курсе про такие случаи.

By design, конечно, они не должны теряться. Хмм, не могу найти где я читал про кейс, когда они пропадали, возможно, было в приватных переписках. Если найду, напишу отдельно. Так же не исключаю, что это уже пофиксили. В SQS все равно лимитированные характеристики по сравнению с Кафкой, поэтому нужно смотреть на потребномти и возможности масштабирования. Для нас одним из решающих факторов были competing consumers.

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

Ставлю на мисконфигурацию

В SQS все равно лимитированные характеристики по сравнению с Кафкой, поэтому нужно смотреть на потребномти и возможности масштабирования.

У топик стартера нагрузка 7 RPS, поэтому я и предложил SQS — даже в самом зарегулированном сценарии он тянет 300 RPS(ок, операций) на ШАРД — запас в 20-30 раз при использовании 1 шарда.
Впрочем, есть валом других сервисов: от кинезиса и IoT Gateway до кафки as a service. Стоит дороже, приседаний больше, но опять таки, весь головняк с конфигурированием уходит инженерам амазона, которые это делают на потоке годами и получается у них сильно лучше чем у типичного девопса(или команды девопсов) небольшой конторы.

Ставлю на мисконфигурацию

В большинстве случаев так и есть. По крайней мере у меня это в 100% кейсов когда знакомые рассказывают, что Кафка теряет сообщения. Но в случае SQS не уверен.

А дальше это уже вопрос managed сервиса vs. self-hosted. Тут у компании могут быть свои полиси на этот счет.

Но в случае SQS не уверен.

Там все хорошо — один самых крутых и популярных амазоновских сервисов.

Тут у компании могут быть свои полиси на этот счет.

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

Як не втрачати дані
Щоб зменшити таймаут вичитки з outbox є банальне рішення — Lamport’s logical clock. Складний термін, який працює на векторах і векторній математиці. Проте ми реалізували простішу його версію — замість ulid взяли auto_increment і саме його використовуємо для ордерингу івентів в Outbox table. Чому? Саме так можна перевірити, що якийсь з івентів був пропущений.

і все одно можете втритит івенти на довгих транзакціях з таким підходом і ордерингу очевидно не буде..
а просто з локами розібратись не пробували? — у вас сіквел база ідеальний не distributed інструмент що може синхронізувати conсurrent запис\читання і зменшити latency від хвилин до мс з коробки(достатьно блокувати просто правильно вичитку і не читати поки є активні транзакції і навпаки) таким чином івенти не будуть втричатись і це єдиний спосіб досягти ордерингу у вашому випадку. ви блокування потоків у процессі теж вирішуєте через лампорта івент ордеринг? ви знаєте що ці концкепції взагалі для distributed обчислень потрібні і тільки?
інженерні рішення дуже сумнівні як і рівень комптенції у цих моментах схоже.

Тобто ви пропонуєте не читати дані поки є незавершені транзакції? А якщо транзакції будуть проводитись постійно? Як ваше рішення буде масштабуватись?

На тих обʼємах запису на які розраховані сіквельні бази в рамках одної патриції буде масштабуватись без проблем. Взагалі outbox потрібен щоб вирішити конкретну проблему і для цієї проблеми і там де він застосовується практично дійсного великого load на запис майже ніколи немає. Інакше там би дійсно булі інші інженерні рішення з сорсингом transaction log на message broker напряму.

Цікаво було б подивитись заміри. Блокування всієї таблиці на кожну операцію має всі шанси стати проблемою.

Все залежить від того які транзакції врапить outbox — якщо шквидкі і короткі(~5ms), то сам outbox буде без проблем відпрацювувати 50 rps і давати спокійно читати з latency 50 ms таблицю паблішером недорогому серверу з 8 ядрами і ssd диском. Насправді сіквельні бази завжди лочать треди на запис (може це сюрприз для когось) , додайте сюди один потік на читання — мало що змінится, їх пррдовжують використовувати в тому числі на навантаженних проектах і в технологічних компаніях в тому числі.

Finance system now publishes to Kafka 1.2k RPS using outbox approach. It means we have a ton of writes in other systems)

Та хоч 12 krps — у вас не вирішена проблема залишилась для якої ви взяли той outbox і ті каунтери — можете втрачати данні неконтрольовано і відсутній ордеринг івентів.

Що за транзакції процесите доречі цікаво? 1,2 це достатньо багато для payment шлюза фін транзакції, гугл каже що віза вся хендлить 24 krps.. у вас виходить як 3% обʼєму трафіку карткових платежів всіх банків на планеті.

Ви порівнюєте платежі з платіжними комісіями, я ж тому і казав про фінансову систему, не про платіжний гейт. 1 платіж може містити до 5-7 транзакцій. 1 транзакція може містити декілька комісій різних рівнів що ще в декілька разів збільшує обсяг данних на 1 транзакцію. Також окрім Візи, є Мастеркард і ще декілька сотен інших платіжних систем, так і в додаток ще й альтернативних по типу пейпелу. Тому 3% невірна цифра.

Та хоч 12 krps — у вас не вирішена проблема залишилась для якої ви взяли той outbox і ті каунтери — можете втрачати данні неконтрольовано і відсутній ордеринг івентів.

аутбокс ми зробили по бакетам, кількість бакетів задаєтся на етапі створення. Sequence(айдішки) під кожен бакет — свій. Бакети для партішінінга.

Відновлення порядку стається на консьюмері, є декілька підходів:
— треба упорядичити всі данні з топіку(так як є ще партішени в кафці) — створюється буфер в пам‘яті і накопичуються данні за проміжок часу. Тут айдішки якраз те що треба, це зроблено для асинхронних конс‘юмерів яким не треба данні в ріал таймі.
— треба упорядочити по сутності — тут порядок відновлюється по бакету+айді+мапа переходів статусів платежу. Платіж не може пригати від одного статусу до іншого, в нього є логічний список станів в яких він може знаходитись. Спочатку перевіряється айді, потім статус. З статусу auth транзакція не може перейти в refunded, втрачений проміжний статус settled.

Тобто ордерінг досягається в залежності від потреби за рахунок або айдішок, або бакетів, або бакетів, айдішок та статусів. Auto increment же дозволяє виконувати базове відновлення(глобальної черги) і відслідковувати роллбеки транзакцій.

Бізнес логіка має значення, побудувати абстрактне рішення було б неможливо.

а ще можна писати в кафку по ключу бакета в outbox*

Це вже вирішує сервіс який заводить стрім — окремо.

Ще варто зазначити, але звичайно це некоректно порівнювати, наприклад, Webhook в Stripe, та і в цілому будь який інший провайдер — вони не гарантують порядок. Порядок відновлюється на стороні конс‘юмера, просто конс‘юмеру надаються необхідні для цього механізми.

У Страйпа статуси переходів платежу.
В нас це бакети, айді, статуси.

Порядок відновлюється на стороні конс‘юмера, просто конс‘юмеру надаються необхідні для цього механізми.

Прочитав ваше рішення — як на мене це просто жах який важко уявити, намагатися відновлювати порядок операцій на консьюмері у паб сабі через буферизацію і реордерінг івентів, нащо вам та kafka, rabbit — дописали би трохи коду і свій би броке зробили, у вас їх аж 3 доречі вийшло в одному нескладному data pipeline (outbox, kafka, consumer buffer) ..

Вибачте, не розумію до чого взагалі Pub/Sub якщо річ про Streaming. А це різні концепти.

Називайте як хочте , суті не змінює.

Так ви ж пропонуєте рішення під інший концепт замість запропонованого в статті)

Ваша проблема вирішується однаково в обох випадках за рахунок ордерінгу на продюсері і брокері, те що ви взяли кафку не робить вас системою зі стрімінгом, система платіжна це аж ніяк не realtime event streaming use case з якимось хитрим CEP(complex event processing).
Те що ви придумали, що вам важливій ордерінг не коли транзакцій комітнулись (як у всіх інших випадках), а коли вона почалась і це ламає вам якісь цілі(які так і не зрозуміло) що вам прийшлось ордери и івенти на консюмерах, теж звучить як надуманна проблема і невміння застосувати рішення технологічні.

Блокування всієї таблиці на кожну операцію має всі шанси стати проблемою.

Блокується на таблиця, а ренж ключів з яким працюють конкурентні транзакції. з іншої сторони блокувати і всю таблицю не буде проблемою.

Блокується на таблиця, а ренж ключів з яким працюють конкурентні транзакції

В такому випадку не розумію як гарантувати, корректну послідовність повідомлень.

з іншої сторони блокувати і всю таблицю не буде проблемою

Не розумію як можна це стверждувати без відповідних тестів. Моє розуміння, що блокування таблиці (чи декількох) в рази повільніше, ніж блокування потрібних записів. Навіщо штучно обмежувати продуктивність системи?

Не розумію як можна це стверждувати без відповідних тестів

Я ж дав вам цифри реальних тестів. Мав досвід з цим на продакшині робочий.

В такому випадку не розумію як гарантувати, корректну послідовність повідомлень

Що саме, у вас інкрементний ключ forward only — ви читаєте і пишите конкурентними тредами, якшо виникає конкуренція, читання або запис зупинятся поки інше працює звичайним updlock який блокує ренж по умові. Where id > %my key% order by id desc. Вставити в інкремент щось з ренжем який вже прочитаний за цих умов вже не вийде(читання відбувається тільки якщо немає активних транзакцій що аллокували інкремент)
Вставка буде лочити за рахунок оновлення індексу, або по умові кастомній в залежнлсті від того чи треба ексклюзивність запису.

Я ж дав вам цифри реальних тестів. Мав досвід з цим на продакшині робочий.

Це було неочевидно з відповіді, яле дякую, що поділились реальним досвідом.

По іншій частині звучить як забагато координації та ускладнення імплементації, але схоже це також має право на існування. Технології — чудова річ, бо одну й ту саму задачу можна виконати різними способами і обрати той, який краще вписуєтсья в контекст.

По іншій частині звучить як забагато координації та ускладнення імплементації

У порівнянні з чим для задачі транзакційного запису у реляційну базу і на брокер?

Так, тут буде потрібна координація у порівнянні з WAF або іншим механізмом реплікації у інших бд.

Якщо не використовувати рсубд то і outbox якби не потрібен.. те що ви називаєте сорсити oplog або transaction log на брокер, це зовсім про інше(і потребує воно куди більше налаштування ніж читати і писати з таблиці на high level рівні), якщо було б інакше не було би паттерну outbox власне і всі би сорсили транзакційні логи не брокер.

Now this approach is used in Finance system which publishes to Kafka 1.2k RPS.
At the time article material was at work outbox in Finance system wasn’t released.

Can’t attach a screenshot. May send it to you.

а чи тестували ви raft в kafka? Його використовують замість зукіперів

Raft не тестували поки що. Тестували сам зукіпер. Ось доречі класна стаття на тему хаос тестування самої кафки та зукіпера: www.gremlin.com/...​ts-to-run-on-apache-kafka

доречі що забавно. 7 RPS майже ліміт Bitcoin, але це не заважає опрацьовувати мільярди доларів в день і сумарно тримати більше трильйона доларів)

на скільки доцільно порівнювати розподілену по всьому світу систему, з внутрішнім рішенням, де вся інфра ваша, і всі апи ваші, а мережеві затримки між сервісами ~1мс.

Це я більше про порядки цифр.
7 RPS це може бути і хайлоадом, деталі мають значення як ви і самі зазначили.

Тому і виглядало дивним, окрім використання postgres, додавати ще щось для 7 рпс

Рішення ± універсальне для більшого лоаду і рішення робилось під мультиплікатор 2.5-3х в рік. Тобто щоб ми могли витримувати навантаження в декілька десятків разів вище ніж є зараз.

Фактично 1.2к рпс на фінансовій системі і 10х ріст буде доволі просто зробити просто заскейливши кількість топіків в кафці. Це не вимагатиме ніяких змін в тулзи.

If you use Postgres with full ACID guarantees, replication, and all other things, why do you need Kafka/Rabbit at all?

Kafka is pull-based; readers actively fetch messages from channels, so you get zero benefits from a latency PoV compared to pulling from Postgres directly. Plus, you don’t need this “2 minutes delay” for some strange settle-down process.

Process-only-once exists in Kafka, but it’s sub-par compared to proper ACID.

Auto-increment in Postgres does not guarantee that all values will actually be used... It doesn’t provide strong ordering guarantees either... And ordering in a transactional world is a very relative term.

You’re proud of FinTech, but it seems like your solutions are not as bulletproof as you try to show here...

Does Postgres support long polling so that it can send data to a consumer as soon as it’s available?

Postgres has ALL the features www.postgresql.org/...​s/current/sql-listen.html
gist.github.com/...​291de962702ea9c237a900c79

I’m not sure if it is better than a proper solution on top of kafka or some MQ but it exists.

P.S. А чому ми пишемо англійською?

Okay, that’s nice. Also it’s worth taking into account scalability, competing consumers, etc. But for some use cases it might work sure, similarly to change streams in mongo, I suppose.

P.S. А чому ми пишемо англійською?

🤷‍♂️

Proper solution with kafka/rabbit will be better for sure, but it’s just too hard to call solution from the article proper :)) Outbox pattern has its use cases, but for completely different reasons

LISTEN/NOTIFY — це не завжди гарно, бо доставка не гарантована, при рестарті серверу черга знищується, і послідовні однакові повідомлення можуть бути доставлені як одне.

Logical replication — це краще, але складніше

It supports LISTEN/NOTIFY, which is sort of pub/sub :) And you can send notifies from triggers

to pulling from Postgres directly

Pulling from Postgres directly brings you DB isolation problem especially if there are more than a 1 consumer. We can have more than 10 consumers. Also worth mentioning a Reading issue as different consumers can be on a different offset. By “reading issue” I mean that you physically should read different WAL files if you read from WAL and if you use replication mechanism.

Even if you skip using WAL, DB will not scale the same way Kafka can. Only if you have a ton of read replicas, but that will not be different from 10+ consumers.

Process-only-once exists in Kafka, but it’s sub-par compared to proper ACID

Sorry, don’t get how is this emerged for dicsussion and how is this related to the topic.

Auto-increment in Postgres does not guarantee that all values will actually be used.

This is solved in both versions in different approaches.

It doesn’t provide strong ordering guarantees

It provides. In the business logic, next transaction in the order can emerge only after first is created. The transaction creation order matter, not the DB commit order. Simply because auto-increment is atomic and it provides you your number during transaction init, not commit. Mostly we need to preserve order only in scope of 1 payment.

The business logic may be not represented in the article but it matters.
So let’s be less abstract and talk about proper Kafka solution. How it should look like?

Even if you skip using WAL, DB will not scale the same way Kafka can. Only if you have a ton of read replicas, but that will not be different from 10+ consumers.

Are you joking here? 10 consumers reading from single table with different offsets create significant load in postgres?..

Postgres on t3.medium can easily scale to 100+ readers...

And you definitely not want to use WAL/replication/etc, you have absolutely standard append-only log-like table, it’s completely covered by 3rd grader sql...

Kafka була спроектована для даних з коротким часом життя. Класичний приклад — замовлення на чомусь типу booking.com
Писати щось подібне в постгрес незважаючи на vacuum все рівно ...

In original post all data is saved to the postgres first, and streamed out later using “innovative solutions” :)

Those innovative solutions and patterns are decades old. Anyway, how would you approach the problem? I wonder if you are oversimplifying the problem or if you can share some good insights.

I’m analyzing the problem based on data in original article, and this means we have to deal with just single append-only table.

And this can’t be a problem, that requires any WAL reading and things. It’s just a straightforward select based on partition and ID saved in meta table.

Probably there are some additional requirements from business logic side, but I have no idea what they should be to require so much “innovative” solution.

Using of kafka to distribute load between multiple processors for the same stream either requires “process-only-once” I mentioned, or using separate partition key for each reader. And in the second case kafka buys you exactly nothing.

I for one don’t understand the problem that is being solved here, because I don’t have enough exposure to the domain and the product. It can be that the PaymentGate can already write messages to Kafka first (which would act as WAL itself and then all interested parties would process those message). But I can also imagine that there are reasons for writes to first go into Postgres and then streamed to other consumers.

And for publishing the outpbox data to Kafka a WAL-based solution does seems more efficient.

Looks like what you are suggesting here is a The Database As Queue Anti-Pattern. There are cases where it can be applied, but it also has known limitations. One reason to avid it here would be a lose coupling between services or subsystems and ensuring their boundaries. Taxer services on the diagrams clearly has its own DB and there’s no reason for this new services (potentially owned by different team) to access different DB just to read messages. Such cross-boundaries DB access often leads to higher coupling, makes it harder to ensure proper contracts and over time becomes very painful to detangle.

We need microservices to enable different teams to work independently. Oh, now we need more people working on microservices to handle all the overhead they cause. And now we need even more decoupled microservices so these people can work independently :)))

And now we need to solve this new pile of exciting technical problems for synchronizing all those shiny independent databases that each microservice runs :)))

At some point it becomes apparent that there’s no one size fits all solution and that there other tools than a hummer.

Підписатись на коментарі