Как стать автором
Обновить

Комментарии 75

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

Как в такой системе показываются баланс и сделки за последние n дней например?

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

Но как оптимизировать показ последних операций, например за сутки, если счету десятки лет и сотни тысяч транзакций? Не пересчитывать же с первой транзакции каждый раз?

По джобе раз в сутки считать баланс на 00:00, в течение дня брать из бд этот предпосчитанный баланс и прибавлять к нему транзакции за сутки

Я сам внутри не копался, но думаю, что просто периодически подводятся итоги и балансы сохраняются. Поэтому достаточно посчитать транзакции за период с последнего подведения итога + баланс на тот момент времени. У банков есть понятие закрытого опердня (в который новые транзакции уже не вносятся) и открытого опердня, который ещё может пополняться новыми данными.

На счете текущий баланс и холды - суммы, которые еще не списаны (получение денег плательщиком не подтверждено), но уже "обещаны к списанию".

Плюс платежные документы, связанные с этим счетом

Но как оптимизировать показ последних операций, например за сутки

Как уже ответили - иметь снепшот/подсчитанный актуальный баланс на определенную дату / с определенной транзакции. Вы наверняка слышали термин опердень (операционный день).

Вы слышали про такой подход, как event sourcing? Вот тут такая же логика: ваш баланс - это результат всех операций. При совершении транзакции, восстанавливается баланс исходя из всех событий. Естественно когда событий много, это может быть проблемой в производительности. Для таких случаев делаются снапшоты каждые n-событий или периодически. Таким образом оптимизированная версия будет восстанавливать баланс из снапшота и событий после снапшота. При грамотном подходе к блокировкам можно добиться хорошего уровня параллелизма

Верно. Также есть 2 механизма (на самом деле больше) механизма отката транзакции:

  1. проведение поверх корректирующей

  2. полностью удаление информации о транзакции

Последняя весьма раздражающая, но используется банками в случае быстрого возврата денежных средств. Например при возврате на кассе в течение короткого (до дня) времени.

Например при возврате на кассе в течение короткого (до дня) времени.

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

Насколько я понимаю, речь идёт о не до конца проведенной транзакции.

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

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

Попадает :)

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

И эта операция также не светится в отчётах в ЦБ

Не транзакции, а платежные документы. Платкльщик, счет плательщика, получатель, счет получателя, сумма платежа...

Вот понятие платёжных документов, оно довольно странное. Их форма и содержание — это полная свобода творчества заинтересованных сторон. Например, если я, находясь в РФ, прошу своего заграничного друга оплатить мне за границей какой-то сервис (например, Zoom), то наша переписка в чате является платёжным документом или нет? Ведь эта переписка потом служит основанием для подсчёта наших взаимных обязательств друг перед другом, кто кому сколько должен, по аналогии с тем, как банки ведут учёт денег на счетах клиентов...

Платежным поручением может считаться)

Замечание справедливое. Исправил вступление. Туториал c фокусом на понимание изоляций и свойств стэйтментов. В этой статье рассматриваю только проблему обновления двух счетов. Бухгалтерские системы гораздо сложнее.

А вот тут еще и фантомные чтения надо рассматривать в тему статьи.

Спасибо. В этих примерах фантомные чтения не помешают. Но их можно будет рассмотреть на другой подобной задаче

Вообще, это основа. Прочитал запись, что-то в ней сделал, перед тем как записывать - проверь. Прочитай еще раз - не изменилось ли что-то пока ты работал с записью

Комментарий по фантомным чтениям относился к варианту досчета остатка по транзакциям.

И все равно вопрос - вы начинате транзакцию на списание 300р со счета. И вэтот же момент начинается транзакция на списание 400р. А на счете вмего 500р. Каждая транзакция по отдельности валидна. Вместе - нет. Что делать?

Вопрос наверное не ко мне, а к автору статьи.

Собственно он статьей на Ваш вопрос и отвечает, что делать и какие патерны серилизации применять.

К сожалению, там нет ответа на поставленный вопрос.

