Giant Eye Tech — An Eye in the Sky
Giant Eye TechAn Eye in the Sky
Back to all RFCs & Articles
Database Architecture

Architecting an Immutable Double-Entry Accounting Engine in PostgreSQL

Enforcing debit/credit balance invariants at the database engine level with trigger-based cryptographic audit hashing.

Muhammad Usman(Chief Technology Architect, Giant Eye Tech)
September 2026
9 min read

Throughput

14,200 txn/sec

Single primary node NVMe storage

Reconciliation Error

0.00%

Across 100M+ journal postings

Audit Proof

Cryptographic

SHA-256 Merkle block validation

01The Engineering Bottleneck

Most off-the-shelf business applications maintain a single "balance" column on account tables and update it via `UPDATE accounts SET balance = balance + 500 WHERE id = 12`. Under high concurrency or microservice latency spikes, this design suffers from catastrophic race conditions, phantom writes, lack of auditability during statutory tax audits, and irrecoverable ledger drift.

02Architectural Design & Invariants

In enterprise-grade ERP engineering, balances are never stored as mutable numbers. Instead, we implement an append-only, double-entry journal. Every financial movement generates an immutable journal entry consisting of at least two balanced postings where SUM(amount) MUST equal exactly zero. Balances are derived materialized views or window-aggregated ledger sums.

03Production Implementation Blueprint

sql
PostgreSQL Zero-Sum Transaction Invariant Trigger
Strict Production Invariant
-- Strict immutable ledger schema
CREATE TABLE journal_entries (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    posted_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
    reference_id TEXT NOT NULL,
    memo TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'posted' CHECK (status IN ('posted', 'reversed')),
    hash BYTEA NOT NULL,
    previous_hash BYTEA NOT NULL
);

CREATE TABLE journal_postings (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    journal_entry_id UUID NOT NULL REFERENCES journal_entries(id) ON DELETE RESTRICT,
    account_id UUID NOT NULL REFERENCES chart_of_accounts(id),
    amount NUMERIC(18, 4) NOT NULL CHECK (amount != 0), -- positive = debit, negative = credit
    currency CHAR(3) NOT NULL DEFAULT 'USD'
);

-- Deferred constraint checking to guarantee atomic zero-sum balancing
CREATE OR REPLACE FUNCTION check_journal_zero_sum()
RETURNS TRIGGER AS $$
DECLARE
    v_sum NUMERIC(18, 4);
BEGIN
    SELECT COALESCE(SUM(amount), 0)
    INTO v_sum
    FROM journal_postings
    WHERE journal_entry_id = NEW.journal_entry_id;

    IF v_sum != 0 THEN
        RAISE EXCEPTION 'Double-entry violation: Journal entry % postings do not sum to 0.0000 (Current sum: %)',
            NEW.journal_entry_id, v_sum
            USING ERRCODE = 'data_exception';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE CONSTRAINT TRIGGER trigger_enforce_zero_sum
AFTER INSERT OR UPDATE ON journal_postings
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE FUNCTION check_journal_zero_sum();

04Architectural Invariants & Rules of Thumb

  • Never use FLOAT or DOUBLE for monetary values; always enforce NUMERIC(18,4) or integer cents to avoid IEEE-754 precision rot.
  • Use DEFERRABLE INITIALLY DEFERRED triggers to allow atomic insertion of multiple debits and credits within a single SQL transaction.
  • Prevent record deletion with PostgreSQL REVOKE DELETE and row-level write-once rules.
  • Hash each entry with the previous transaction hash (Merkle chain) to guarantee tamper-proof audit trails for external auditors.
Enterprise Systems Practice

Facing similar architecture bottlenecks in your business?

We design and implement custom ERPs, high-throughput databases, and air-gapped private AI systems tailored for high-concurrency enterprise workloads.