Overview
Graph Node converts GraphQL schemas into PostgreSQL table structures following a systematic set of rules. Understanding this transformation is essential for optimizing subgraph performance and troubleshooting storage issues.Core Schema Generation Rules
Namespace Isolation
Namespace Isolation
Each subgraph’s data is stored in a dedicated PostgreSQL namespace named
sgdNNNN. The mapping between namespace names and deployment IDs is maintained in the deployment_schemas table.Entity Type Mapping
Entity Type Mapping
Each entity type in the GraphQL schema becomes a table with a structure that mirrors the type’s field declarations.
Enum Types
Enum Types
Enums in the GraphQL schema are created as PostgreSQL enum types and used directly in table definitions.
Interface Handling
Interface Handling
Interfaces are not stored in the database. Only concrete types that implement interfaces are stored as tables.
Table Structure
Mutable Entities (Standard @entity)
System Columns
System Columns
vid: Internal version identifier used to uniquely identify specific entity versionsblock_range: Enables time-travel queries by tracking which blocks each entity version is valid for
The
block_range column enables querying historical state at any block height. See the time-travel queries documentation for details.Immutable Entities
Entities declared with@entity(immutable: true) use a simpler structure:
Optimizations for Immutable Entities
Optimizations for Immutable Entities
Block Range Simplification: Since immutable entities never change, the upper bound of
block_range is always infinite. Instead of storing this, we use:block$: Single integer column (not a range)- Visibility check: Simplified to
block$ <= Binstead of range containment
UNIQUE(id)Index Type: Can use simple BTree indexes instead of expensive GiST indexesTimeseries Entities
Timeseries entities use the same structure as immutable entities:The only difference from immutable entities is that timeseries entities must have a
timestamp attribute.Aggregation Entities
Aggregations are represented by multiple tables, one per interval:Aggregation tables are never updated, only appended to. They do not support mutable entities.
Type Mapping
ID Column Types
Supported ID Types
Supported ID Types
ID: Stored astext(alias forStringfor historical reasons)String: Stored astextBytes: Stored asbytea
Primitive Types
Reference Types
Entity References
Entity References
When an attribute references another entity, the column type matches the referenced entity’s
id type.No Foreign Keys: Graph Node does not use foreign key constraints. This allows:- Storing entities that reference entities created later
- Avoiding constraint checking overhead during indexing
- Foreign key violations only detected at query time
- References may return no results if target entity doesn’t exist
Enum Types
Array Types
List types in GraphQL become PostgreSQL array types:Nested arrays like
[[String]] are not allowed in GraphQL schemas. Arrays only contain primitive types or entity references.Indexing Strategy
Graph Node builds indexes extensively due to not knowing query patterns ahead of time. This leads to significant overindexing, but both reducing overindexing and supporting custom indexes are open development areas.
Indexes for Mutable Entities
Exclusion Index
Exclusion Index
Purpose: Ensures that different versions of the same entity have disjoint block ranges.This prevents overlapping versions of the same entity ID.
BRIN Index
BRIN Index
Purpose: Speeds up operations with good data locality (e.g., reversion).Particularly effective for tables where entities are never updated or deleted.
Indexes for Immutable/Timeseries Entities
Attribute Indexes
Graph Node creates indexes for each attribute with a naming convention:Index Naming: attr_N_M
Index Naming: attr_N_M
- N: Number of the entity type in the GraphQL schema
- M: Number of the attribute within that type
attr_3_1 for the second attribute of the fourth entity type (zero-indexed for attributes, but entity types start from 0)String Attribute Indexes
Large string attributes require special handling:String Prefix Indexing
String Prefix Indexing
Problem: PostgreSQL limits individual index entry sizes.Solution: Index only the prefix of string attributes.Query Generation: Automatically adds prefix clauses to queries:The prefix clause allows index usage while the full comparison ensures correctness.
Known Issues and Limitations
Array Performance
Array Performance
Issue: Storing arrays as PostgreSQL array attributes can have catastrophically bad performance.Condition: Arrays with unbounded or large sizes.Impact: Slow queries and storage bloat.Mitigation: Keep array sizes small and bounded.
Overindexing
Overindexing
Issue: Extensive indexing leads to:
- Large storage requirements for indexes
- Slower write operations
Index Usability for Sorting
Index Usability for Sorting
Issue: BTree indexes on single attributes aren’t always usable for sorting.Reason: Graph Node adds A single-column index on
id to all ORDER BY clauses for deterministic ordering.name cannot efficiently support this multi-column sort.Future: PostgreSQL 13’s incremental sorting may help.Custom Indexes
Custom Indexes
Issue: No built-in support for custom indexes makes it hard to transfer manually created indexes between subgraph versions.Convention: Manually created indexes should start with Status: Custom index support is an open development area.
manual_ prefix.Best Practices
Schema Design
Performance Optimization
- Use immutable entities for data that never changes
- Avoid large arrays in entity fields
- Consider manual indexes for common query patterns (prefix with
manual_) - Monitor index usage and remove unused indexes
- Use appropriate ID types (Bytes for addresses, String for human-readable IDs)

