Overview
Time-travel queries let you retrieve the exact state of entities as they existed at a specific block height. Graph Node achieves this by storing entity versions with block ranges indicating their validity period.How It Works
Entity Versioning
For each entity, Graph Node maintains multiple versions in the database:Unique version identifier for this specific entity version.
The entity’s logical ID (can have multiple versions).
Entity attribute(s) - specific to your schema.
PostgreSQL range indicating blocks where this version is valid:
[start, end)- Start (inclusive): Block where this version was created
- End (exclusive): Block where this version was replaced or deleted
- Current version has unlimited upper bound:
[7, )
Block Range Examples
Immutable Entities
Entities declared with@entity(immutable: true) use optimized storage:
- Uses
block$(INT) instead ofblock_range(INT4RANGE) - Stores only the creation block
- Check becomes:
block$ <= $Binstead ofblock_range @> $B - Enforces
UNIQUE(id)constraint - Enables simpler, faster BTree indexes
Immutable entities can never be updated or deleted, only created. This matches blockchain event semantics where events are permanent.
Querying Historical State
Block Height Argument
All entity queries accept an optionalblock argument:
Block height to query state at.
Block hash to query state at (alternative to number).
Single Entity Queries
Collection Queries
Apply time-travel to collection queries:Nested Entity Queries
Time-travel applies to nested entities automatically:SQL Translation
Here’s how Graph Node translates time-travel queries to SQL:Query Entity at Block
Collection Query at Block
Immutable Entity Query
For immutable entities:Entity Operations with Block Ranges
Create Entity
When creating an entity at block B:Update Entity
Updating at block B:Immutable entities cannot be updated - the operation is not allowed.
Delete Entity
Deleting at block B:Rollback / Revert
Reverting to block B (removing all changes after B):Use Cases
Historical Analytics
Historical Analytics
Analyze how metrics evolved over time:
Snapshots and Reporting
Snapshots and Reporting
Generate reports for specific points in time:
Auditing and Verification
Auditing and Verification
Verify state at specific blocks for auditing:
Time-Series Analysis
Time-Series Analysis
Build time-series data by querying multiple blocks:
Debugging and Investigation
Debugging and Investigation
Investigate issues by checking state before/after specific blocks:
Block Number vs Block Hash
Performance Considerations
Indexing
Graph Node creates indexes to optimize time-travel queries:Query Performance
Recent Blocks (Fast)
Recent Blocks (Fast)
Queries for recent blocks are very fast:
- Current state (no block specified): Fastest
- Recent blocks (< 1000 blocks ago): Fast
- PostgreSQL effectively caches recent data
Historical Blocks (Slower)
Historical Blocks (Slower)
Queries for distant historical blocks:
- May scan more data to find correct versions
- BRIN indexes help but queries are slower than recent blocks
- Consider caching results for frequently-accessed historical states
Optimization Tips
Optimization Tips
- Query recent state when possible: Current state is always fastest
- Batch historical queries: If analyzing many blocks, batch them into single query
- Use specific filters: Narrow queries with
whereclauses - Consider aggregations: Use aggregation features for time-series analytics instead of many point queries
Limitations
- Block hash lookups: Querying by block hash for distant blocks can be expensive
- Storage overhead: Maintaining version history increases storage requirements
- Immutable entities: Time-travel works but is trivial - each entity exists at only one block
- Cross-subgraph queries: Time-travel applies to single subgraph only
Example: Token Balance History
Track how a token’s total supply changed over time:Best Practices
- Use current state by default: Only specify
blockwhen historical data is needed - Cache historical queries: Results for past blocks never change - cache them
- Query recent blocks: More efficient than distant historical blocks
- Use block numbers: Simpler than block hashes for most use cases
- Consider aggregations: For time-series analysis, use aggregation features instead of many point queries
- Document block heights: When storing block numbers, document what events they represent
Next Steps
- Explore aggregation queries for efficient time-series analytics
- Learn about query filters for precise historical queries
- Review GraphQL API overview for basic concepts

