Почему баланс в MySQL — это не архитектура, а будущий инцидент
Ledger — это не таблица с колонкой balance. Это журнал событий: кто, когда, почему и на каком основании изменил деньги. MySQL отлично хранит строки, но плохо переносит фантазию про “текущий остаток как одно поле”. Как только у вас появляются холды, возвраты, частичные списания и рекурренты, баланс начинает жить в голове у разработчика, а не в системе.
Если вы пишете `update accounts set balance = balance - 100`, вы уже в зоне риска:
— гонки между транзакциями;
— двойные списания при ретраях;
— расхождение между платежным провайдером и внутренним учетом;
— невозможность нормально восстановить состояние после сбоя.
Идемпотентность или смерть. Без уникального ключа операции и неизменяемого event_id любая повторная доставка вебхука превращает учет в цирк с конями.
Правильный ledger строится так: сначала append-only запись события, потом агрегация в снэпшоты или проекции. Баланс — это производная, а не источник истины. Источник истины — цепочка проводок с типами: authorize, capture, refund, void, chargeback. Документация — это ложь, логи — истина: если событие не воспроизводится из журнала, значит у вас не учет, а декорация.
MySQL можно оставить как хранилище проводок, но не как место, где “живет баланс”. Баланс должен вычисляться из неизменяемых записей, с блокировками на уровне бизнес-ключа и жесткой дедупликацией входящих событий.
Если хотите спать спокойно, перестаньте апдейтить баланс и начните хранить историю движений денег. Все остальное — костыль на костыле и финтехом погоняет.
Интеграция платежных решений
@payment_integration_ops_arb
Почему баланс в MySQL — это не архитектура, а будущий инцидент
Этот пост опубликован в Telegram-канале Интеграция платежных решений. Подписаться можно по ссылке: @payment_integration_ops_arb.