- What outcome are we optimizing for? โ Transaction accuracy: zero financial errors. Every penny must be accounted for, every ledger must balance, every regulatory report must be correct. Secondary: customer experience (card auth in <100ms, mobile deposit in seconds, no false fraud declines). This shapes everything: the ledger is the source of truth, double-entry is non-negotiable, and reconciliation is the immune system that catches what the real-time path misses.
- Core products? โ Deposit accounts (checking, savings, CD), credit cards, loans (mortgage, auto, personal), payments (ACH, wire, Zelle, bill pay), debit card transactions.
- Channels? โ Mobile app (~40M active users), web, 3,700 branches, 15,000 ATMs, phone banking, and third-party integrations (payroll providers, billers).
- Real-time vs. batch? โ BOTH. Customer-facing transactions (transfers, card swipes) must be real-time. But the bank's books close nightly โ interest accrual, fee assessment, regulatory reporting, and reconciliation happen in a massive end-of-day (EOD) batch window.
- Scale? โ ~68M consumer/small-business clients, ~200M accounts, ~50M+ transactions/day, $1.9T in deposits.
- Regulatory? โ OCC, Federal Reserve, FDIC, CFPB regulated. Every transaction auditable. Capital adequacy ratios computed daily. BSA/AML compliance mandatory.
| In Scope | Out of Scope |
|---|---|
| Core ledger (accounts, balances, transactions) | Investment / wealth management (Merrill) |
| Payment processing (ACH, wire, card auth) | Commercial / institutional banking |
| End-of-day batch processing | Mortgage origination workflow |
| Real-time fraud detection | Branch teller system UI |
| Account opening & KYC | Treasury / FX trading desk |
| Interest accrual & fee engine | Mobile app frontend |
| General Ledger (GL) posting | Data warehouse / analytics |
- UC1 (Deposit/Withdrawal): Customer deposits $500 at ATM โ system credits account, updates available balance, records transaction, posts to GL. Balance visible immediately across all channels.
- UC2 (Transfer): Customer transfers $1,000 from checking to savings โ atomic double-entry: debit checking, credit savings. Both accounts update in the same transaction.
- UC3 (Card Authorization): Customer swipes debit card at a store โ card network sends auth request โ system checks balance, places hold, returns approve/decline in <100ms.
- UC4 (ACH Payment): Payroll provider sends ACH batch crediting 50,000 employees' accounts โ system processes batch, credits each account, makes funds available per Reg CC schedule.
- UC5 (End of Day): At 11 PM ET, batch processing runs: accrue interest on all 200M accounts, assess monthly fees, process pending ACH, generate regulatory reports, reconcile sub-ledgers to GL.
- Absolute financial integrity: The ledger must ALWAYS balance. Sum of all debits = sum of all credits across the entire bank. A $0.01 discrepancy triggers investigation.
- 24/7 availability: Unlike legacy systems that "closed" at 5 PM, modern banking is always on. But batch processing MUST run nightly. The system must handle real-time transactions AND batch processing concurrently without conflicts.
- Auditability: Every balance change must be traceable to a source transaction with full provenance: who, what, when, why, which system, which approval.
- Regulatory deadlines: EOD processing must complete before market open. Regulatory reports (call reports, stress tests) have hard deadlines with severe penalties for missed filing.
- Multi-product consistency: A customer's checking account, savings account, credit card, and loan must all reflect a consistent view โ no partial updates visible across products.
| Requirement | Decision | Why (and what was rejected) | Consistency |
|---|---|---|---|
| Ledger must balance to the penny | Double-entry bookkeeping in single ACID transaction | Stored procedure inserts debit + credit atomically. CHECK constraint verifies sum = 0. Any other approach risks financial discrepancy. | CP |
| Card authorization in <100ms | Redis for auth cache + inline ML fraud scoring | Visa/Mastercard contractually require <100ms. DB lookup alone = 20ms. Redis cache + pre-loaded fraud model features enable real-time decision. | CP |
| ACH processing in scheduled Fed windows | Batch pipeline (not real-time) | Federal Reserve accepts NACHA files in time windows. ACH is inherently batch. Real-time architecture would add complexity with zero benefit. | โ |
| 40+ years of battle-tested core banking logic | Mainframe for core ledger (wrapped with modern APIs) | COBOL handles every edge case. Rewriting is multi-year, multi-billion risk (TSB UK 2018 disaster). Wrap with REST/gRPC gateway. | โ |
| SOX compliance: immutable audit trail | Append-only audit store (no UPDATE/DELETE allowed) | Every balance change logged with before/after, timestamp, and actor. Cannot be modified โ regulatory requirement. | โ |
| Nightly reconciliation must catch every discrepancy | Independent balance recalculation from ledger entries | Recalculate all balances by summing ledger entries. Compare to cached balances. Any difference = P1 investigation. Belt-and-suspenders. | โ |
๐ Core Ledger HEART
- Double-entry subledger: every account, every transaction
- Immutable append-only journal
- Materialized balances (current, available, held)
- Source of truth for the entire bank
โ๏ธ Product Engine RULES
- Configurable rules per product type
- Interest calculation (daily accrual, monthly credit)
- Fee assessment (monthly maintenance, overdraft)
- Overdraft / credit limit logic
๐ณ Card Authorization REAL-TIME
- Visa/Mastercard network integration
- Auth request โ balance check โ hold โ approve/decline
- Must respond <100ms (network SLA)
- Settlement batch arrives next day
๐ ACH / Wire Gateway PAYMENTS
- ACH: batch-based, files exchanged with Fed/EPN
- Wire: real-time via Fedwire or SWIFT
- Zelle: real-time P2P via RTP/FedNow
- OFAC screening on every outbound payment
๐ EOD Batch Engine BATCH
- Nightly processing: interest, fees, statements
- GL aggregation and posting
- Regulatory report generation
- Must complete in 4-6 hour window
๐ก๏ธ Fraud Engine GUARD
- Real-time scoring on every transaction
- ML model: device, location, amount, velocity
- BSA/AML: suspicious activity detection
- Must not add >20ms to auth latency
๐ General Ledger (GL) ACCOUNTING
- Bank-level aggregate accounting
- Chart of accounts (assets, liabilities, equity, income, expense)
- Fed from subledger postings
- Source of truth for regulators and auditors
๐ฑ Channel Gateway ACCESS
- Mobile app, web, ATM, branch, phone
- API layer abstracting core from channels
- Session management, MFA, biometrics
- Consistent view across all channels
Account Data Model
Multiple Balance Types โ Why It Matters
| Balance Type | What It Represents | Example |
|---|---|---|
| CURRENT | Settled, "book" balance. All completed transactions. | $5,000 after payroll deposit settled. |
| AVAILABLE | What the customer can actually spend RIGHT NOW. | $4,800 (current - $200 card hold). This is what ATM shows. |
| HELD | Funds reserved but not yet settled. Card auths, deposit holds. | $200 gas station pre-auth hold. |
| PENDING | Transactions in flight โ not yet posted. | ACH credit arriving tomorrow but not yet in current balance. |
SELECT SUM(CASE WHEN type='credit' THEN amount ELSE -amount END) FROM ledger_entries WHERE account_id=X. For an account with 10 years of transactions (~50K entries), this is too slow for a <100ms card auth. Materialized balances are updated IN THE SAME transaction as each ledger entry, giving O(1) read performance. The tradeoff: more complex write path (must update both ledger entries AND balance table atomically). But the raw ledger remains the source of truth โ balances can always be recomputed for reconciliation.Subledger โ General Ledger Flow
- Subledger: Individual account balances (Customer A's checking has $5,000). This is what the customer sees.
- General Ledger (GL): Aggregate bank-level balances. "Total consumer checking deposits = $500B." This is what regulators see.
- GL posting: Each subledger transaction maps to GL accounts via a chart of accounts. A deposit: subledger credits customer's checking โ GL debits "Cash & Due from Banks" and credits "Consumer Deposits (liability)."
- Timing: Real-time subledger updates. GL postings are batched (accumulated during the day, posted to GL in EOD batch). The GL is always slightly behind the subledger during the day โ reconciled at EOD.
| Network | Model | Speed | Cost | Reversible? |
|---|---|---|---|---|
| ACH | Batch files (NACHA format) | Next day (same-day ACH: hours) | ~$0.25/txn | Yes (returns within 2 days) |
| Wire (Fedwire) | Real-time gross settlement | Minutes (irrevocable once sent) | ~$25/txn | No โ final and irrevocable |
| SWIFT | Message-based (correspondent banking) | 1-3 business days | ~$30-50/txn | Very difficult |
| Card (Visa/MC) | Two-phase: auth โ settlement | Auth: <100ms. Settle: T+1 | Interchange fee (~1-3%) | Chargeback process (60 days) |
| Zelle/RTP | Real-time push payment | Seconds | ~$0.01-0.50 | No โ irrevocable once sent |
- Feature vector (computed in <5ms): Transaction amount, merchant category (MCC), time of day, distance from last transaction, device fingerprint, IP geolocation, transaction velocity (# transactions in last hour), average transaction amount for this customer, whether this merchant is new for this customer.
- Model: Gradient-boosted tree (XGBoost/LightGBM) or neural network. Trained on historical labeled fraud data. Updated weekly with new fraud patterns. Inference: <5ms on GPU or optimized CPU.
- Score โ Action: Score 0-1000. <300 = approve. 300-700 = step-up auth (SMS code, app notification). >700 = decline. Thresholds tuned by product team balancing fraud loss vs. customer friction.
- Real-time feature store: Redis cluster holding per-customer aggregates: last N transactions, running sums, last known location. Updated on every transaction. Read latency: <1ms.
- Consortium data: Shared fraud signals across the banking industry. Known compromised cards from Visa/MC alerts. Merchant fraud reports. These feed into the model as additional features.
- BSA/AML (Anti-Money Laundering): Separate from card fraud. Analyzes PATTERNS across days/weeks: structuring (multiple deposits just under $10K), unusual international wires, rapid movement of funds. Runs as a batch + streaming hybrid โ not inline. Generates Suspicious Activity Reports (SARs) for FinCEN.
- Account takeover detection: Monitors login patterns: new device, unusual time, impossible travel (login from NYC, then London 2 hours later). Triggers step-up authentication or account lock.
| Data | Store | Why This Store |
|---|---|---|
| Account balances | Core banking DB (mainframe) | Current balance, available balance, holds. Updated by every transaction. Strong consistency. ACID guaranteed. |
| Transaction ledger | Append-only relational DB | Every debit and credit entry. Immutable. Double-entry: sum of all entries = 0. Partitioned by account + date. |
| General ledger | Relational DB | Aggregated accounting entries per GL code. Updated by EOD batch. Source of truth for regulators and auditors. |
| Card authorization cache | Redis | Recent auth decisions, velocity counters (transactions per card per hour), merchant category caches. Sub-ms lookups. |
| Fraud model features | Feature store + Redis | Real-time features: spending patterns, location history, device fingerprints. Fed into ML scoring engine. |
| ACH/wire files | Secure file storage | NACHA-format batch files sent to/from Federal Reserve. Archived for 7 years (regulatory requirement). |
| Audit trail | Immutable append-only store | Every balance change, every admin action, every policy override. SOX compliance. Cannot be modified or deleted. |
- Internal reconciliation: Every night, subledger account balances are re-computed from raw ledger entries and compared to materialized balances. Any discrepancy โ alert.
- Subledger-to-GL: Sum of all subledger checking accounts must equal the GL "Consumer Checking Deposits" line item. Checked nightly.
- External reconciliation: Compare bank's records to: (a) Fed's records (ACH, wire), (b) card network settlement files, (c) correspondent bank statements (Nostro/Vostro accounts). Differences โ suspense account โ investigation.
- Nostro/Vostro: BofA holds accounts at other banks (Nostro = "ours at theirs") and other banks hold accounts at BofA (Vostro = "theirs at ours"). These must reconcile daily โ a mismatch means money went somewhere unexpected.
- RPO (Recovery Point Objective): Zero. Synchronous replication โ no committed transaction can be lost.
- RTO (Recovery Time Objective): <4 hours for full system, <30 seconds for database failover.
- Active-active vs. active-passive: Most tier-1 banks run active-passive for the core ledger (write-primary + read-replicas). Active-active for the core ledger is extremely difficult due to the need for global ordering of transactions affecting the same account. Card auth and read-heavy services can be active-active across data centers.
- Regulatory requirement: OCC requires bank to demonstrate ability to resume critical operations within 2 hours of a disaster. Regular DR drills with documented results.
- Data at rest: AES-256 encryption. PCI DSS for card data. Tokenization: card numbers never stored in plain text โ replaced with tokens that map to a secure vault.
- Data in transit: TLS 1.3 everywhere. Internal service-to-service: mTLS.
- Access control: Role-based (teller vs. manager vs. compliance officer). Least privilege. Dual control for high-risk operations (wire transfers >$1M require two approvals).
- Data residency: Customer financial data must stay within US jurisdiction. No cloud region outside US for core banking data.
| Extension | Architecture Impact |
|---|---|
| Real-Time Payments (FedNow) | Instant settlement 24/7/365 โ no more T+1. Eliminates the batch-oriented ACH model for covered transactions. Requires always-on ledger with no maintenance windows. Fundamentally challenges the EOD batch concept. |
| Open Banking (APIs for fintechs) | Expose account data and payment initiation via standardized APIs. Requires consent management (OAuth2), rate limiting, and a new trust model where third parties can read balances and initiate payments with customer permission. |
| Multi-Currency / Cross-Border | Accounts holding multiple currencies. FX conversion on the hot path. SWIFT gpi integration for traceable cross-border payments. Nostro account management becomes critical infrastructure. |
| Event-Driven Architecture (replacing batch) | Move from nightly batch to continuous processing: interest accrues in real-time, fees assessed at the moment the trigger occurs, GL updated continuously. Eliminates the EOD bottleneck but requires massive re-architecture of systems that assume batch timing. |
| AI-Powered Financial Advisor | Analyze transaction patterns โ recommend savings strategies, detect upcoming cash shortfalls, suggest bill payment timing. Requires read-only access to transaction history and ML inference pipeline separate from the core. |
Why do banks still use mainframes in 2026?
Because no other technology matches their transaction throughput with ACID guarantees at scale. A modern IBM zSystem can process 100,000+ transactions per second with sub-millisecond latency, with hardware-level encryption and fault tolerance. More importantly, the core banking software (often COBOL) has been battle-tested over 40+ years. It handles every edge case: leap years, currency conversions, regulatory holds, overdraft protection chains, interest compounding โ all proven correct by decades of operation. Rewriting this in a modern stack is a multi-year, multi-billion-dollar project with enormous risk. Several banks have tried and failed (TSB UK's 2018 migration disaster is a cautionary tale). The pragmatic approach is to wrap the mainframe with modern APIs (REST/gRPC gateway) and build new features in microservices that call the mainframe for core ledger operations. The mainframe does what it's best at (ACID transactions at scale), and modern services handle what it's worst at (mobile UX, real-time notifications, ML-based fraud detection).
How does double-entry bookkeeping prevent financial errors?
Every financial movement is recorded as exactly two entries: a debit and a credit of equal amounts. When you transfer $100 from checking to savings: debit checking $100, credit savings $100. The invariant: the sum of all entries in the entire ledger must equal zero. This is checked continuously โ any non-zero sum means an error occurred. The beauty is that it's self-auditing: you can't accidentally create money (a credit without a matching debit), and you can't accidentally destroy money (a debit without a matching credit). In code: the stored procedure that creates ledger entries takes (from_account, to_account, amount) and atomically inserts both rows in a single transaction. If either INSERT fails, both are rolled back. There's a CHECK constraint that verifies the sum remains zero. For complex transactions (fee charges, interest accrual), there may be 4-6 entries, but they always sum to zero. The nightly reconciliation independently verifies this by summing the entire ledger.