Я уже писал как это происходит в реальной жизни.

  1. Формируется платежный документ где указывается плательщик, получатель, счет плательщика, счет получателя. Причем, эти счета на обязательно в одном банке. Скорее всего, даже в разных. И сложности возникают только со списанием денег со счета плательщика. С зачислением на счет получателя все проще.

  2. Если счет плательщика в нашем банке, Сумма по платежному документу переводится со счета на холд - резервируется (если на счете достаточно денег). Это быстрая операция, она делается с блокировкой записи.

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

  4. Сумма холда уменьшается на сумму платежного документа только тогда, когда от получателя придет подтверждение о том, что деньги реально пришли на его счет (иногда это может занимать несколько секунд, иногда несколько минут).

Т.е. одной транзакцией тут никак не обойтись - между переводом денег с баланса на холд и списанием их с холда или возвратом обратно на баланс (в случае отказа в операции или неподтверждения операции в установленные сроки) может пройти существенное время.

Это и есть та самая (причем, достаточно упрощенная) бизнес-логика, в отрыве от которой все это превращается в сферического коня в вакууме.

Кроме того, транзакции достаточно сильно грузят сервер. Я скажу крамольную вещь, но мы работаем с COMMITMENT CONTROL (*NONE). Т.е. без коммитов и роллбеков. Вместо этого ведется журналирование всех операций (в специальные журналы пишутся образы записей "до" и "после" изменения (или только "после" в случае добавления или только "до" в случае удаления). Плюс двойное чтение - прочитали запись "до", внесли изменения (получили "после"), перед записью изменений еще раз читаем запись с проверкой - совпадет она с образом "до" - если да, то записываем изменения, если нет - возвращаем ошибку "кто-то другой изменил запись" (и дальше она уже по бизнес-логике обрабатывается).

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

Правда, опять крамола, мы не используем SQL для изменений в продуктовых таблицах. Наш стек позволяет напрямую работать с БД (SQL используется там, где нужный сложные выборки по нескольким таблицам и многим условиям, и то, там не так все просто с точки зрения производительности).

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

  • Update модуль - собственно работа с таблицей и ее журналом. Чтение образа "до", запись образа "после" (с проверкой что "до" не изменился). В реальной жизни может быть достаточно сложным т.к. может работать не с одной, а с несколькими логически связанными таблицами.

  • Validate модуль - контроль валидности данных записи в соответствии с бизнес-логикой

  • Модуль интерактивной работы с записью (позволяет вносить ручные изменения). Вводим уникальный ключ - если запись есть - работаем режиме изменения, если нет - в режиме добавления. После внесенных изменений вызывается Validate модуль и, если ошибки нет - Update модуль.

  • Модуль внешнего ввода. Получает на вход образ "после", читает из таблицы образ "до" (опять, есть запись - изменение, нет - добавление), вызывает Validete модуль, если ошибок нет - Update модуль. Этот же модуль позволяет делать "накат по журналу". Для этого на вход передается не образ "после", а имя библиотеки где лежит журнал и ключ записи с образом "после". В этом случае образ "после" берется из журнала.

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

у автора же для этого как раз поле с версией заведено. Логическая transaction_id так сказать

Мне кажется автор просто не удачно назвал статью, тем самым притянув не ту аудиторию. Статья вообще не про "денежные переводы". И тут я понимаю Вы все прекрасно расписали.

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

Но безусловно интересно почитать детали Вашей реализации.

Мне кажется автор просто не удачно назвал статью, тем самым притянув не ту аудиторию.

Это действительно так, но раз уж это произошло, то почему бы и не обсудить смежную тему?

Я не один раз наблюдал, когда неправильно прописанная (или неправильно понятая) бизнес-логика приводила к непомерному росту затрат на техническую реализацию.

Например, для реализации одной задачи мы сводили все сделки в одну таблицу Excel (выгрузка из системы). Всё хорошо работало, пока все сделки были в одной IT-системе. Но как только появилась вторая система со сделками (при живой первой), технари тут же бросились реализовывать перекачку сделок из одной системы в другую (через какие-то шины, хранилища данных и т.д. и т.п.), чтобы потом объединить все сделки в одной табличке. Хорошо, что я вовремя это заметил и сказал, что их необязательно объединять в системе, исходя из бизнес-задачи табличек может быть больше чем одна, их легче объединить вовне системы, чем внутри и т.д. и т.п., что вызвало значительный вздох облегчения у технолога на другом конце телефонной линии...

Кроме того, транзакции достаточно сильно грузят сервер. Я скажу крамольную вещь, но мы работаем с COMMITMENT CONTROL (*NONE).

И дальше, похоже, вручную делается то, что в более простых случаях (или более умных базах) делается самим движком базы данных.

 Плюс двойное чтение - прочитали запись "до", внесли изменения (получили "после"), перед записью изменений еще раз читаем запись с проверкой - совпадет она с образом "до" - если да, то записываем изменения,

Без уточнения что происходит - не гонка? Потому что между "еще раз читаем с проверкой' и 'то записываем изменения' - записи могут и измениться.

И дальше, похоже, вручную делается то, что в более простых случаях (или более умных базах) делается самим движком базы данных.

Тут вопрос не в том, что может БД. Она (DB2 for i) много что может.

Вопрос в производительности в условиях больших нагрузок. Наши исследования показали что SQL может начать работать нестабильно (в смысле скорости выполнения) в ситуациях, когда один и тот же запрос начинает выполняться параллельно из многих заданий (job) с большой плотностью вызовов. При этом прямая работа с БД не только работает стабильно в таких условиях, но и еще обеспечивает меньшую загрузку системы.

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

Это то, что бывает сложно понять людям, не имеющим иных возможностей работы с БД, помимо SQL. Но у нас, слава богу, такие возможности в системе заложены.

Ну и SQL (точнее, commitment control) не решает проблему журналирования, возможность которого для нашей архитектуры является ключевой.

Без уточнения что происходит - не гонка? Потому что между "еще раз читаем с проверкой' и 'то записываем изменения' - записи могут и измениться.

Да. Могут измениться. Именно поэтому и проводится проверка с выдачей ошибки. Как эта ошибка обрабатывается - тут все зависит от бизнес-логики процеса.

Но блокировка записи на длительное время (пока идет какая-то обработка - это прямой путь к дедлокам и деградации производительности. Что в нашем случае неприемлемо.

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

А чтобы перевести со счета на холд транзакция не нужна - это одна запись (счет - сумма текущего баланса - сумма холдов по счету) - read (с блокировкой), cerbal -= n, curhold += n, update (с разблокировкой). А транзакция по определению, это цепочка связанных изменений в нескольких записях.

И транзакцией это не решается т.к. вы не можете в такой системе держать транзакцию открытой в течении длительного времени

Только главное не налететь на синонимы терминологии. Потому что с точки зрения клиента: пока холд не нулевой - это разве не означает, что есть открытая транзакция(другая, не БД-ная) перевода денег? Ну вот и висит эта транзакция несколько дней.

И все описанный операции - это ручное выполнение протокола этой транзакции, а не БД-ной. Чтобы было то самое 'либо целиком выполнилось, либо сделаем вид что ничего не было'.

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

Ну на самом деле Холды часто бывают не нулевые. Например, на счет у вас 10 000р На холде 0. Пошли по магазинам - тут купили на 1 000, там 2 000, здесь на 3 000... В результате "на счете" 4 000, на холде 6 000. Потом, когда начинают приходить подтверждения по платежным операциям, холд начинает уменьшаться (каждый раз на ту сумму, на которую пришло подтверждение).

Или на заправке - оплачиваете бензина на 2 500р Они встают на холд. Но в бак влезло только на 2 400р. Тут возможны варианты (это уже как банк отработает). Или сначала отмена все операции на 2 500р с возвратом всей суммы с холда на счет и сразу создание новой операции уже на 2 400р с перемещением на холд со счета 2 400р. Или 2 400р остаются на холде до подтверждения , а 100р оформляются как "возврат" т с холда переходят обратно на счет. И так и этак встречал.

У бухгалтеров это называется - проводки. В 1с есть еще регистр накопления для оперативного отображения сумм (агрегат, чтобы в реальном времени транзакции не считать). Всё хранится в журналах, sqrs + event sourcing до того как это стало мейнстримом.

Если балансы корректировать через ячейки - выгонят на мороз и спасибо если не посадят

Последние 2 варианта содержат ошибку в реализации - фиксация транзакции на шаге 2, до update - обнуляет все усилия. Между шагом 2 и 3 могут втиснуться любые транзакции, а блокировки уже сняты.

Вот этот текст, на мой взгляд спорный:

"Особенности: зависит от реализации: при Snapshot Isolation — аналог оптимистической блокировки с версией, при блокировках — аналог пессимистической блокировки"

В стандарте SQL, для этого уровня блокировки, при совершении обновления строк измененных с момента начала транзакции должно выдать ошибку (которую приложение должно быть готово обработать), в той же версионной СУБД PostgreSQL возникает по факту ожидание снятие блокировки изменённой другой транзакцией строки (вдруг там rollback и все же можно), что приводит нас что даже в версионниках будет ожидание как в пессимистическом варианте. Тут конечно автор может предположить о существовании другой версионной СУБД где такого ожидания не происходит, но я о такой не знаю (в другом популярном версионнике Oracle - нет Repeatable Read). В целом такие истории, наверное, лучше рассматривать в привязке к СУБД.

В статье достаточно примитивное представление о технологии перевода денег со счёта на счёт: уменьшить один счёт на некую сумму и увеличить другой счёт на эту же сумму. Если посмотреть на банковскую практику, то перевод раскладывается на технологические этапы, как минимум следующие (допустим, это внутри одного филиала одного банка):

  1. Принять к исполнению документ о переводе со счёта на счёт (поручение/заявление владельца счёта, инкассо, безакцептное и т.д.).

  2. Проверить наличие денег на счёте, заблокировать соответствующую сумму.

  3. Создать проводку о переводе денег со счёта на счёт.

  4. Пометить исходный документ исполненным, снять блокировку суммы.

  5. Создать документы для получателя о приходе денег на счёт.

И каждый этап — добавление (корректировка) записей нескольких таблиц (таблицы с документами, таблицы с проводками и т.д. и т.п.). Соответственно, если там суммы в итоге где-то не сойдутся, то регулярные сверки (контроль) это выявят.

Это понятно, я писал реальную банковскую систему и видел внутри еще 2-3. И там все сложнее. Понятно что там есть стадийность принятия решения, есть системы где есть остаток на начало дня и все остальное досчитывается, есть системы где есть несколько остатков (прогнозный и реальный). Если добавить карты то там еще "чудесатее" - и остаток на карте и счете - несколько дней после совершения операции разный.

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

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

У меня есть знакомый, который регулярно делает управленческие отчёты в своей организации, и при этом из года в год борется с тем, что эти отчёты с чем-то не совпадают (возникают необъяснимые исчезновения денег или наоборот, они появляются ниоткуда). Но реальная проблема у него не в отчётах, а в учёте. И исправлять нужно сначала учёт...

Ой, с отчетами конечно оффтопик: но там основная причина это исправление данных задним числом и отсутствие версионности данных в системе. Если система не позволяет получить отчет за январь, в том виде как он выглядел в момент принятия решения, скажем 10 февраля, то такая фигня будет постоянно.

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

Конкретно в описанном мной случае причина в том, что в учёте сохраняются не все данные, которые в итоге необходимы для построения отчётов. В формализованной отчётности такое редко бывает, а вот управленческий учёт чаще всего требует дополнительных данных, которые по умолчанию не сохраняются.

Также иногда путают порядок этапов: например, сначала сдают отчётность, а потом начинают проверять корректность учёта. В мелких организациях такое бывает.

Если уж офтопить, то до конца: вспомнил случай, когда в банке сидела проверка ЦБ РФ, мы сделали переоценку стоимости одного актива, причём сильно в плюс. Эта переоценка очень не понравилась председателю, было совещание, я сам не присутствовал, но мне сказали, что скандал был жуткий. В обычной ситуации могли бы успеть исправить, но отчётные документы уже успели передать проверяющим...

В обработке платежей это наименьшая из проблем

Плюс еще комплекс контроля платежей. Лимиты, комплаенс контроль...

И кроме внутренних платежей (оба счета в одном банке), есть входящие (из другого банка) и исходящие (в другой банк)

Коллеги, вы слишком глубоко копаете, на мой взгляд, статья простая и тема ее не финансовые системы - тут полный швах. А просто рассказ об уровнях изоляции и проблемах с ними связанных.

Заголовок и первая фраза статьи задают основную тему. )

Да к ИТшникам просто так не приходи, народ дотошный )))

