Введение

Долги - важнейшая часть современной экономики, тема эта бездонна и может поглотить тысячи человекочасов, но современная платёжная система обязана уметь оперировать такими сущностями. В данной статье в первом приближении автор покажет, как Mireapay работает с задолженностями, какие они бывают и как ими пользоваться в системе.

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

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

Описание изменений

В фиче MPS-96, автором были добавлены новые ресурсы - долг и проценты по долгу. У каждого долга есть владелец - заёмщик (потому что долг отдаёт заёмщик, а не кредитор, который передаёт средства и получает за них долговые обязательства).

Все долги делятся на несколько типов:

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

  • Кредит - обычная выдача средств кредитором заёмщику под процент;

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

  • Депозит - обычный банковский депозит, с которым все имели дело.

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

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

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

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

Модель данных

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

Таблицы БД

Таблицы для хранения долгов
Таблицы для хранения долгов

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

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

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

Ресурс с типом долг имеет следующие поля:

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

  2. Объем долга, который может быть как отрицательным, так и положительным числом. Например для заёмщика при создании кредита - число всегда отрицательное, а для кредитора положительное. При выплате долга в точности наоборот;

  3. Валюта долга;

  4. Идентификатор кошелька кредитора;

  5. Политика работы с долгом - используется для задания правил обработки долгового обязательства;

  6. Дата завершения - при закрытии долга данное поле задаётся датой, когда долг считается завершённым. Эта дата необходима так же для корректного расчёта процентов.

Для ресурса процент по долгу определены следующие поля:

  1. Идентификатор долга заёмщика - пара из идентификатор кошелька заёмщика и уникального в рамках кошелька идентификатора долга;

  2. Размер выплачиваемых/получаемых процентов;

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

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

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

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

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

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

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

Следует отметить, что пока для долгов не реализована в полной мере детализация платежа (позволяет ограничивать платежи). Данная работа требует отдельной проработки и времени.

Технический долг

В задаче MPS-96 основной целью была реализация технических задолженностей - это такой тип долга, когда система обязана списать средства с кошелька, но они отсутствуют в данный момент. Данная фича нужна для реализации другой, попробуйте угадать в комментариях какой именно. И почему эта фича является киллер-фичей платёжной системы Mireapay.

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

Депозит

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

Кредит

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

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

Кредитная линия

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

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

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

При оплате чего-либо с использованием кредитной линии формируется специальный контракт, согласно которому заёмщик продаёт долг кредитору, от него получает средства в нужном объёме и передаёт их продавцу. Следует отметить, что в данный момент сервис Контракт не умеет работать с графами платежей (контракт - это совокупность платежей), списывает и зачисляет в два приёма, а именно - в начале списывает всё, а как только придут подтверждения о списании с других кошельков и все зачисления можно осуществить на каждый конкретный кошелёк, то производится зачисление. В данном случае для кредитной линии появляется проблема, что у пользователя будут списываться одни деньги, а зачисляться другие, вместо того, что бы формировать нулевую транзакцию, которую сервис Баланс поддерживает. Если ему отправить два платежа одной транзакцией - одну на списание 100 рублей, а другой на зачисление 100 рублей, то баланс пользователя изменён не будет. Так как данная фича (граф платежей) влияет только на комфорт пользователя, то у автора нет времени на ее реализацию.

Нагрузочное тестирование

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

Для тестирования сервиса был собран стенд со следующими характеристиками:

  • Количество под у сервиса равно единице, объем выделенной памяти равен 1Gi, обработка сообщений не более чем 8 штук параллельно, из пульсара сообщения выбираются по одному и подтверждаются по одному;

  • Сервис читает из трёх партиций пульсара сообщения, генерируемые вне кластера;

  • В качестве СУБД используется PostgreSQL под управлением patroni в режиме мастер + две реплики, у каждого 8 ядер и 16 гб ОЗУ.

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

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

С 7:18 автор постепенно увеличивал нагрузку, и в 7:45 стал давать все возрастающую нагрузку, имитируя аварийную работу. В 7:50 количество потоков, генерирующих сообщения было увеличено до 48.

Графики НТ

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

Количество созданных транзакций на создание долга в секунду
Количество созданных транзакций на создание долга в секунду

Беклог сообщений
Беклог сообщений

Время обработки сообщения (.99 перцентиль)
Время обработки сообщения (.99 перцентиль)

Процент использования ЦПУ базой данных (максимумы)
Процент использования ЦПУ базой данных (максимумы)

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

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

  1. Количество параллельно обрабатываемых сообщений равное 8 недостаточно для эффективной утилизации ресурсов и кеширования;

  2. Нехватка памяти для jvm;

  3. Сборщик мусора останавливает jvm пока не очистит память, из-за ее небольшого общего количества;

  4. Чтение сообщений из пульсара по одному не является самым производительным вариантом реализации.

Заключение

Автором был показан новый сервис платёжной системы Mireapay - долговых обязательств. Задолженности, какие бы они ни были - являются важной частью современной экономики и без них ни одна платёжная система не может называться полноценной.

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

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

И хотя разработанный сервис не реализует такие важные возможности как: ограничение выдачи кредитов и приёма депозитов только разрешённым лицам, кредитный мультипликатор и многое другое - представленный сервис является хорошей демонстрацией возможностей платёжной системы Mireapay. Очевидно, что показанный сервис можно использовать не только для выдачи кредитов, небольшими доработками и изменениями в архитектуре и модели данных можно получить реализацию склада, с которого вы сможете получить не просто обезличенный товар, а конкретный, что позволяет напрямую использовать возможности таких систем как "Честный знак".

Послесловие

Пора, пора уже уходить в отпуск, тем более что в прошлый раз он был как раз в конце прошлого февраля. Заодно появится время на то, что бы пересобрать весь кластер (учитывая сколько граблей автору удалось собрать и набить шишек) и добить многопоточность в Rust. Наконец-то вы сможете до апреля отдохнуть от статей автора!

Комментарии (0)