Accounting system scaling
Accounting system scaling and launch checklist
An accounting system stays fast by paginating every list, indexing the main questions, and deferring heavy work until a measured trigger fires. At launch, apply 7 rules, watch 9 signals and hold the performance targets below.
Last checked
Rules to apply at launch
- Pagination: Ledger, bill and invoice lists use keyset pagination on (posting_date, id) or (due_date, id); page size <= 100; exports stream in batches, never load a full ledger into memory.
- Caching: Account balances by period are a maintained summary table (entity, account, period) updated in the posting transaction and proved nightly against journal_lines; reports read the summary, only drill-down touches lines.
- Idempotency: Posting, invoice issue, payment-run release and bank import require an Idempotency-Key with unique (entity_id, idempotency_key); processor webhooks are unique by event id.
- Rate limits: Per-user limits on posting, import and report generation; per-entity cap on concurrent heavy reports; payment-run release has a daily value ceiling that fails closed.
- Data growth: journal_lines and journals are range partitioned by posting year; closed years are detached to read-only archive and never dropped; the balance summary keeps close-period reports cheap.
- Search: Counterparty and document search uses Postgres trigram and full-text indexes scoped by entity; no external search engine in phase 1.
- Storage: Bill scans, invoice PDFs and report snapshots live in a private bucket with content-hash keys and lifecycle to cold storage after the retention window opens.
Performance targets
| What to measure | Target |
|---|---|
| Journal post | p95 < 200 ms for a journal of up to 50 lines, one transaction |
| Trial balance for an entity and period | p95 < 500 ms from the balance summary table |
| Ledger drill-down page (100 lines) | p95 < 120 ms via (entity_id, account_id, posting_date) |
| Bank statement import of 5,000 lines | < 30 s including auto-match suggestions |
| Consolidation run for 20 entities | < 10 min asynchronous with progress |
What to watch
- Metric journals posted total and post latency ms: by source and entity; includes constraint and lock wait time.
- Metric unreconciled bank lines: count and age by bank account.
- Metric trial balance difference minor: must be exactly 0 per entity and currency, checked nightly.
- Log financial audit log: request id, actor, document type and id, outcome; no bank or tax identifiers.
- Trace invoice issue trace: number allocation, journal post, PDF render, notification.
- Slo ledger correctness: 0 unbalanced journals, 0 postings into closed periods, 0 duplicate postings per idempotency key.
- Slo close timeliness: 90% of entities close the period within the configured working days.
- Alert ledger integrity failure: imbalance, summary drift or audit chain break pages immediately.
Recurring work
- load_fx_rates: runs cron daily 06:00 UTC. Guard: unique (pair, as_of, source); a failed load alerts and postings in that currency block rather than use a stale rate.
- revalue_open_balances: runs period-end action. Guard: idempotent per (entity, period); posts revaluation journals reversed on day one of the next period.
- send_dunning: runs cron daily. Guard: one reminder per (invoice, level), capped levels, respects customer opt-out and dispute flags.
- auto_match_bank_lines: runs after statement import. Guard: suggest only above a confidence threshold, never auto-post a difference.
- run_consolidation: runs queue (jobs) on request. Guard: reads closed periods only; writes a snapshot; unmatched intercompany pairs block the run.
- verify_ledger_integrity: runs cron nightly. Guard: recompute trial balance per entity, assert total debits equal credits, compare cached balances; mismatch alerts.
Add when the numbers say so
- Read replicas: Financial statements and group reports move to a read replica when primary CPU > 60% sustained or report p95 > 5 s; postings stay on the primary.
Deferred until it is needed
| Deferred | Adopt it when |
|---|---|
| Bank feeds via open-banking aggregator | customer needs daily automatic statement retrieval. |
| Intercompany netting and consolidation engine | second legal entity added. |
| E-invoicing network (Peppol or country platform) | mandate applies in a launch country. |
| OCR for bill capture | > 200 bills per month per entity. |
| Read replica for statements | report p95 > 5 s. |
On Forge64
Forge64 matches your description to one of 25 reference architectures, writes the plan and data model into the project, generates the app, and runs a quality gate over the code before you see it. Each app gets its own Postgres database and sign-in with no per-user fee. Hosting on Forge Cloud and Forge Payments are rolling out.
Every metered resource on Forge64 has a hard cap, so a traffic spike stops at your limit instead of becoming a surprise bill. This is a reference design for the category, not a promise that every item is generated today. Built-ins such as background jobs, file uploads and search are rolling out; email sent from your app is coming soon, so anything here that needs it is something you add yourself for now.
FAQ
- When should I worry about scale for an accounting system?
- Not before a measured trigger fires. For example, for "Bank feeds via open-banking aggregator": customer needs daily automatic statement retrieval.
- What performance targets should an accounting system aim for?
- Journal post: p95 < 200 ms for a journal of up to 50 lines, one transaction. Trial balance for an entity and period: p95 < 500 ms from the balance summary table. Ledger drill-down page (100 lines): p95 < 120 ms via (entity_id, account_id, posting_date).
- Does Forge64 cap my usage costs?
- Yes. Every metered resource has a hard cap, and paid plans add an overage spend cap that you set.
Build your app with Forge64
One prompt to a running app with auth, database, hosting and payments. Start free, no credit card required.
Start building for free