3.8 KiB
3.8 KiB
Database Indexes & Query Optimization
Optimize query performance with strategic indexing.
Why Indexes Matter
| Data Size | Without Indexes | With Indexes |
|---|---|---|
| 1,000 rows | ~10ms | ~1ms |
| 100,000 rows | ~500ms | ~2ms |
| 1,000,000+ rows | 5+ seconds | ~5ms |
Single-Column Indexes
Use @index directive on frequently queried fields:
type Transaction {
id: ID!
userAddress: String! @index
tokenAddress: String! @index
amount: BigInt!
timestamp: BigInt! @index
}
Use when:
- Frequently filter by a field
- Sort results by a field
- Field has many different values (high cardinality)
Composite Indexes
For multi-field queries, use entity-level @index:
type Transfer @index(fields: ["from", "to", "tokenId"]) {
id: ID!
from: String! @index
to: String! @index
tokenId: BigInt!
value: BigInt!
timestamp: BigInt!
}
Creates:
- Individual indexes on
fromandto - Composite index on
from + to + tokenId
Use when:
- Query multiple fields together
- "Find transfers from X to Y for token Z"
Multiple Composite Indexes
type NFTListing
@index(fields: ["collection", "status", "price"])
@index(fields: ["seller", "status"]) {
id: ID!
collection: String! @index
tokenId: BigInt!
seller: String! @index
price: BigInt!
status: String! @index # "active", "sold", "cancelled"
createdAt: BigInt! @index
}
Supports:
- Active listings for collection, sorted by price
- Listings by seller with status
- Recently created listings
Automatic Indexes
HyperIndex auto-indexes:
- All
IDfields - All
@derivedFromfields
No manual indexing needed for these.
Common Index Patterns
Token Transfers
type TokenTransfer {
id: ID!
token_id: String! @index
from: String! @index
to: String! @index
amount: BigInt!
blockNumber: BigInt! @index
timestamp: BigInt! @index
}
DEX Swaps
type Swap @index(fields: ["pair", "timestamp"]) {
id: ID!
pair_id: String! @index
sender: String! @index
amountIn: BigInt!
amountOut: BigInt!
timestamp: BigInt! @index
}
User Activity
type UserAction @index(fields: ["user", "actionType", "timestamp"]) {
id: ID!
user: String! @index
actionType: String! @index
timestamp: BigInt! @index
amount: BigInt!
}
Performance Tradeoffs
Write Impact
| Index Level | Write Slowdown | Read Speed |
|---|---|---|
| No indexes | Baseline | Slowest |
| Few targeted | 5-10% | Fast |
| Many indexes | 15%+ | Fastest |
Blockchain data is read-heavy - indexes usually worth it.
Storage
- Each index: 2-10 bytes per row
- Consider for very large tables (millions+ rows)
Query Optimization Tips
Fetch Only What You Need
# Good
query {
Transfer(where: { token: { _eq: "0x123" } }, limit: 10) {
id
amount
}
}
# Bad - unnecessary fields
query {
Transfer(where: { token: { _eq: "0x123" } }, limit: 10) {
id
from
to
amount
timestamp
blockNumber
transactionHash
# ... more fields
}
}
Always Paginate
query {
Transfer(
where: { token: { _eq: "0x123" } }
limit: 20
offset: 40 # Page 3
) {
id
amount
}
}
Filter on Indexed Fields
# Fast - userAddress is indexed
query {
Transaction(where: { userAddress: { _eq: "0x..." } }) { ... }
}
# Slow - amount is not indexed
query {
Transaction(where: { amount: { _gt: "1000" } }) { ... }
}
Index Checklist
When designing schema:
- Index fields used in
whereclauses - Index fields used in
order_by - Add composite indexes for multi-field queries
- Consider cardinality (high variety = good index candidate)
- Don't over-index write-heavy entities
- Test query performance with realistic data volumes