Нужно очень аккуратно формулировать тему и пример, а то получишь 100500 замечаний на другом уровне абстракции )))

У меня начальник возмущался, почему он от меня получает ответ на тот вопрос, который он задал, а не на тот, который он имел в виду...

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

От ваших пояснений толку нет, у вас в коде явно commit на 2 шаге, который закроет транзакцию и 3 шаг будет в новой транзакции. Код лучшее пояснение, а он с ошибками.

Новичок скопирует код и получит ерунду.

Оу, большое спасибо, исправил.

Еще одно замечание: в реальном коде нужно разделять почему не получился перевод: из-за отсутствия средств на счете или из-за ошибки параллельного доступа к счету. Во втором случае код должен содержать повтор операции, о чем в статье не написано. Это применимо ко всем вариантам, кроме варианта с пессимистичной блокировкой.

Вот поэтому и создается платежный документ, сумма ставится на холд, а потом уже платежный документ проверяется. И если он подтвержден, тога уже осуществляется сам перевод. Деньги при этом списываются с холда.

Если платкж заблокировпн, сумма с холда возвращается обратно.

Описывать технические проблемы в отрыве от бизеслогики в корне неверно

Описывать технические проблемы в отрыве от бизеслогики в корне неверно

100%. Причём ошибки в бизнес-логике чаще всего ведут к проблемам, которые техническими средствами неустранимы в принципе.

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

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

