Как стать автором
Обновить
20
1.1
Виктор Поморцев @SpiderEkb

Консультант направления по разработке

Отправить сообщение

Ну на самом деле Холды часто бывают не нулевые. Например, на счет у вас 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р оформляются как "возврат" т с холда переходят обратно на счет. И так и этак встречал.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Естественно. Если вместо простого if поставить вызов какой-то функции (с разворачиванием-сворачиванием стека, а еще, не дай бог, выбросом исключения), а потом вызвать все это 100500 раз, разница в производительности будет заметна. Но кого все это волнует? Главное же концептуальность. А если у потребителя все это начинает тормозить - пусть заменит один старый сервер на два новых, помощнее и подороже.

Зато когда MS заявляет что поддержка Win10 заканчивается, а для перехода на Win11 потребуется обновить комп, нас это дико возмущает - да как они посмели...

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

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

Но не суть.

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

Есть ощущение, что все это "концептуальность ради концептуальности".

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

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

Функциональное программирование представляет собой такой подход, при котором программа строится только на использовании функций, которые передаются как аргументы, возвращаются из других функций

А где та самая первая функция, которая не вызывает других функций, а делает все сама? Является ли она частью функциональной парадигмы, или это процедурность, лежащая фундаменте всего остального?

В мое время 5080/1 крутился под NT 3.51

Я, собственно, не к сравнению OS/2 а NT, а к тому, что NT никогда не была секретом, но развивалась параллельно с Win 3.1/95/98

Самому приходилось сидеть на Win 3.11, 98SE, NT4, Win2000, XP, Win7, Win10.

OS/2 не довелось (увы) попробовать. Пробовал в те времена Linux (и RH и SL), но не впечатлился.

Сейчас - Win10 на одном компе и Linux Mint 22.1 на другом. А пишу вообще под AS/400 (которая сейчас IBM i), т.е. много времени в эмуляторе IBM5250 приходится проводить.

Ну, вообще, NT не была тайной. Она вполне себе развивалась параллельно какое-то время.

Я помню, в 90-е была альтернатива - ставить Win 3.11 если железо слабое, или WinNT 3.51 если железо позволяет (требования не помню сейчас, но там было что-то типа 386 и 2-4Мб памяти чтобы оно нормально работало).

Интерфейсы у них одинаковые (внешне) были.

Потом аналогично - Win95/98 или NT4, чуть позже - Win2000 (NT5)

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

1
23 ...

Информация

В рейтинге
1 505-й
Откуда
Екатеринбург, Свердловская обл., Россия
Работает в
Дата рождения
Зарегистрирован
Активность