# 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.
> **Release status:** v1.0.3 — all [22 test suites](test/sql/) passing (pgTAP).
---
## Table of Contents
- [Overview](#overview)
- [Core Concepts](#core-concepts)
- [Movement](#movement)
- [Balance](#balance)
- [Turnover](#turnover)
- [Delta (Compaction)](#delta-compaction)
- [Quick Start](#quick-start)
- [SQL API Contracts](#sql-api-contracts)
- [Post Movement — `register_post()`](#post-movement--register_post)
- [Update Movement — `register_repost()`](#update-movement--register_repost)
- [Cancel Movement — `register_unpost()`](#cancel-movement--register_unpost)
- [Get Balance — `<register>_balance()`](#get-balance--register_balance)
- [Get Turnover — `<register>_turnover()`](#get-turnover--register_turnover)
- [Architecture](#architecture)
- [Data Flow](#data-flow)
- [Module Reference](#module-reference)
- [Consistency Strategy](#consistency-strategy)
- [Compaction Strategy](#compaction-strategy)
- [Benchmarks](#benchmarks)
- [Demo Applications](#demo-applications)
- [Package Ecosystem](#package-ecosystem)
- [Prisma ORM Integration](#prisma-orm-integration)
- [Testing](#testing)
- [Configuration](#configuration)
- [Full API Reference](#full-api-reference)
- [License](#license)
---
## Overview
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. Every project re-invents this infrastructure.
`pg_accumulator` solves this with a single declarative API call:
```sql
SELECT register_create(
name := 'inventory',
dimensions := '{"warehouse": "int", "product": "int", "lot": "text"}',
resources := '{"quantity": "numeric", "amount": "numeric"}',
kind := 'balance'
);
```
This creates the complete infrastructure automatically:
- **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.
---
## Core Concepts
### Movement
A **movement** is an atomic record of resource change. Movements are always appended — never updated or deleted by the application. Each movement belongs to a **recorder** (business document identifier) and has an accounting **period** (date).
```sql
-- Receipt of 100 units at warehouse 1
SELECT register_post('inventory', '{
"recorder": "purchase:7001",
"period": "2026-04-18",
"warehouse": 1,
"product": 42,
"lot": "LOT-A",
"quantity": 100,
"amount": 5000
}');
```
Positive values represent receipts, negative values represent expenditures.
**Tested in:** [test/sql/03_register_post.sql](test/sql/03_register_post.sql), [test/sql/12_direct_insert.sql](test/sql/12_direct_insert.sql)
### Balance
A **balance** is the cumulative sum of all movements for a given combination of dimensions. The `balance_cache` table stores the current balance and is updated synchronously within the same transaction as the movement write.
```sql
-- O(1) current balance — reads directly from cache
SELECT * FROM accum.inventory_balance(
dimensions := '{"warehouse": 1, "product": 42}'
);
-- Historical balance at any point in time — uses totals hierarchy
SELECT * FROM accum.inventory_balance(
dimensions := '{"warehouse": 1, "product": 42}',
at_date := '2026-03-15'
);
```
Historical balance queries use the hierarchical totals optimization: sum complete years from `totals_year`, add complete months from `totals_month`, then scan only the remaining partial period from `totals_day` or raw movements — producing sub-millisecond results regardless of total data volume.
**Tested in:** [test/sql/09_balance_cache.sql](test/sql/09_balance_cache.sql), [test/sql/22_consistency.sql](test/sql/22_consistency.sql)
### Turnover
A **turnover** is the net change in resources during a specific time period. Turnovers are stored at day, month, and year granularity in the totals tables. Unlike cumulative balances, turnovers record only the delta for each period — this is the key design decision that makes retroactive corrections O(1).
```sql
SELECT * FROM accum.inventory_turnover(
from_date := '2026-04-01',
to_date := '2026-04-30',
dimensions := '{"warehouse": 1}',
group_by := '["product"]'
);
```
**Why turnovers, not cumulative totals:**
| Approach | Retroactive correction complexity |
|---|---|
| Cumulative totals in each period | O(N) — must update all subsequent periods |
| Per-period turnovers (pg_accumulator) | O(1) — update only the affected period |
**Tested in:** [test/sql/08_triggers_totals.sql](test/sql/08_triggers_totals.sql), [test/sql/11_turnover_register.sql](test/sql/11_turnover_register.sql), [test/sql/10_correction_retroactive.sql](test/sql/10_correction_retroactive.sql)
### Delta (Compaction)
When multiple concurrent writers target the same dimension combination (same `dim_hash`), standard mode requires row-level locking on the `balance_cache` row. For high-contention scenarios, **high-write mode** replaces the `UPDATE` with an append-only `INSERT` into a delta buffer table. A background worker periodically **compacts** (merges) accumulated deltas into the balance cache.
```sql
-- Enable high-write mode at register creation
SELECT register_create(
name := 'page_views',
dimensions := '{"page": "text"}',
resources := '{"views": "int"}',
kind := 'balance',
high_write := true
);
```
Balance reads in high-write mode automatically include pending (unmerged) deltas, so query results are always accurate regardless of compaction timing.
**Tested in:** [test/sql/16_high_write_mode.sql](test/sql/16_high_write_mode.sql)
---
## Quick Start
### 1. Run with Docker
```bash
git clone https://github.com/example/pg_accumulator.git
cd pg_accumulator
# Start PostgreSQL with the extension pre-installed
docker compose -f docker/docker-compose.yml up --build -d
# Connect
psql -h localhost -p 5432 -U postgres
```
### 2. Create a register
```sql
CREATE EXTENSION pg_accumulator;
SELECT register_create(
name := 'inventory',
dimensions := '{"warehouse": "int", "product": "int"}',
resources := '{"quantity": "numeric(18,4)", "amount": "numeric(18,2)"}',
kind := 'balance'
);
```
### 3. Post movements
```sql
SELECT register_post('inventory', '[
{
"recorder": "receipt:1",
"period": "2026-04-18",
"warehouse": 1, "product": 42,
"quantity": 100, "amount": 5000.00
},
{
"recorder": "shipment:1",
"period": "2026-04-18",
"warehouse": 1, "product": 42,
"quantity": -10, "amount": -500.00
}
]');
```
### 4. Read the balance
```sql
SELECT * FROM accum.inventory_balance(
dimensions := '{"warehouse": 1, "product": 42}'
);
-- {"quantity": 90.0000, "amount": 4500.00}
```
### 5. Cancel a document
```sql
SELECT register_unpost('inventory', 'shipment:1');
-- Balance reverts to {"quantity": 100.0000, "amount": 5000.00}
```
### 6. Run the test suite
```bash
docker compose -f docker/docker-compose.test.yml up --build \
--abort-on-container-exit --exit-code-from test-runner
```
---
## SQL API Contracts
All functions reside in the `accum` schema (configurable at extension creation time).
### Post Movement — `register_post()`
Records one or more movements for a register. Accepts a single JSON object or a JSON array of objects. Returns the count of inserted movements.
```sql
SELECT accum.register_post(
p_register := 'inventory',
p_data := '{
"recorder": "purchase:7001",
"period": "2026-04-18",
"warehouse": 1,
"product": 42,
"quantity": 100,
"amount": 5000.00
}'
);
```
**What happens inside one transaction:**
```
1. Validate JSON → compute dim_hash → INSERT INTO movements
2. UPSERT totals_day += delta
3. UPSERT totals_month += delta
4. UPSERT totals_year += delta
5. UPSERT balance_cache += delta (or INSERT into delta buffer in high-write mode)
6. COMMIT → fully consistent, visible to all readers
```
| Field | Type | Required | Description |
|---|---|---|---|
| `recorder` | text (configurable) | Yes | Business document identifier |
| `period` | timestamp / date | Yes | Accounting period of the movement |
| `<dimension>` | as declared | Yes | Value for each dimension |
| `<resource>` | numeric | Yes | Delta value (positive = receipt, negative = expenditure) |
**Tested in:** [test/sql/03_register_post.sql](test/sql/03_register_post.sql)
### Update Movement — `register_repost()`
Atomically replaces all movements for a recorder with new data. Equivalent to unpost + post, but optimized to compute and apply only the net delta within a single transaction.
```sql
SELECT accum.register_repost('inventory', 'purchase:7001', '{
"period": "2026-04-18",
"warehouse": 1,
"product": 42,
"quantity": 110,
"amount": 5500.00
}');
```
This is the correct tool for correcting already-posted documents. Old movements are deleted, new movements are inserted, and all derived tables are updated with the net change.
**Tested in:** [test/sql/05_register_repost.sql](test/sql/05_register_repost.sql), [test/sql/10_correction_retroactive.sql](test/sql/10_correction_retroactive.sql)
### Cancel Movement — `register_unpost()`
Cancels all movements belonging to a recorder, reversing their effect on totals and balance cache.
```sql
SELECT accum.register_unpost('inventory', 'purchase:7001');
```
**Tested in:** [test/sql/04_register_unpost.sql](test/sql/04_register_unpost.sql)
### Get Balance — `<register>_balance()`
Returns a JSONB object with the current (or historical) balance of resources for a given dimension filter.
```sql
-- Current balance (O(1) from balance_cache)
SELECT * FROM accum.inventory_balance(
dimensions := '{"warehouse": 1, "product": 42}'
);
-- Historical balance (uses totals hierarchy)
SELECT * FROM accum.inventory_balance(
dimensions := '{"warehouse": 1}',
at_date := '2026-03-31'
);
-- Aggregate across all dimensions
SELECT * FROM accum.inventory_balance();
```
**Tested in:** [test/sql/09_balance_cache.sql](test/sql/09_balance_cache.sql), [test/sql/14_end_to_end_warehouse.sql](test/sql/14_end_to_end_warehouse.sql)
### Get Turnover — `<register>_turnover()`
Returns turnovers (net resource change) for a date range, with optional grouping.
```sql
SELECT * FROM accum.inventory_turnover(
from_date := '2026-04-01',
to_date := '2026-04-30',
dimensions := '{"warehouse": 1}',
group_by := '["product"]'
);
```
**Tested in:** [test/sql/11_turnover_register.sql](test/sql/11_turnover_register.sql)
---
## Architecture
### 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 │
└─────────────────────────────────────┘
│
(high-write mode only)
▼
┌─────────────────────────────────────┐
│ balance_cache_delta (UNLOGGED) │
│ Append-only buffer, merged by │
│ background worker (compaction) │
└─────────────────────────────────────┘
```
All derived tables (totals, cache) are updated **synchronously within the same transaction** as the movement write. After `COMMIT`, every reader sees fully consistent data.
### Module Reference
The extension source is organized into focused modules. Each module has its own documentation:
| Module | Purpose | Documentation |
|---|---|---|
| **Core** | Extension init, schema, internal registry | [src/core/MODULE.md](src/core/MODULE.md) |
| **DDL Generator** | Table, index, function generation on `register_create()` | [src/ddl/MODULE.md](src/ddl/MODULE.md) |
| **Hash** | 64-bit dimension hashing (xxhash64) for fast lookups | [src/hash/MODULE.md](src/hash/MODULE.md) |
| **Triggers** | Synchronous totals and cache updates on movement writes | [src/triggers/MODULE.md](src/triggers/MODULE.md) |
| **Write API** | `register_post`, `register_unpost`, `register_repost` | [src/write_api/MODULE.md](src/write_api/MODULE.md) |
| **Read API** | `_balance()`, `_turnover()`, `_movements()` with hierarchy optimization | [src/read_api/MODULE.md](src/read_api/MODULE.md) |
| **Registry API** | `register_create`, `register_alter`, `register_drop`, `register_list`, `register_info` | [src/registry_api/MODULE.md](src/registry_api/MODULE.md) |
| **Delta Buffer** | High-write mode: append-only delta buffer and merge logic | [src/delta_buffer/MODULE.md](src/delta_buffer/MODULE.md) |
| **Partitioning** | Automatic partition management for movements tables | [src/partitioning/MODULE.md](src/partitioning/MODULE.md) |
| **Maintenance** | Consistency verification, totals/cache rebuild, diagnostics | [src/maintenance/MODULE.md](src/maintenance/MODULE.md) |
| **Background Worker** | Delta merge, partition creation, periodic maintenance | [src/bgworker/MODULE.md](src/bgworker/MODULE.md) |
---
## Consistency Strategy
pg_accumulator provides **strong transactional consistency**: after `COMMIT`, all derived data (totals and balance cache) reflects the committed movements exactly.
### Guarantees
| Property | Mechanism |
|---|---|
| **Freshness** | Triggers update totals and cache in the same transaction as the movement write |
| **Correctness** | `balance_cache = SUM(all movements)` is always true (provable via `register_verify()`) |
| **Rollback safety** | If the transaction rolls back, all trigger-side effects roll back with it |
| **MVCC isolation** | Each reader sees a consistent snapshot; no dirty reads |
| **Concurrent writes** | Row-level locks on `dim_hash` rows serialize writes to the same dimension combination |
### Protection Triggers
Derived tables (`totals_day`, `totals_month`, `totals_year`, `balance_cache`) are protected from direct modification. Any attempt to `INSERT`, `UPDATE`, or `DELETE` rows directly raises an exception, unless:
- The modification originates from the internal trigger chain (`pg_trigger_depth() > 1`), or
- The `pg_accumulator.allow_internal` GUC is explicitly enabled (used by maintenance functions).
This prevents accidental corruption of derived data.
### Verification
The `register_verify()` function performs a full consistency audit:
```sql
SELECT * FROM accum.register_verify('inventory');
```
It compares:
- `balance_cache` values against `SUM(movements)` per `dim_hash`
- `totals_day` values against `SUM(movements)` per `(dim_hash, day)`
- `totals_month` values against `SUM(movements)` per `(dim_hash, month)`
- `totals_year` values against `SUM(movements)` per `(dim_hash, year)`
Detected statuses: `OK`, `MISMATCH`, `MISSING_IN_CACHE`, `ORPHAN_IN_CACHE`, `MISSING_IN_TOTALS`, `ORPHAN_IN_TOTALS`.
**Recovery:** If mismatches are detected, use `register_rebuild_totals()` and `register_rebuild_cache()` to reconstruct all derived data from the movements source of truth.
### Test Coverage for Consistency
| Test | What it verifies |
|---|---|
| [test/sql/22_consistency.sql](test/sql/22_consistency.sql) | Full aggregation chain: movements → totals_day → totals_month → totals_year → balance_cache. Multi-resource, cross-month, unpost/repost, protection triggers. **36 assertions.** |
| [test/sql/08_triggers_totals.sql](test/sql/08_triggers_totals.sql) | Trigger chain correctness: INSERT creates totals, DELETE reverses them, delta propagation. **17 assertions.** |
| [test/sql/09_balance_cache.sql](test/sql/09_balance_cache.sql) | Balance cache creation, accumulation, counter-movements, edge cases. **11 assertions.** |
| [test/sql/10_correction_retroactive.sql](test/sql/10_correction_retroactive.sql) | Retroactive corrections: O(1) update of past periods without cascade. |
| [test/sql/16_high_write_mode.sql](test/sql/16_high_write_mode.sql) | Delta buffer creation, merge correctness, balance accuracy with pending deltas. **26 assertions.** |
| [test/sql/20_maintenance.sql](test/sql/20_maintenance.sql) | `register_verify()`, `register_rebuild_totals()`, `register_rebuild_cache()`. |
---
## Compaction Strategy
In **high-write mode**, the balance cache is not updated directly. Instead, resource deltas are appended to an `UNLOGGED` delta buffer table, avoiding row-level lock contention on hot `dim_hash` rows.
### Write Path
```
Standard mode: High-write mode:
UPDATE balance_cache INSERT INTO balance_cache_delta
SET qty += 10 (dim_hash, views) VALUES (H, 10)
WHERE dim_hash = H — no lock on cache row
— row lock on H — no contention
```
### Compaction (Delta Merge)
The background worker periodically merges accumulated deltas into the balance cache:
```sql
-- Simplified merge logic (runs automatically):
WITH consumed AS (
DELETE FROM balance_cache_delta
WHERE created_at < now() - interval '2 seconds'
ORDER BY id LIMIT 10000
RETURNING dim_hash, views
),
agg AS (
SELECT dim_hash, SUM(views) AS views
FROM consumed GROUP BY dim_hash
)
UPDATE balance_cache c
SET views = c.views + a.views, version = c.version + 1
FROM agg a WHERE c.dim_hash = a.dim_hash;
```
### Read Accuracy
Balance reads in high-write mode **always include pending deltas** via `UNION ALL` in the internal query, so results are accurate regardless of whether compaction has run.
### Compaction Configuration
| Setting | Default | Description |
|---|---|---|
| `pg_accumulator.delta_merge_interval` | 5000 ms | Time between merge cycles |
| `pg_accumulator.delta_merge_delay` | 2000 ms | Minimum delta age before merge |
| `pg_accumulator.delta_merge_batch_size` | 10000 | Max delta rows per merge cycle |
### Manual Compaction
```sql
-- Force immediate merge of all pending deltas
SELECT accum._force_delta_merge(
p_max_age := interval '0 seconds',
p_batch_size := 1000000
);
```
> **Important:** The delta buffer table is `UNLOGGED` for performance. In the event of a PostgreSQL crash, pending (unmerged) deltas are lost. After a crash, run `register_rebuild_cache()` to restore consistency.
**Tested in:** [test/sql/16_high_write_mode.sql](test/sql/16_high_write_mode.sql)
---
## Benchmarks
Measured results from the benchmark suite ([bench/sql/bench.sql](bench/sql/bench.sql)) on PostgreSQL 17, Docker, macOS ARM, single connection.
### 1x scale
| Scenario | Iterations | Total | Avg/op | ops/sec |
|---|---|---|---|---|
| `register_post()` single insert | 5,000 | 5,226.50 ms | 1.0453 ms | 956.7 |
| `register_post()` batch of 10 | 5,000 | 2,498.61 ms | 0.4997 ms | 2,001.1 |
| `register_post()` batch of 100 | 10,000 | 4,460.77 ms | 0.4461 ms | 2,241.8 |
| `register_post()` batch of 1000 | 10,000 | 2,845.90 ms | 0.2846 ms | 3,513.8 |
| `balance_cache` direct SELECT | 2,000 | 236.65 ms | 0.1183 ms | 8,451.2 |
| `b_std_balance()` function | 1,000 | 408.49 ms | 0.4085 ms | 2,448.1 |
| `register_post()` high-write mode | 5,000 | 4,427.16 ms | 0.8854 ms | 1,129.4 |
| `register_unpost()` | 1,000 | 715.70 ms | 0.7157 ms | 1,397.2 |
### 10x scale
| Scenario | Iterations | Total | Avg/op | ops/sec |
|---|---|---|---|---|
| `register_post()` single insert | 50,000 | 116,504.98 ms | 2.3301 ms | 429.2 |
| `register_post()` batch of 10 | 50,000 | 61,083.96 ms | 1.2217 ms | 818.5 |
| `register_post()` batch of 100 | 100,000 | 101,164.23 ms | 1.0116 ms | 988.5 |
| `register_post()` batch of 1000 | 100,000 | 47,264.90 ms | 0.4726 ms | 2,115.7 |
| `balance_cache` direct SELECT | 20,000 | 561.07 ms | 0.0281 ms | 35,646.3 |
| `b_std_10_balance()` function | 10,000 | 4,997.46 ms | 0.4997 ms | 2,001.0 |
| `register_post()` high-write mode | 50,000 | 106,581.80 ms | 2.1316 ms | 469.1 |
| `register_unpost()` | 10,000 | 15,811.21 ms | 1.5811 ms | 632.5 |
These values were captured from the 1x and 10x benchmark runs on this environment; actual performance may vary by hardware and PostgreSQL configuration.
Key takeaways:
- **Batch mode** delivers much higher throughput than single-item inserts
- **Balance cache** point lookups remain extremely fast even at 10x scale
- **High-write mode** is useful for larger write workloads, though average per-op latency is higher than the standard register
Run the benchmark suite:
```bash
# Via Docker
docker compose -f bench/docker-compose.bench.yml up --build
# Or directly
psql -f bench/sql/bench.sql
```
---
## Demo Applications
Interactive demo applications are available for different technology stacks:
| Demo | Stack | App URL | PostgreSQL Port | Documentation |
|---|---|---|---|---|
| **Python** | Flask + psycopg2 | `localhost:3301` | `5434` | [demo/python/README.md](demo/python/README.md) |
| **TypeScript** | Express + pg | `localhost:3302` | `5435` | [demo/typescript/README.md](demo/typescript/README.md) |
| **Prisma** | Express + Prisma ORM + prisma-accumulator | `localhost:3303` | `5436` | [demo/prisma/README.md](demo/prisma/README.md) |
| **SQLAlchemy** | Flask + SQLAlchemy + sqlalchemy-accumulator | `localhost:3304` | `5437` | [demo/sqlalchemy/README.md](demo/sqlalchemy/README.md) |
Each demo demonstrates:
- Register creation and initialization
- Posting movements (`register_post`)
- Canceling documents (`register_unpost`)
- Real-time balance display from `balance_cache`
- Historical balance queries (`_balance()` with `at_date`)
- Movement history browsing
```bash
# Start any demo
cd demo/python # or demo/typescript, demo/prisma, demo/sqlalchemy
docker compose up --build
```
---
## Package Ecosystem
The repository currently contains three integration packages:
| Package | Language | Purpose | Documentation |
|---|---|---|---|
| **prisma-accumulator** | TypeScript | Type-safe adapter for Prisma ORM | [packages/prisma-accumulator/README.md](packages/prisma-accumulator/README.md) |
| **sqlalchemy-accumulator** | Python | SQLAlchemy-first adapter with typed operations | [packages/sqlalchemy-accumulator/CONCEPT.md](packages/sqlalchemy-accumulator/CONCEPT.md) |
| **go-accumulator** | Go | Planned Go integration package | [packages/go-accumulator/CONCEPT.md](packages/go-accumulator/CONCEPT.md) |
---
## Prisma ORM Integration
The **[prisma-accumulator](packages/prisma-accumulator/README.md)** package provides a type-safe TypeScript adapter for using pg_accumulator with Prisma ORM:
```typescript
import { defineRegister, AccumulatorClient } from 'prisma-accumulator';
const inventory = defineRegister({
name: 'inventory',
kind: 'balance',
dimensions: { warehouse: 'int', product: 'int' },
resources: { quantity: 'numeric', amount: 'numeric' },
});
const accum = new AccumulatorClient(prisma);
await accum.use(inventory).post({ recorder: 'receipt:1', period: '2026-04-18', ... });
const balance = await accum.use(inventory).balance({ warehouse: 1 });
```
See the [concept document](packages/prisma-accumulator/CONCEPT.md) for the architecture and development roadmap.
---
## Testing
The test suite comprises **22 pgTAP test files** covering every aspect of the system. All tests pass on the current release.
```bash
# Run the full test suite
docker compose -f docker/docker-compose.test.yml up \
--build --abort-on-container-exit --exit-code-from test-runner
```
### Test Matrix
| Test | File | Scope |
|---|---|---|
| Core registry CRUD | [01_core_registry.sql](test/sql/01_core_registry.sql) | `_registers` metadata table |
| Register create | [02_register_create.sql](test/sql/02_register_create.sql) | DDL generation, table structure |
| register_post | [03_register_post.sql](test/sql/03_register_post.sql) | Single and batch posting |
| register_unpost | [04_register_unpost.sql](test/sql/04_register_unpost.sql) | Document cancellation |
| register_repost | [05_register_repost.sql](test/sql/05_register_repost.sql) | Atomic movement replacement |
| register_drop | [06_register_drop.sql](test/sql/06_register_drop.sql) | Infrastructure teardown |
| register_list / info | [07_register_list_info.sql](test/sql/07_register_list_info.sql) | Registry introspection |
| Trigger chain | [08_triggers_totals.sql](test/sql/08_triggers_totals.sql) | Totals consistency on INSERT/DELETE |
| Balance cache | [09_balance_cache.sql](test/sql/09_balance_cache.sql) | Cache creation, accumulation, edge cases |
| Retroactive corrections | [10_correction_retroactive.sql](test/sql/10_correction_retroactive.sql) | O(1) past-period corrections |
| Turnover register | [11_turnover_register.sql](test/sql/11_turnover_register.sql) | Turnover-only registers |
| Direct INSERT | [12_direct_insert.sql](test/sql/12_direct_insert.sql) | Trigger-based updates via raw INSERT |
| Multiple dimensions | [13_multiple_dimensions.sql](test/sql/13_multiple_dimensions.sql) | Multi-dimension combinations |
| End-to-end: warehouse | [14_end_to_end_warehouse.sql](test/sql/14_end_to_end_warehouse.sql) | Full warehouse scenario |
| End-to-end: finance | [15_end_to_end_finance.sql](test/sql/15_end_to_end_finance.sql) | Full financial accounting scenario |
| High-write mode | [16_high_write_mode.sql](test/sql/16_high_write_mode.sql) | Delta buffer, merge, read accuracy |
| Recorder pattern | [17_recorder_pattern.sql](test/sql/17_recorder_pattern.sql) | Document-based post/unpost/repost |
| register_alter | [18_register_alter.sql](test/sql/18_register_alter.sql) | Adding dimensions/resources |
| Partitioning | [19_partitioning.sql](test/sql/19_partitioning.sql) | Partition creation and management |
| Maintenance | [20_maintenance.sql](test/sql/20_maintenance.sql) | verify, rebuild_totals, rebuild_cache |
| Background worker | [21_bgworker.sql](test/sql/21_bgworker.sql) | Worker registration and lifecycle |
| Full consistency | [22_consistency.sql](test/sql/22_consistency.sql) | Comprehensive aggregation chain validation |
---
## Configuration
Set in `postgresql.conf` or via `ALTER SYSTEM`:
```ini
# Required for the background worker
shared_preload_libraries = 'pg_accumulator'
# Background worker (requires restart)
pg_accumulator.background_workers = 1 # 0 to disable, range: 0..8
# Delta buffer compaction (reload with SELECT pg_reload_conf())
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 # future partitions to maintain
pg_accumulator.maintenance_interval = 3600000 # ms between maintenance runs
```
---
## Full API Reference
For the complete API documentation with detailed parameter tables, return types, usage examples, and architectural diagrams, see:
**[docs/README.md](docs/README.md)**
---
## License
pg_accumulator is distributed under the [PostgreSQL License](https://www.postgresql.org/about/licence/).