Ну так тут еще фееричнее, 3 и 4 вариант, делают все проверки, а потом закрывают транзакцию, и потом просто фигачат update, а то что между закрытием транзакции и update может быть все что угодно, автор не думает.

Спасибо, добавил пояснение что все блоки в одной транзакции

Исправьте код, выше писал, у Вас там явно commit который явно закроет транзакцию, толку от таких пояснений нет, если код кривой.

Если не понимаете как, то просто уберите из 3 и 4 варианта на 2 шаге:
} else { commit();

Большое спасибо, моя грубая ошибка - исправил

Да, так будет работать.

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

Да? Давайте посмотрим попристальнее.

UPDATE accounts
SET amount = amount + :transferAmount,
version = :creditAccountIdVersionAfter
WHERE id = :creditAccountId
AND version = :creditAccountIdVersionBefore;

Что видим? А видим мы прилетевшие откуда-то снаружи значения параметров какой-то там версии. Ну ладно, опустим тот момент, что эта самая версия есть совершенно непонятная и неясно кому и зачем нужная фигня. Но давайте, убедите нас, что эта версия ну никак не может получить одно и то же значение в двух разных конкурентных процессах... а если это вдруг и получится, в чём я лично сомневаюсь, то объясните, почему при рассмотрении внутренних для сервера БД вопросов мы априорно закладываемся на гарантии от внешнего и совершенно неконтролируемого сервером БД процесса.

Шаг 3: Проверка перерасхода. Исключение при отрицательном балансе

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

----------------

А на самом деле всего-то и надо, что

UPDATE accounts
SET amount = CASE id
WHEN :debitAccountId THEN amount - :transferAmount
WHEN :creditAccountId THEN amount + :transferAmount

END
WHERE id IN (:debitAccountId, :creditAccountId);

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

И еще раз - что делать когда счета плательщика и аолучателя в разных банках?

Что делать, когда платкж по каким-то причинам отправлен на ручной контроль и подтверждение его будет через час, два а то и вообще только завтра?

Еще более сложное. В банке каждый день наступает фаза закрытия опредня и перехода на следующий день. Фактически - сведение баланса. Но при этом платежи принимаются и обрабатываются. Но не в том юните, где идет переход на следующий лень, а в другом - юните ночного ваода. А потом, когда основной юниттперешел на следующий день, в него накатываются все изменения из ночи... Как с этим быть?

Статья про сферического коня в вакууме, а не про реальную жизнь.

Статья про сферического коня в вакууме, а не про реальную жизнь.

Несомненно. Но я-то тут каким боком?

И еще раз - что делать когда счета плательщика и аолучателя в разных банках?

А там есть транзакции в том смысле, что в статье? Нет, слово-то одно и то же. Но что мне кажется что банковская транзакция (что бы они там ни имели в виду) и транзакция БД (которые с уровнем изоляции) - сильно не то же самое.

спасибо! очень наглядно

Ребята лучше почитайте Алекса Петрова с его книгой "Распределенные базы данных" там достаточно информации на эту тему и даже больше. Кроме того могу посоветовать Алекса Ху. С его книгами "System design interview" там достаточно информации чтобы увидеть архитектуру подобных решений.

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

На практике в банковских приложениях разных банков РФ последние годы при переводе денег между собственными счетами внутри банка регулярно наблюдаю дикую дичь:

  • Деньги задваиваются (на исходящем счёте ещё не списались, а на новом уже отражаются)

  • Деньги испаряются (на исходящем счёте уже списались, а на целевом все ещё показывает ноль

Это временные проблемы, обычно в течение 10 секунд, реже минуты все устаканивается. Но, судя по пользовательскому интерфейсу, нынче элементарные переводы внутри счетов одного пользователя в одном банке стало принято реализовывать с помощью асинхронных обновлений каждого счета по отдельности, т.е под капотом микросервисы, eventual consistency и вот это всё.

Как пользователя, меня такое поведение банковских приложений расстраивает неимоверно и думаю я не один такой.

Вопрос знатокам - почему всё же банки на практике ушли от нормальных транзакций в рамках одной БД к распределенной асинхронщине?

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

Но, судя по пользовательскому интерфейсу, нынче элементарные переводы внутри счетов одного пользователя в одном банке стало принято реализовывать с помощью асинхронных обновлений каждого счета по отдельности, т.е под капотом микросервисы, eventual consistency и вот это всё.

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

Я в том обсужденнии даже пару старых книг находил. Прошлых веков. Там этих журналов, обеспечивающих работу транзакций - пачками.

Вопрос знатокам - почему всё же банки на практике ушли от нормальных транзакций в рамках одной БД к распределенной асинхронщине?

Одна БольшаяБазаДанных обладает недостатками. Прежде всего по железу. Мейнфрейм - он дорогой и труднодоступный.

Потому нарезали, географически распределили, и откатились назад по времени, когда сообщения ходят между 'клерками-микросервисами'. И каждый из них сам по себе их обрабатывает. Только поскольку протоколы и правила оформления немного забыли - все и показывается в виде 'деньги временно непонятно где'.

Ну я в соседнем комментарии пытался сказать, что это не баг, а фича. Поскольку задача не только отразить текущее состояние счетов, а ещё и сохранить взаимосвязи, почему и на каком основании сделана та или иная операция. И, по-моему, последнее сильно важнее, чем непонятно зачем нужная синхронность. Всё равно банки баланс сводят не чаще, чем ежедневно.

Не знаю как сейчас, а когда был рейсовый механизм взаиморасчётов в ЦБ РФ, то банки могли рассчитаться, даже если ни у кого денег на счёте. Например, банк А платит банку Б 1000 рублей, банк Б платит банку В 1000 рублей, банк В платит банку А 1000 рублей. ЦБ РФ мог обработать все эти платёжки, даже если у каждого из них не было этой тысячи на счёте. Если потребовать синхронности отражений остатков и обрабатывать платёжки по очереди, то такой финт не удалось бы проворачивать без лимита овердрафта.

Ну я в соседнем комментарии пытался сказать, что это не баг, а фича. 

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

И вот тут, если уж асинхронщина - оно должно отображаться не как человек жаловался "на одном счете пропали, а на другом еще не появились", а "На одном счете пропали, но видны как 'Перевод в обработке'- XYZруб".

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

Да не про отдельный туннель речь. Вот мне тут расписывали процедуру перевода. Сильно многоступенчато, да. Объясняя, что все так сложно как раз для того, чтобы в каждый момент знать, где деньги и все балансы сходились. А жалоба была на

  • Деньги задваиваются (на исходящем счёте ещё не списались, а на новом уже отражаются)

  • Деньги испаряются (на исходящем счёте уже списались, а на целевом все ещё показывает ноль

Т.е. эти самые процедуры, предусмотренные правилами, и не выполнятся - деньги берутся из ниоткуда или пропадают.

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

Т.е. эти самые процедуры, предусмотренные правилами, и не выполнятся - деньги берутся из ниоткуда или пропадают.

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

Ну не все :-) У нас, слава богу, это дичи нет.

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

На самом деле там все не так. А примерно так:

  • С баланса счета плательщика сумма переводится на холд

  • Проводится комплекс проверок платежа.

  • Если все проверки пройдены, сумма зачисляется на счет получателя

  • При успешном зачислении суммы на счет получателя она списывается с холда на счету плательщика.

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

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

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

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

сумма зачисляется на счет получателя

Там этот критерий как-то иначе формулируется (списано с корсчёта или как-то иначе). Если СБП, то может быть, там же онлайн всё.

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации