open atlas
↑ К треку
Разборы System Design SDC · 05 · 10

Данные и деньги: собери корректный двигатель денег

Практический проект: собери небольшой сервис кошелька/ledger, что переживает ретраи и конкуренцию — двойная запись, клиентские ключи идемпотентности, атомарный перевод против потерянного обновления и проход сверки — доказанный адверсариальными тестами.

SDC Senior ◷ 240 min
Уровень
ОсновыJuniorMiddleSenior

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

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

Проект
0 из 8
Цель

Собери небольшой сервис кошелька/ledger (любой язык + реальная транзакционная БД), что двигает деньги между счетами корректно под ретраями и конкуренцией. Он должен реализовать append-only ledger двойной записи, принимать клиентские ключи идемпотентности, выполнять атомарные переводы, что не могут списать дважды или потерять обновление, и включать проход сверки, что проверяет схождение книг — затем доказать всё это адверсариальными тестами.

Требования
Критерии приёмки
  • Append-only ledger, где балансы вычисляются суммированием неизменяемых записей, со скриптом сидинга и запросом, что печатает баланс любого счёта и его историю записей.
  • Перевод, что атомарен (обе записи или ни одной) и идемпотентен (повторный ключ возвращает исходный результат без второго движения; конфликтующий ключ отклоняется).
  • Проходящий тест шторма ретраев: N конкурентных идентичных запросов производят записи ровно на один перевод и единственную смену баланса.
  • Проходящий тест конкурентных дебетов: много переоверкоммиченных конкурентных переводов оставляют счёт неотрицательным и глобальный инвариант дебеты = кредиты целым.
  • Команда сверки, что пересчитывает балансы из записей и репортит книги как сбалансированные (и пометила бы внесённый дисбаланс), плюс заметка дизайна, объясняющая выбор изоляции/блокировки и один отказ, что сверка ловит.
Senior-стретч
  • Добавь межшардовый перевод как saga: раздели счета на две БД, реализуй дебет→кредит с компенсирующим действием, смоделируй in-flight pending-состояние и покажи, как сверка исцеляет перевод, что ты намеренно прервал между двумя ногами.
  • Добавь снимок/материализованный баланс и обслуживай чтения из него (снимок + свёртка хвоста свежих записей), затем докажи, что снимок всегда равен полной пересвёртке — оптимизация чтения event-sourcing без потери проверяемости.
  • Добавь внешний 'PSP'-заглушку, чей ответ можешь дропнуть/задержать, поставь списание за ключом идемпотентности и покажи, что потерянный ответ + ретрай клиента не списывает дважды, пока дневная сверка с отчётом PSP всё же ловит расхождение.
  • Нагрузь горячий счёт: сделай один счёт целью большинства переводов и измерь, как твой выбор блокировки его сериализует; сравни пессимистичную блокировку против атомарной условной записи против оптимистичной/CAS при высокой конкуренции и отрепорти пропускную способность и частоту ретраев.
Вспомните перед уходом
  1. 01
    Почему выводить балансы суммированием неизменяемых записей, а не хранить изменяемое поле баланса?
  2. 02
    Как два адверсариальных теста доказывают два ядровых инварианта?
  3. 03
    Что проход сверки ловит, чего один горячий путь не может?
Итог

Этот проект превращает два несущих паттерна юнита в рабочий код. Ты собираешь append-only ledger двойной записи, где каждый перевод — две неизменяемые записи в сумме ноль, а баланс — выводимая сумма, никогда изменяемое поле — так что деньги сохраняются по построению, а книги проверяемы. Перевод — это одна атомарная транзакция (обе ноги или ни одной), охраняемая клиентским ключом идемпотентности (атомарный check-and-store под уникальным ограничением), а потерянное обновление побеждается атомарной условной проверкой, блокировкой строки или serializable-транзакцией. Затем ты атакуешь свою же систему: шторм ретраев (N идентичных конкурентных запросов должны двинуть деньги ровно раз) доказывает идемпотентность, а конкурентные переоверкоммиченные дебеты (счёт никогда не должен уйти в минус, дебеты должны всё ещё равняться кредитам) доказывают, что потерянное обновление закрыто. Наконец проход сверки независимо пересуммирует записи и утверждает глобальный инвариант — слой, что ловит потерянные ответы, прерванные saga и дрейф, что горячий путь не видит. Инженер, что собрал это однажды, перестаёт доверять одному ответу как истине о деньгах и начинает доверять сверенному append-only ledger — и знает, с тестами в доказательство, что ретраи и конкуренция не заставят книги лгать.

Связанные уроки

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.