Reorg Handling
This document describes how substreams-sink-sql implements re-organization (re-org) handling to maintain data consistency when blockchain reorganizations occur.
Overview
Blockchain networks can experience reorganizations where previously accepted blocks are replaced by a different chain. When this happens, database changes that were based on the replaced blocks must be reverted to maintain consistency with the canonical chain.
The sink implements different re-org handling strategies depending on the data processing model used:
DatabaseChanges Model (
db_outmodules) - Tracks individual operations in a history tableRelational Mappings (from Protobuf directly) - Documentation to come soon
Delayed block signalling (
undo-bufferflags) - Documentation to come soon
This document currently focuses on the DatabaseChanges model implementation.
DatabaseChanges Model Re-org Handling
The DatabaseChanges model (used by db_out modules) handles re-orgs through a four-phase process:
Tracking changes - Storing a record of all database operations in a history table, only in the reversible segment of the chain, this means there are no operations happening when backfilling historical segments.
Detecting re-orgs - Receiving undo signals when reorganizations occur
Reverting changes - Rolling back operations from forked blocks
Cleaning up - Removing history records for finalized blocks
History Table Schema
The substreams_history table tracks all database operations with the following structure:
Example Walkthrough
Let's trace through a complete re-org scenario using a simple transfer table with the DatabaseChanges model:
1. Normal Operations (Blocks 100-102)
Block 100: Insert new transfer
Block 101: Update existing transfer
Block 102: Delete transfer
2. Re-org Detection
When a re-org is detected at block 101 (meaning blocks 101+ are now invalid), the sink receives an undo signal with lastValidFinalBlock = 100.
3. Reversion Process
The sink queries the history table for all operations after the last valid block:
Results (processed in reverse chronological order):
4. Applying Reversions
Revert Delete (Block 102):
Revert Update (Block 101):
5. History Cleanup
After successful reversion, remove invalidated history records:
6. Final State
The database is now consistent with block 100:
transfertable contains:{id: 'tx1', from: 'alice', to: 'bob', value: 1000}substreams_historycontains only the insert record from block 100
Operation Types and Reversions
Insert (I)
DELETE using primary key
Removes the inserted row
Update (U)
UPDATE with previous values
Restores old column values
Delete (D)
INSERT with previous values
Recreates the deleted row
Upsert
Context-dependent
Saved as either I or U based on whether row existed
Finality and Cleanup
When blocks become final (irreversible), their history records are no longer needed:
This cleanup happens automatically during normal operation to prevent unbounded growth of the history table.
Limitations
Primary keys required: All tables must have primary keys for re-org handling to work
Clickhouse delay: The ClickHouse sink supports re-org handling but with a slight delay compared to PostgreSQL
Performance impact: History tracking adds overhead to write operations
Storage growth: History table grows with write volume until cleanup occurs
Troubleshooting
Missing primary key error: Ensure all tables have primary keys defined and that substreams output matches the schema.
Performance issues: Consider adjusting flush intervals or using undo-buffer-size for high-throughput scenarios.
History table growth: Monitor cleanup operations and finality progression to ensure proper pruning.
Last updated
Was this helpful?

