ormDB

Performance Optimization in ormDB

Master ormDB's query budgets, fanout limits, field selection, and tuning parameters to build high-performance applications on ormDB.

Difficulty: advancedTime: 20 minutes

Performance by Design

ormDB is a relational database engine written in Rust that replaces PostgreSQL, MySQL, and SQLite. Performance optimization in ormDB starts with the architecture: graph fetches eliminate N+1 queries, the zero-copy wire protocol eliminates serialization overhead, and query budgets prevent runaway queries. This guide covers how to use these features and tune them for your workload.

Query Budgets

Query budgets are ormDB’s primary mechanism for preventing expensive queries from degrading system performance. A budget is a numeric limit on the computational cost of a single query, measured in internal cost units that account for records scanned, relations traversed, and result size.

Set a default budget per session:

const session = db.session({
  user_id: user.id,
  query_budget: 10000, // default budget for all queries in this session
})

Override the budget on individual queries:

const results = await session.graphFetch('User', {
  include: {
    posts: {
      include: { comments: true },
    },
  },
  budget: 50000, // higher budget for this specific query
})

When a query exceeds its budget, ormDB returns a partial result:

{
  records: [...],  // results gathered before budget exhaustion
  truncated: true,
  budget_consumed: 10000,
  budget_limit: 10000,
}

Budget Strategy

Assign budgets based on the context:

  • Public API endpoints: Low budgets (5,000-10,000) to prevent abuse
  • Authenticated user requests: Medium budgets (10,000-50,000) for normal application queries
  • Admin dashboards: Higher budgets (50,000-200,000) for complex data views
  • Background jobs: Highest budgets (200,000+) for batch processing and reports

Fanout Limits

Graph fetches can produce combinatorial explosion when entities have many related records. A user with 1,000 posts, each with 100 comments, produces 100,000 comment records in a single query. Fanout limits cap the number of related records at each level:

graph_fetch User [limit: 50] {
  id, name,
  posts [limit: 20, order_by: { created_at: desc }] {
    id, title,
    comments [limit: 10, order_by: { created_at: desc }] {
      id, body,
      author { id, name }
    }
  }
}

Maximum result size: 50 users x 20 posts x 10 comments = 10,000 comment records. Without fanout limits, the same query could return millions.

Field Selection

Fetch only the fields you need. Over-fetching wastes bandwidth and memory:

// Bad: fetching all fields including large text columns
const users = await db.graphFetch('User', {
  fields: ['*'],
  include: { posts: { fields: ['*'] } },
})

// Good: fetching only needed fields
const users = await db.graphFetch('User', {
  fields: ['id', 'name', 'avatar_url'],
  include: {
    posts: {
      fields: ['id', 'title', 'created_at'],
      limit: 10,
    },
  },
})

Field selection applies at every level of the graph. The zero-copy wire protocol means that selected fields are read directly from the response buffer without materializing unselected fields.

Connection Pooling

ormDB supports connection pooling at the engine level:

const db = new OrmDB('ormdb://localhost:5555/mydb', {
  pool: {
    min: 5,
    max: 20,
    idle_timeout: 30000, // ms
  },
})

Size the pool based on your application’s concurrency. For SaaS applications and microservices, connection pool sizing is especially important. Each connection maintains its own session context (user identity, query budget), so connections are not shared across requests.

Index Optimization

ormDB automatically creates indexes for primary keys and relations. Additional indexes improve performance for filtered queries:

entity Order {
  id: uuid @primary
  status: text @index
  customer_id: uuid @relation(Customer) // auto-indexed
  created_at: timestamp @index
  total: integer

  @index([status, created_at]) // composite index
}

Use composite indexes for queries that filter on multiple columns. The index column order should match your most common query patterns: the most selective column first. For guidance on structuring your entities effectively, see schema design patterns.

Monitoring and Diagnostics

ormDB exposes query statistics for every request:

const result = await db.graphFetch('User', {
  include: { posts: true },
  explain: true,
})

console.log(result.stats)
// {
//   execution_time_ms: 12,
//   budget_consumed: 3400,
//   entities_scanned: 150,
//   relations_traversed: 150,
//   result_records: 750,
//   cache_hits: 120,
// }

The explain flag adds execution statistics without changing the query results. Use this in development to identify queries that consume more budget than expected, scan excessive records, or return more data than your application uses.

Performance in ormDB is not about writing better SQL. It is about setting appropriate budgets, limiting fanout, selecting only needed fields, and letting the engine’s native graph-fetch architecture do the work. See how this compares to traditional approaches in ormDB vs PostgreSQL and ormDB vs traditional ORMs.

Frequently Asked Questions

What is a query budget?

A query budget is an engine-level limit on the computational cost of a single query. It prevents runaway graph fetches from consuming excessive resources. When a query exceeds its budget, ormDB returns a partial result with a truncation indicator rather than letting the query run indefinitely.

How do fanout limits work?

Fanout limits cap the number of related records returned at each level of a graph fetch. Setting a fanout limit of 100 on a posts relation means each user returns at most 100 posts, regardless of how many exist. This prevents combinatorial explosion in deep graph fetches.

Does the zero-copy wire protocol really make a difference?

Yes. Traditional database protocols serialize data to text or binary format, transmit it, then deserialize on the client. ormDB's rkyv-based protocol allows the client to read response data directly from the wire buffer without a deserialization step. For large result sets, this eliminates a significant CPU and memory overhead.

How do I identify slow queries?

ormDB's query log includes execution time, budget consumption, and traversal statistics for every query. You can identify queries that consume disproportionate budget or traverse unexpected numbers of records.

Can I set different budgets for different users or endpoints?

Yes. Query budgets can be set per-session, allowing you to assign higher budgets to admin users or background jobs and lower budgets to public API endpoints.

What happens when a query exceeds its budget?

ormDB returns the results gathered so far along with a truncation flag and the budget consumed. Your application can detect the truncation and decide whether to paginate, narrow the query, or raise an error.

Related Content

Try ormDB today

Open source, MIT licensed. Install and start building.