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 table

  • Relational 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:

  1. 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.

  2. Detecting re-orgs - Receiving undo signals when reorganizations occur

  3. Reverting changes - Rolling back operations from forked blocks

  4. 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

Original Operation
Reversion Strategy
Notes

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?