pg_accumulator

A high-performance data accounting engine for PostgreSQL.

Declarative accumulation registers that provide instant access to balances and turnovers across arbitrary dimensions — with full transactional consistency, retroactive corrections, and zero application-side aggregation logic.

Synopsis

CREATE EXTENSION pg_accumulator;

-- Create a register with one API call
SELECT register_create(
    name       := 'inventory',
    dimensions := '{"warehouse": "int", "product": "int"}',
    resources  := '{"quantity": "numeric", "amount": "numeric"}',
    kind       := 'balance'
);

-- Post movements
SELECT register_post('inventory', '{
    "recorder":  "receipt:1",
    "period":    "2026-04-18",
    "warehouse": 1,
    "product":   42,
    "quantity":  100,
    "amount":    5000
}');

-- O(1) current balance
SELECT * FROM accum.inventory_balance(
    dimensions := '{"warehouse": 1, "product": 42}'
);

-- Historical balance at any date
SELECT * FROM accum.inventory_balance(
    dimensions := '{"warehouse": 1}',
    at_date    := '2026-03-31'
);

-- Turnovers for a period
SELECT * FROM accum.inventory_turnover(
    from_date  := '2026-04-01',
    to_date    := '2026-04-30',
    dimensions := '{"warehouse": 1}',
    group_by   := '["product"]'
);

-- Cancel a document
SELECT register_unpost('inventory', 'receipt:1');

-- Atomically replace movements
SELECT register_repost('inventory', 'receipt:1', '{ ... }');

Description

pg_accumulator solves the data accounting infrastructure problem. Building accounting systems in PostgreSQL typically requires hand-crafting movement tables, aggregate tables, trigger logic for keeping totals current, recalculation logic for historical corrections, and indexes for fast reads.

A single register_create() call generates the complete infrastructure:

  • Partitioned movements table — append-only source of truth
  • Hierarchical totals (totals_day, totals_month, totals_year) — pre-aggregated turnovers
  • Balance cache — O(1) current balance lookup
  • Triggers — synchronous consistency within the same transaction
  • Query functions — optimized balance and turnover reads
  • Indexes — dimension hash, recorder, period

The concept of accumulation registers is a proven pattern from enterprise accounting systems (ERP), adapted for PostgreSQL with modern concurrency and performance characteristics.

Key Features

  • Declarative API — define dimensions and resources as JSON, get full infrastructure
  • Transactional consistency — all derived data updated in the same transaction
  • O(1) balance lookups — from pre-computed balance cache
  • Historical queries — hierarchical totals optimization (years → months → days)
  • Retroactive corrections — O(1) via per-period turnovers (not cumulative)
  • High-write mode — UNLOGGED delta buffer with background worker compaction
  • Protection triggers — derived tables are read-only for applications
  • Consistency verificationregister_verify() auditing + register_rebuild_*() recovery
  • Partition management — automatic partition creation for movements tables

Installation

From source

make
make install

Or if pg_config is not in your path:

make PG_CONFIG=/path/to/pg_config
make install PG_CONFIG=/path/to/pg_config

Docker

docker compose -f docker/docker-compose.yml up --build -d

Loading the extension

CREATE EXTENSION pg_accumulator;

For background worker support (delta merge, partition management), add to postgresql.conf:

shared_preload_libraries = 'pg_accumulator'

SQL API

Registry Management

Function Description
register_create(name, dimensions, resources, kind, ...) Create a new accumulation register
register_alter(name, add_dimensions, add_resources) Add dimensions/resources to existing register
register_drop(name) Drop register and all its infrastructure
register_list() List all registered accumulation registers
register_info(name) Detailed information about a register

Write Operations

Function Description
register_post(name, data) Post one or more movements (JSON object or array)
register_unpost(name, recorder) Cancel all movements for a recorder
register_repost(name, recorder, data) Atomically replace movements for a recorder

Read Operations

Function Description
<register>_balance(dimensions, at_date) Current or historical balance
<register>_turnover(from_date, to_date, dimensions, group_by) Turnovers for a period
<register>_movements(recorder, from_date, to_date, dimensions) Raw movement history

Maintenance

Function Description
register_verify(name) Consistency audit of all derived tables
register_rebuild_totals(name) Rebuild totals from movements
register_rebuild_cache(name) Rebuild balance cache from movements

Data Flow

                    register_post() / register_repost()
                                  │
                                  ▼
┌──────────────────────────────────────────────────────────┐
│  movements  (partitioned, append-only, source of truth)  │
└──────────────────────────┬───────────────────────────────┘
                           │ AFTER INSERT/DELETE triggers
                           ▼
         ┌─────────────────────────────────────┐
         │  totals_day    (daily turnovers)     │
         │  totals_month  (monthly turnovers)   │
         │  totals_year   (annual turnovers)    │
         └─────────────────┬───────────────────┘
                           │
                           ▼
         ┌─────────────────────────────────────┐
         │  balance_cache  (current balance)    │
         │  O(1) point lookup by dim_hash       │
         └─────────────────────────────────────┘

Configuration

# Background worker (requires restart)
pg_accumulator.background_workers = 1

# Delta buffer compaction
pg_accumulator.delta_merge_interval   = 5000   # ms between merge cycles
pg_accumulator.delta_merge_delay      = 2000   # ms minimum delta age
pg_accumulator.delta_merge_batch_size = 10000  # max deltas per cycle

# Partition management
pg_accumulator.partitions_ahead     = 3
pg_accumulator.maintenance_interval = 3600000  # ms between maintenance runs

Testing

22 pgTAP test suites covering registry CRUD, posting, cancellation, reposting, trigger chains, balance cache, retroactive corrections, high-write mode, partitioning, maintenance, background worker, and full consistency validation.

docker compose -f docker/docker-compose.test.yml up \
    --build --abort-on-container-exit --exit-code-from test-runner

Author

Treedo treedo@github.com

License

The PostgreSQL License.