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_out
modules) - Tracks individual operations in a history tableRelational Mappings (from Protobuf directly) - Documentation to come soon
Delayed block signalling (
undo-buffer
flags) - 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 is 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:
transfer
table contains:{id: 'tx1', from: 'alice', to: 'bob', value: 1000}
substreams_history
contains 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
PostgreSQL only: ClickHouse does not support re-org handling due to its architecture
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?