Комментарии 50
Проблема: Ловушка UPDATE accounts
Решение: Детерминированный стейт-машина
Вдохновившись LMAX Disruptor и принципами блокчейна (но без майнинга и консенсуса, так как мы работаем в доверенной среде), мы пришли к архитектуре Append-Only Log.
Почему не реализовать это в виде (внешней) функции условного Постгреса?
Атомарность мульти-ассетных переводов (Atomic Swaps)
Но ведь для этого в SQL издревле существуют транзакции, о которых вы и сами выше пишете
Идемпотентность (idempotency_key) встроена в ядро. Если сеть моргнула и вы послали запрос повторно с тем же ключом, Qazna вернет результат уже выполненной транзакции, не выполняя её дважды.
Уже решено буквально в любой распределённой (master-master) БД
В сумме выглядит слишком мало для того чтобы пилить с нуля что-то своё
«Спасибо за комментарий! Вопрос "Почему не Postgres" - самый частый, и мы сами с него начинали.
Проблема "Hot Account" (Блокировки) Да, SQL-транзакции дают атомарность (
BEGIN ... COMMIT). Но когда у вас есть один "горячий" счет (например, кошелек биржи или мерчанта), на который летят 1000 платежей в секунду, Postgres выстраивает их в очередь на Row Level Lock. Каждая транзакция ждет освобождения блокировки. В итоге производительность всей системы упирается не в CPU, а в Lock Manager базы данных. Qazna (как и LMAX) использует single-threaded serial execution in-memory. Мы вообще не берем блокировки на уровне данных. Это дает пропускную способность на порядки выше, чем
UPDATE ... WHERE id = ....
Audit Trail как "побочный эффект" vs First-Class Citizen В Postgres история операций — это обычно таблица
history, которую пишут триггером или из кода. Если разработчик ошибся и сделал прямой
UPDATEмимо сервиса - история разъехалась с балансом. У нас архитектура Event Sourcing: баланс - это следствие проигрывания лога. Изменить состояние без записи в лог физически невозможно. Это другой уровень гарантий целостности.
Сложность логики (Smart Tax, Demurrage) Реализовать "умные налоги" или "демередж" (убывание денег со временем) на PL/pgSQL можно, но:
а) Это превращает БД в монолит с бизнес-логикой (тяжело тестировать, версионировать, мигрировать).
б) Вычисление процентов "на лету" при каждом чтении баланса в SQL убьет процессор. Qazna делает это на уровне приложения в памяти за наносекунды.
Итог: Postgres - прекрасная БД общего назначения. Но когда требования к аудиту и пропускной способности становятся специфичными (Financial Grade), специализированное решение всегда выигрывает у универсального.»
Как обеспечивается целостный снапшот вашей системы, когда её нужно поставить на паузу (бэкап, например, или банальное - выключить, перенести и включить для продолжения работы на другом хосте)? Это один файл-образ для всех или каждый сервер приложений свой бэкап создаёт?
Здравствуйте! Спасибо за правильный вопрос.
Мы используем подход Event Sourcing с Append-Only Log (журналом, доступным только для дозаписи).
Целостность: "Истиной" является журнал событий (транзакций), который пишется последовательно на диск. Это обеспечивает атомарность и консистентность по определению.
Бэкап/Перенос: Да, это один файл-журнал (или набор сегментов журнала) для всего инстанса леджера. Чтобы сделать бэкап или перенести систему, достаточно скопировать этот файл.
Восстановление: При старте на новом хосте система просто перепроигрывает (Replay) события из журнала и восстанавливает точное состояние балансов в памяти.
Application Servers: Серверы приложений (бизнес-логика) не хранят свои локальные "бэкапы" денег. Они работают с Леджером как с единым источником правды по gRPC API.
В будущем для ускорения старта (чтобы не перечитывать терабайты истории) мы добавим механизм периодических снапшотов (сброс текущего состояния памяти на диск), но это лишь оптимизация - фундаментом остается лог.
Всем известно, что UPDATE в PgSQL, это долгая операция в сравнении с простым INSERT потому что это INSERT + DELETE.
Решается либо шардированием либо обработкой в кэше
Странно что Вы этот аспект в статье опустили и сразу взяли "быка за рога"
Вы абсолютно правы насчет механики UPDATE в Postgres (MVCC, bloat и т.д.). Мы не опустили этот момент, а наоборот - именно от него и отталкивались.
Проблема стандартных решений, которые вы упомянули:
Шардирование: Отлично работает для независимых данных, но в финансах создает ад распределенных транзакций (2PC). Как только нужно перевести деньги с Шарда А на Шард Б - эффективность падает. Плюс, шардинг не спасает от «горячих кошельков» (когда тысячи пользователей платят на один системный аккаунт).
Кэш: Обработка в памяти - это именно то, что мы сделали :) Только мы сделали этот «кэш» строгим, детерминированным и персистентным (ACID). Обычный кэш (условный Redis) не дает гарантий сохранности данных при сбое питания 'здесь и сейчас'.
По сути, мы заменили тяжелые
UPDATE(Random Write) на сверхбыстрые
INSERTв журнал (Sequential Write), что и дало нам производительность, недостижимую для реляционных баз на том же железе.
REDIS прекрасно пишет на диск(ваше решение при сборе питания будет вести себя ровно так же) + у redis есть master-slave. В чем Вы быстрее?Какие-то тесты делались?
Справедливый вопрос. Мы не утверждаем, что быстрее Redis "в вакууме". Мы утверждаем, что архитектурно выгоднее для финансовых транзакций, если сравнивать в режиме Strict Durability (ACID).
В чем нюансы с Redis:
Persistence (AOF): Чтобы гарантировать сохранность каждой транзакции (как в банке), Redis нужно запускать с
appendfsync always. Это заставляет его делать
fsyncна каждую команду записи. В этом режиме Redis вырождается в производительность жесткого диска (IOPS), и магия "in-memory скорости" на запись исчезает. Если использовать дефолтный
everysec, вы рискуете потерять секунду данных при крахе - для биллинга это неприемлемо.
Логика: Redis - это KV. Чтобы сделать атомарный перевод (снять у "А", добавить "Б", проверить, что у "А" не ушел баланс в минус), вам нужно писать Lua-скрипты. Мы же вынесли эту логику в скомпилированный Rust-код ядра, что убирает оверхед на интерпретацию.
Про тесты: Прямое сравнение "лоб в лоб" (Redis
fsync always+ Lua Script vs Qazna) сейчас готовим как раз для следующей статьи, чтобы быть голословными. Спасибо, что мотивируете ускорить этот бенчмарк!
У меня одного ощущение, что на комментарии к статье отвечает нейронка?
Вы абсолютно правы, скрывать не буду.
Мы строим честный Open Source проект, поэтому и ответ будет честным. В сегодняшних реалиях огромная часть кода пишется в паре с AI. И будем откровенны: мой AI-агент знает кодовую базу, архитектурные нюансы и детали реализации лучше меня самого, так как мы писали это вместе.
Было бы нерационально тратить время на "ручную сборку" ответов, когда инструмент, помогающий строить этот проект, может сформулировать их технически точнее и быстрее. Так что да, AI помогает мне и в коде, и в диалоге с сообществом. Добро пожаловать в новую реальность разработки.
Я бы предложил Вам изучить сначала бухгалтерский учёт, обратив внимание на периоды, их закрытие, активно-пассивные и транзитные счета. А потом еще и посмотреть, как это реализуется в подавляющем большинстве ERP.
"Спасибо за совет". Мы прекрасно понимаем разницу.
Вы описываете функционал ERP-системы (Active/Passive accounts, closing periods, balance sheet reports). Мы же строим Transaction Processing Engine (Ledger) - низкоуровневое ядро, которое обеспечивает атомарность и консистентность перемещения средств.
Аналогия:
Вы говорите про "Бухгалтерию 1С" (ERP).
Мы делаем SQL-движок, на котором эта 1С работает (Ledger).
В нашем ядре есть примитивы (Дебет/Кредит, Двойная запись), но понятия "Закрытие периода" или "План счетов" вынесены на уровень выше - в Application Layer. Пытаться зашить налоговый кодекс или правила РСБУ внутрь высоконагруженного ядра транзакций - это архитектурная ошибка, которая убивает производительность.
Вы описываете функционал ERP-системы
Я описываю то, что придумали сотни лет назад. А посмотреть на ERP предлагаю лишь в качестве примера реализации.
В нашем ядре есть примитивы (Дебет/Кредит, Двойная запись)
Ну если настаиваете.
баланс — это не число в ячейке, а агрегатная функция от истории всех транзакций
И первое, и второе имеет свои недостатки. Но на миллиардах транзакций считать баланс агрегированным слишком медленно. Поэтому, на практике, эти подходы используются совместно. Если закон требует закрытия счетов каждый период, то нет никакого смысла в агрегации истории закрытых периодов.
Каждая транзакция криптографически связана с предыдущей
система откажется запускаться при следующей проверке целостности
При наличии криптографической проверки целостности эта система вообще не запустится даже на довольно скромном массиве из нескольких миллиардов операций. Я уже молчу о какой-то Visa, обрабатывающей по 600 миллионов операций ежедневно.
Алиса хочет обменять свои USD на EUR Боба.
В традиционных базах вам нужно городить сложные саги (Sagas) или двухфазные коммиты (2PC).
Не нужно ни то, ни другое. Во первых, конвертация всё равно идёт через 57 счет, во-вторых, забыли о курсовых разницах.
Метод execute принимает список движений (
movements) и выполняет их все или ничего
Блокируя полностью баланс Алисы на всё время, пока выясняется баланс Боба в другом банке? А если Алиса - это маркетплейс, баланс которого изменяется по тысячи раз за секунду?
Финансовый мир сейчас — это тысячи закрытых "велосипедов".
Финансовый мир сейчас - это жесткие требования ПБУ и МСФО. Поэтому сначала потрудитесь обеспечить поддержку всех нормативных требований.
Спасибо за развернутый комментарий. Видно, что вы глубоко в теме энтерпрайз-учета. Давайте разделим мух и котлет.
Производительность:
Агрегация: Мы используем Event Sourcing + Snapshots. Баланс всегда лежит в памяти (Hot State) готовым числом. Нам не нужно агрегировать историю при чтении.
Криптография: SHA-256 на современном CPU - это дешево. 600 млн операций Visa в сутки - это всего 7,000 TPS. Наши бенчмарки на MacBook Air M2 показывают 200,000 TPS с хешированием (результаты в репо:
docs/benchmarks.md). Запас прочности - два порядка.
Блокировки:
Вы мыслите в парадигме классических БД с Row Lock. У нас архитектура Single Writer (как в Redis/LMAX). Транзакции исполняются строго последовательно в памяти. Никаких блокировок "пока выясняется баланс Боба". Если Боб в другом банке - это асинхронный процесс (Saga) на уровне приложения, а не долгая транзакция в ядре.
ПБУ/МСФО и 57 счет:
Главный тезис: Ledger != ERP.
Ядро Linux не знает про НДС, но на нем работает SAP.
Ядро Qazna дает атомарный мульти-валютный перевод. Если для ПБУ нужно прогнать его через 57 счет (Транзитный) - вы просто формируете транзакцию из 4 движений (Дт Алиса -> Кт 57; Дт 57 -> Кт Боб). Ядро исполнит это атомарно.
Наша цель - дать надежный и быстрый двигатель, а "правила дорожного движения" (МСФО) настраиваются водителем (Application Layer).
Баланс всегда лежит в памяти (Hot State) готовым числом.
Как и когда он туда попадет при старте системы, если в БД миллиарды транзакций? И что будете делать, если памяти не хватит для всех счетов? Я уже молчу о HashMap, который даже на миллионах записей не отличается эффективностью, в отличии от BTree.
600 млн операций Visa в сутки - это всего 7,000 TPS.
Всего за десять лет получаем свыше 2 триллионов транзакций. Сколько времени потребуется, только чтобы их прочитать при старте? А как хранить эти транзакции на диске подумали? Ведь всё равно данные и индексы в итоге потребуется хранить постранично, так как ничего лучшего пока не придумали.
Никаких блокировок "пока выясняется баланс Боба". Если Боб в другом банке - это асинхронный процесс (Saga) на уровне приложения, а не долгая транзакция в ядре.
Но в этой парадигме баланс Алисы не имеет смысла. На практике же, проводкой 57-52 баланс обеих счетов меняется транзакционно. А уже потом, 52-57 эти средства или вернутся на баланс 52 счета, либо на 57 счете произойдет конвертация и возникнут курсовые разницы, что попадет на баланс соответствующих счетов.
Главный тезис: Ledger != ERP
Точно так же, как и Hana не ERP. Но обязана обеспечивать все примитивы, необходимые ERP, с высокой производительностью.
Ядро Qazna дает атомарный мульти-валютный перевод
Эта операция по определению (ПБУ 3/2006) не атомарная и состоит, как минимум, из трех транзакций как на стороне Алисы, так и на стороне Боба. О чём я и веду речь.
Отличные вопросы по архитектуре. Давайте углубимся.
HashMap vs BTree: Для Point Lookup (поиск по ID)
HashMapдает O(1), а
BTree— O(log N). В высоконагруженном процессинге, где нужен именно Random Access, хеш-таблица выигрывает. BTree нужен для Range Queries, которые в ядре транзакций редки.
Про память: Если аккаунтов станет больше, чем RAM (Hot State), мы используем Tiered Storage: вытесняем холодные аккаунты на NVMe (например, через RocksDB/LSM-Tree), оставляя в памяти только активные. Это решенная задача (см. Redis on Flash).
Триллионы транзакций и старт: Вы предполагаете, что мы читаем историю. Мы не читаем историю при старте. Мы читаем Snapshot (Слепок Стейта). Размер снапшота зависит от количества счетов, а не от количества транзакций. Если у вас 100 млн счетов по 100 байт - это 10 ГБ. Загрузка 10 ГБ с SSD в память - это секунды (даже через
mmap). История (2 трлн записей) лежит холодным грузом на дисках/S3 и поднимается только для аудита.
Атомарность и Валютный обмен: Вы правы, бизнес-процесс (Сага) может быть не атомарным во времени. Но шаг этого процесса (проводка) обязан быть ACID. Qazna гарантирует: В рамках одного шага (одного вызова execute) деньги не исчезнут и не появятся из воздуха. Либо спишутся и зачислятся везде, либо нигде. На этом примитиве вы и строите свои сложные Саги по ПБУ 3/2006.
Для Point Lookup (поиск по ID)
А почему Вы рассматриваете только поиск, а не добавление записей? Основные проблемы с хешами начинаются именно на последнем этапе.
Мы не читаем историю при старте.
система откажется запускаться при следующей проверке целостности
Какая из двух этих фраз ложная?
Загрузка 10 ГБ с SSD в память - это секунды
А запись снапшота? А обработка WAL от момента последнего снапшота?
Но шаг этого процесса (проводка) обязан быть ACID. Qazna гарантирует
Это так же гарантирует любая РСУБД. А вот каким образом Qazna гарантирует READ COMMITED, REPEATABLE READ, SNAPSHOTи SERIALIZABLE изоляцию транзакций я вообще в статье не заметил. Ну разве что
Однопоточная (для детерминизма) стейт-машина, которая принимает батчи операций и применяет их последовательно
что, по сути, обозначает, что любая транзакция блокирует вообще всё и ни о какой конкурентной обработке транзакций и речи быть не может.
Одна транзакция при разноске может модифицировать сотни различных "балансов" (центры затрат/доходов, остатки на складе, слои себестоимости, адреса хранения, начисленные налоги и т.п.), и при этом модифицировать мегабайты, как какая-нибудь накладная с сотнями позиций.
Вы задаете правильные вопросы, но исходите из предположения, что "Финансовая система = SQL-база с дисковым хранением".
Проверка целостности: Фразы не ложные. Целостность проверяется по цепочке:
Genesis -> ... -> Snapshot_Hash -> WAL_Hash. Нам не нужно перепроверять всю цепочку от Генезиса при каждом старте, если мы доверяем подписанному Снапшоту. Это стандартная практика (Pruning) в блокчейнах и логах.
Изоляция транзакций: Вы ищете
READ COMMITTED, а мы даем SERIALIZABLE (строжайший уровень). За счет чего? За счет Sequencer (Single Thread). У нас нет состояния "гонки", нет Deadlock-ов, нет "грязного чтения", потому что транзакции исполняются строго одна за другой. В мире In-Memory это быстрее, чем тратить 80% времени CPU на переключение контекста и Mutex Contention в многопоточной среде. (Почитайте про LMAX Disruptor — архитектуру Лондонской биржи).
"Транзакция блокирует вообще всё": Верно. Именно поэтому Qazna - это Ledger (Реестр проводок), а не ERP. Если вам нужно "модифицировать мегабайты и пересчитывать склады" - делайте это в ERP. В Qazna вы присылаете только финальные проводки ("Списать X, Зачислить Y"). База, которая делает атомарный перевод, должна заниматься переводом, а не расчетом себестоимости слоев на складе.
Мы не заменяем Oracle ERP. Мы заменяем медленный слой
UPDATE accountsна специализированный быстрый движок.
"Финансовая система = SQL-база с дисковым хранением".
SQL - не обязательно, а вот дисковое хранение в кластере - обязательно.
доверяем подписанному Снапшоту
Для того, чтобы ему доверять, нужно сначала проверить все цепочки.
транзакции исполняются строго одна за другой
Что ставит жирный крест на применимости системы в реальном мире, так как даже внутри корпоративной среды возникают задержки при выполнении транзакций, не говоря уже о B2B.
В Qazna вы присылаете только финальные проводки ("Списать X, Зачислить Y"). База, которая делает атомарный перевод, должна заниматься переводом, а не расчетом себестоимости слоев на складе.
Проблема в том, что в реальной жизни это не нужно. Даже если взять простейшую операцию физического лица со своим счетом, то транзакция - это далеко не одна проводка. Атомарно должны выполниться множество проводок и рассчитаться множество "балансов". "Баланс" самого счета - это одно, "баланс" снятия наличных за день - это другое, "баланс" оплаты услуг и товаров - третье, "баланс" переводов самому себе по СБП - четвертое, "баланс" переводов сторонним лицам по СБП - пятое, "баланс" корреспондентских счетов банка - шестое. Это даже не считая целый ворох аналитических и нормативных "балансов".
Я уже молчу о необходимости оперативного получения информации по самым различным срезам, где 100 байт на баланс будет катастрофически мало для размещения всех необходимых аналитик. И вопрос еще, как эти аналитики собираетесь индексировать.
Вадим, мы с вами подходим к главному водоразделу: Monolith vs CQRS.
Trust & Snapshots: В распределенных реестрах источником истины является не "вся цепочка с 1970 года", а Кворум. Если Снапшот криптографически подписан валидаторами (Consensus), узел ему доверяет. Это база работы любого блокчейна/Raft-кластера. Никто не реплеит терабайты логов при ротации ноды.
Sequential Execution: Вы предполагаете, что "одна за одной" = "ждем завершения I/O". В архитектуре LMAX Disruptor / Redis (которую используем мы) транзакция — это чистая операция в памяти (In-Memory), занимающая наносекунды. Никаких сетевых вызовов или Disk Seek внутри критической секции стейт-машины нет. Поэтому "очередь" не тормозит, она летит. (См. наши бенчмарки: 200k TPS на одном ядре ноутбука).
Сложные балансы и аналитика: Вы описываете классический "толстый" Enterprise-монолит, где в одной SQL-транзакции считаются и деньги, и налоги, и склад. Это работает, но плохо масштабируется. Мы проповедуем CQRS:
Write Side (Ledger): Максимально просто и быстро. Проверяем только критичные лимиты (Hard Limits) и двигаем деньги.
Read Side (Analytics): Стримим события (через Kafka/gRPC Stream) в DWH/ClickHouse, где строим ваши "100 видов аналитических балансов".
Пытаться посчитать себестоимость слоев на складе синхронно в момент оплаты картой - это архитектурный тупик. Мы разделяем эти процессы.
Пытаться посчитать себестоимость слоев на складе синхронно в момент оплаты картой
Вот поэтому я рекомендовал Вам ознакомиться сначала с бухгалтерским учётом. Чтобы не было каши в голове, когда реализация и оплата сваливаются в одну кучу. Это вообще разные транзакции, в общем случае, на разные суммы, и разделенные во времени.
Вадим, принимаю замечание. С примером про "синхронный расчет себестоимости в момент оплаты" я действительно погорячился - это разные хозяйственные операции.
Но именно это и подтверждает мой тезис! Раз "Оплата" и "Реализация" разнесены во времени и по сути - значит, их должны обслуживать разные подсистемы.
Ledger (Qazna): Идеально обрабатывает "Оплату" (Движение денег, Лимиты).
ERP: Обрабатывает "Реализацию" (Склад, Налоги, Себестоимость).
Мы не пытаемся заменить ERP. Мы даем ERP быстрый и надежный инструмент для первой части - для Денег. Давайте на этом зафиксируем консенсус: "Мухи - отдельно, котлеты - отдельно". Было приятно подискутировать с профессионалом.
Мы даем ERP быстрый и надежный инструмент для первой части - для Денег.
Так не получится, потому что 50/51 счет будет корреспондировать с 90, 60, 72 и т.п., где опять необходим ворох аналитик, не говоря уже о курсовых/суммовых разницах.
Баланс (не в Вашем понимании, а по ПБУ и МСФО) - он един для всех балансовых счетов.
Давайте перейдем от теории к практике (коду), который выложен в Open Source.
"Как читать историю при старте?": Мы не читаем историю. Мы читаем Snapshot (Binary Dump of Memory).
Код: См.
core/ledger/src/lib.rs -> метод
load_snapshot().
Логика: При старте мы маппим в память файл снапшота (секунды для десятков ГБ) и доигрываем WAL только с момента его создания.
Тест: Запустите
cargo test snapshot_recoveryв проекте - он демонстрирует именно этот сценарий: старт -> транзакции -> снапшот -> рестарт -> доигровка.
"Qazna гарантирует SERIALIZABLE?": Да. Гарантия обеспечивается архитектурой Single Writer (как в Redis или LMAX Disruptor).
Код: См. структуру
Ledger в
core. Все изменения стейта идут через
RwLock::writeв одном потоке исполнения. Конкуренции нет физически.
Изоляция: Транзакция Т2 всегда видит финализированное состояние после Т1.
"HashMap не эффективен / Проблемы с добавлением": Мы используем
AHash(один из самых быстрых в мире) и Rust
HashMap.
Бенчмарк: Чтобы снять вопросы про скорость добавления и чтения, мы сделали специальный бенчмарк-сюит.
Запуск:
cargo run --release --bin qazna-bench -- --target qazna --count 1000000На MacBook Air M2 это выдает ~200k TPS. Это включает запись в WAL (fsync) и обновление HashMap.
Предлагаю вам склонировать репозиторий и прогнать
cargo testи бенчмарки. Буду рад обсудить конкретные места в коде, которые покажутся вам узкими.»
Дискуссия теряет смысл. Я Вам на пальцах показал, что для тысячи счетов потребуется миллион "балансов", причем с ворохом аналитик, которые тоже надо как-то индексировать, а Вы продолжаете отстаивать свою архитектуру, упрощённую до состояния ненужности в реальной жизни.
Вадим, я услышал вашу позицию. Она сводится к тому, что "Система должна уметь всё сразу: и проводки двигать, и аналитику по 100 измерениям считать в той же транзакции, и индексы перестраивать". Это подход Монолитной ERP, и он абсолютно валиден для корпоративного учета.
Мы же строим Transaction Processing Engine. Его задача - быть "двигателем внутреннего сгорания": делать 200,000 оборотов в минуту (проводок), не разваливаясь. А магнитола, кондиционер и навигатор (аналитика, налоги) - это внешние системы.
Для вас "упрощение до ненужности" - это баг. Для HFT и процессингов - это фича, за которую платят скоростью. Спасибо за уделенное время и жесткий челлендж архитектуры! Это было полезно.»
Да не будет она двигателем. Потому что она не отражает реального положения дел. Начало и конец транзакции могут быть значительно разнесены по времени. И при этом в этот интервале скорее всего будут и еще транзакции по этому же счету.
Вы бы хоть поинтересовались для начала ка все это реализовано в банковских АБС... И сколько там операция связано с простым переводом денег с одного счета на другой.
Виктор, я прекрасно понимаю, о чем вы говорите. В классических АБС "Проводка" - это часто тяжелый процесс с кучей проверок, блокировок и курсоров, который может висеть секунды или дольше (если ждать ответа от смежных систем).
Именно поэтому банковские переводы ночью не ходят или ходят часами. Потому что "начало и конец разнесены", и база держит контекст.
Мы предлагаем архитектуру "Smart Saga, Dumb Ledger":
Длительность: Бизнес-транзакция (Платеж) действительно длится часами.
Исполнение: Но она разбивается на серию мгновенных шагов в Леджере (Hold -> Wait -> Capture).
Конкурентность: В промежутке между Hold и Capture счет не заблокирован для других операций (кроме замороженной суммы).
Qazna - это движок для выполнения этих мгновенных шагов. Мы не отрицаем сложность бизнес-процесса, мы просто убираем эту сложность из ядра БД на уровень Оркестратора. Это позволяет процессить тысячи платежей в секунду, а не десятки.
Еще про контроль платежей забыли :-)
В реальной жизни транзакция может полностью завершиться через несколько часов. Или через день... И что, баланс все это время будет блокирован?
В реальной жизни все это решается холдами.
Тут не совсем так. С точки зрения БД транзакция не будет длится часами или днями. Просто выполняется сначала транзакция резервирования, а потом она либо сторнируется, либо заменяется подтверждающей транзакцией.
Другой вопрос, что для этого под каждое резервирование в описываемой системе потребуется создавать отдельный "баланс", что приведет к неуправляемому росту количества таких "балансов" и необходимости добавления в эти "балансы" целого ряда аналитик.
Вадим, вы абсолютно правы про механизм Холдов (Reserve -> Capture), но ошибаетесь насчет "роста балансов". Для Холда не создается отдельный
AccountID. В структуре Аккаунта уже заложены поля
Availableи
Held. Резервирование - это просто перемещение цифр внутри одной записи аккаунта Алисы. Никакого роста базы и индексов не происходит. Это O(1) операция update.
Вот именно. Тут не будет одной транзакции. Их будет несколько. И все они будут завязаны на один платежный документ.
Виктор, мы с вами говорим об одном и том же, но разными словами. Вы абсолютно правы:
Платежный документ - это долгоживущий процесс (Saga). Он может длиться часы.
Он состоит из нескольких шагов (Холдирование, Проверка AML, Списание, Зачисление).
В старых АБС часто пытались обернуть весь этот процесс в одну длинную блокирующую транзакцию ("начало и конец разнесены"). Это приводило к тем самым deadlock-ам и блокировкам счетов, о которых вы пишете.
Мы предлагаем Event-Driven подход:
Платежный документ - это Saga (оркестратор на уровне приложения).
Каждый шаг этой саги (например, "Холдирование") - это микро-транзакция в Qazna. Она выполняется наносекунды и не блокирует счет для других операций (кроме суммы холда).
Между шагами счет доступен для других платежей.
То есть Qazna - это движок для исполнения шагов вашего сложного процесса, а не замена самого процесса. Мы не спорим с реальностью АБС, мы даем вам более быстрые "пишущие машинки" для этих операций.»
То есть Qazna - это движок для исполнения шагов вашего сложного процесса, а не замена самого процесса.
Ну и каким образом тогда Qazna обеспечивает ACID, если транзакциями управляет не она? Ведь транзакция - это все шаги сложного процесса. Либо все они фиксируются, либо все откатываются. При этом необходима ещё и изоляция процессов. Как минимум, один процесс не должен видеть "микро-транзакции", совершенные другим процессом, до тех пор, пока этот другой процесс не зафиксирует всю транзакцию.
Вадим, вы требуете Global ACID (или Distributed Transactions/XA) для длительных процессов. Это классический подход 90-х и 00-х (J2EE, Oracle Two-Phase Commit).
К сожалению, Global ACID не масштабируема. Нельзя держать блокировку на счетах в двух разных банках 2 часа. Весь мир (Google, Uber, Netflix, современные Neobanks) перешел на паттерн Saga.
В Saga:
ACID гарантируется только на уровне атомарного шага (микро-транзакции в Qazna).
Изоляция обеспечивается семантически (через статусы "Held", "Pending").
В вашем подходе: "Никто не видит списания в холд, пока нет коммита".
В нашем подходе: "Все видят, что деньги в холде. Их нельзя потратить второй раз".
Это не "потеря ACID", это сознательный отказ от блокировок в пользу доступности и скорости (CAP-теорема). Qazna обеспечивает строгую консистентность (Strict Consistency) данных в любой момент времени, просто "единица работы" стала меньше.
Вадим, вы требуете Global ACID (или Distributed Transactions/XA) для длительных процессов
Нет, конечно, и я писал об этом выше.
Весь мир (Google, Uber, Netflix, современные Neobanks) перешел на паттерн Saga.
Назовите хотя бы одну ERP. Не назовёте. Потому Saga не натягивается никак на требования к бухгалтерскому учёту. Не верите?
Google - OEBS, Uber - тоже OEBS, Netflix - SAP.
Всё очень просто. Saga не обеспечивает изоляцию транзакций (которые состоят из множества "микро-транзакций"), о чём я явно написал и что Вы почему-то проигнорировали.
ACID гарантируется только на уровне атомарного шага (микро-транзакции в Qazna).
А одна транзакция, как Вы сами признали, состоит из множества "микро-транзакций". И она тоже должна быть ACID. Так что просьба ответить на вопрос, а не натягивать сову на глобус:
"Ну и каким образом тогда Qazna обеспечивает ACID, если транзакциями управляет не она?"
Вадим, вы правы: Google и Uber действительно используют Oracle/SAP для бухгалтерского и финансового учета (Accounting). Это стандарт индустрии, и глупо это отрицать.
Но давайте разделим уровни:
Execution Layer (Процессинг): Когда вы вызываете Uber, списываются деньги и блокируется карта - это работает не на SAP, а на Schemaless/Spanner/Cassandra. Здесь нужны миллионы TPS, низкая латентность и Eventual Consistency (Saga). Это ниша Qazna.
Accounting Layer (Учет): Раз в сутки/месяц эти данные агрегируются и улетают в Oracle EBS для сведения баланса, налогов и отчетности. Здесь нужен строгий Global ACID и планы счетов.
Мы не предлагаем выкинуть Oracle из бэк-офиса. Мы предлагаем инструмент для построения того самого Execution Layer, который стоит перед бэк-офисом и принимает на себя удар пользовательской нагрузки.
Спасибо за дискуссию, она помогла мне четче сформулировать позиционирование продукта. Удачи!
Когда вы вызываете Uber, списываются деньги и блокируется карта - это работает не на SAP, а на Schemaless/Spanner/Cassandra.
Вы уверены? А как же 2 300 независимых кластеров MySQL?
Я подскажу. По той же самой причине. Для изоляции транзакций, а не только "микро-транзакций".
Абсолютно верно! В реальной жизни это решается Холдами (reservation), а не блокировкой базы данных. Qazna поддерживает паттерн "Hold" на уровне архитектуры:
Hold:
Execute(Debit Alice, Credit Alice_Hold_Account). Это атомарная, мгновенная операция. Деньги "заморожены" на спецсчете.
Wait: Ждем 2 часа подтверждения от другого банка (база Qazna в это время НЕ заблокирована, она обслуживает тысячи других клиентов).
Capture:
Execute(Debit Alice_Hold_Account, Credit Bob). Вторая мгновенная операция.
Вы правы, нельзя держать открытой SQL-транзакцию 2 часа. Мы и не держим. Мы делаем мгновенные переходы состояний (State Transitions).
Надеюсь, что прежде чем начинать какой-то проект, который вы сами позиционируете как что-то фундаментальное/инфраструктурное, сравниваете с Linux, то вы сравнили его по фичам/производительности/надежности с другими аналогичными проектами.
Контроль целостности за счёт однопоточности и хешей транзакций по цепочке (наподобие того как это делается в гите или блокчейне) вроде и правда позволяет ответить на вопрос, что всё действительно ок.
А что на счёт резервирования? Как сейчас решается вопрос, например, отказа железа? Как восстанавливать данные в случае повреждения? Если делать какой-то банк, то данные рано или поздно отказ оборудования произойдёт, а поднимать всё из бэкапов - риск простоев и риск потери части свежих данных, а это буквально деньги.
Есть ли какая-то сравнительная таблица с альтернативами? (По фичам и производительности)
Вот например мне одно из первых при поиске выдаёт tigerbeetle, которых хоть и написан не на "blazingly fast" / "memory safe" Rust, а на zig, но для доказательства корректности он проходит кучу тестов (в том числе на обработку разных сбоев: сетевых и диска).
Какие у вас тесты?
TigerBeetle - отличный проект, и мы внимательно следим за их развитием (особенно за их реализацией VSR и Zig-спецификой). Если вам нужно просто считать трансферы с максимальной скоростью на уровне железа - берите TigerBeetle, они в этом чемпионы.
В чем разница и почему мы не взяли TB: TigerBeetle позиционируется как Ledger Database. Qazna - это Financial Protocol/OS. В TB вы не загрузите логику "Умных налогов" (Smart Tax), правила AML-комплаенса или динамический демередж внутрь стейт-машины ядра. Вам придется городить это сверху в application-слое, получая те же round-trip задержки и рассинхрон. Мы зашили эту бизнес-логику в ядро (Rust), что гарантирует атомарность: транзакция либо проходит вся (с налогами и проверками), либо не проходит вообще.
Про надежность и железо: Сейчас архитектура Single Leader.
Отказ диска/хоста: Восстановление идет через Replay лога операций (Journal).
HA (High Availability): Прямо сейчас реализован стриминг транзакций (тот же механизм, что и для Индексера). Это позволяет поднять Standby-ноду в режиме "Hot Replica", которая получает данные асинхронно.
Планы: Честный консенсус (Raft или тот же VSR) стоит в роадмапе, но для стадии MVP мы выбрали простоту и скорость внедрения бизнес-фич.
Сравнительную таблицу подготовим во второй части статьи с бенчмарками. Спасибо за наводку.
Кажется вы переизобрели durable execution, в частности restate.dev
Хорошее наблюдение! Мы действительно разделяем философию Durable Execution (код как стейт-машина), которую проповедуют Restate и Temporal.
Разница в специализации:
Restate/Temporal - это General Purpose оркестраторы для любых микросервисов (вызов Lambda, ожидание HTTP).
Qazna - это Domain Specific Ledger. Мы оптимизировали этот подход специально для финансовых транзакций (Double Entry, детерминизм чисел, неизменяемый журнал).
Так что да, мы "двоюродные братья" по архитектуре, но решаем разные бизнес-задачи.
не понимаю как цензура пропустила такой запрос.
UPDATE users SET balance = balance - 100 WHERE id = 'ALICE';
наверно надо сразу в полицию звонить ?
я не могу привести пример, почему это плохо и почему нельзя публиковать такое даже в качестве примера. Потому что Это и есть пример того, как делать нельзя.
«Абсолютно согласен!
UPDATE balance- это зло. Именно поэтому мы и создали Ledger, который запрещает такие апдейты конструктивно. Спасибо, что подсветили проблему.»
отнюдь. как раз такое обновление баланса даёт возможность реплицировать транзакцию на другой сайт и, либо, при такой записи избежать коллизии, либо, наоборот, поймать и успешно обработать коллизию, если запрос чуть изменить.
А в чем принципиальные отличия с системами вроде https://github.com/tigerbeetle/tigerbeetle?
Мы с большим уважением относимся к команде TigerBeetle, они делают фундаментально важную вещь - распределенную базу данных для финансового учета с упором на safety и performance (Zig, VSR, strict consistency).
Принципиальное отличие Qazna в том, что мы строим не просто базу данных, а Финансовое Ядро (Financial Kernel). Если TigerBeetle - это идеальный двигатель, то Qazna - это двигатель + шасси с электроникой и системой безопасности.
Вот 3 ключевых отличия:
Встроенная бизнес-логика (Sentinel Engine) TigerBeetle фокусируется на учете (дебет/кредит). В Qazna прямо в ядро вшита машина состояний для Compliance. Мы поддерживаем Smart Tax (авто-налоги), Demurrage (латание валюты) и Sanctions Screening синхронно в момент транзакции. В TigerBeetle эту логику пришлось бы писать "сбоку" в слое приложения.
ISO 20022 как First-Class Citizen TigerBeetle оперирует 128-байтными полями
user_data(opaque blobs). Для нас метаданные стандарта ISO 20022 (pacs.008, remitance info) являются частью протокола ("Glass Pipeline"). Регулятор видит не только "кто и сколько", но и "за что", причем эти данные криптографически связаны с транзакцией.
Ликвидность и Маршрутизация Qazna имеет встроенный механизм Liquidity Routing («BGP для денег»). Ядро умеет искать путь обмена через цепочку провайдеров ликвидности (Atomic Swaps). Это уже уровень протокола обмена ценностью, а не просто хранения балансов.
Резюмируя: TigerBeetle - это специализированная БД, поверх которой можно построить банк. Qazna - это готовый бэкенд-протокол (ledger + compliance + routing) для построения суверенной финансовой системы «из коробки».

От стартапа к протоколу: Почему мы решили написать свой «PostgreSQL для финансов»