Benchmarking REST Direct Execution vs GraphQL Bridge
FraiseQL’s REST transport has two execution paths: direct execution (REST queries bypass GraphQL entirely) and GraphQL bridge (REST requests are translated to GraphQL queries internally). This post shares real benchmark data comparing the two.
What we’re measuring
Section titled “What we’re measuring”When you annotate a query with rest_path, FraiseQL registers a direct SQL execution route. The request flow is:
Direct execution:
HTTP GET /rest/v1/posts → parse URL params → SQL query → JSON envelopeGraphQL bridge (hypothetical — what you’d get without direct execution):
HTTP GET /rest/v1/posts → build GraphQL query → parse → validate → resolve → SQL query → GraphQL response → JSON envelopeThe question: how much does skipping GraphQL parsing, validation, and resolution actually save?
Methodology
Section titled “Methodology”All benchmarks use VelocityBench, our open-source benchmarking harness. The setup:
- Hardware: Single machine, Linux, 32 GB RAM
- Database: PostgreSQL 15, shared
velocitybench_benchmarkdataset - Connections: 50 concurrent connections via
hey - Runs: 3 runs per scenario, 2,000 requests per run, median reported
- Query:
users(limit: 20) { id username fullName }(shallow list)
Results
Section titled “Results”| Metric | REST Direct (projected) | GraphQL (measured) | Difference |
|---|---|---|---|
| Requests/sec | ~34,200 | 29,672 | ~+15% |
| p50 latency | ~1.4 ms | 1.3 ms | ~-8% |
| p99 latency | ~4.8 ms | 5.6 ms | ~-14% |
| Allocations/req | ~42 | ~49 | ~-14% |
| Peak memory | ~14 MB | 16 MB | ~-12% |
The GraphQL column matches our VelocityBench framework-matrix results (29,672 RPS median at 50 connections using hey). The REST Direct column is an estimate based on profiling the allocation savings from skipping the GraphQL layer — not a direct measurement.
Where the savings come from
Section titled “Where the savings come from”We profiled both paths with perf and DHAT. The differences:
- No GraphQL parsing (~3% of projected savings): Skipping
graphql-parserAST construction. For simple queries this is cheap; the savings would grow with query complexity. - No validation pass (~4%): No type-checking the query against the schema. Direct execution knows the types at route registration time.
- No resolver dispatch (~5%): No walking the resolver tree. Direct execution goes straight from URL parameters to SQL WHERE clauses.
- Fewer allocations (~3%): No intermediate GraphQL response that gets re-serialized into the REST envelope. The SQL result is wrapped directly.
These percentages are from DHAT allocation analysis comparing the two code paths. They have not yet been validated with end-to-end throughput benchmarks.
What this doesn’t mean
Section titled “What this doesn’t mean”GraphQL is not slow. 29,672 RPS with 5.6ms p99 is fast by any measure. The GraphQL path is already optimized — async-graphql is one of the fastest GraphQL implementations.
This doesn’t justify choosing REST over GraphQL. If your clients benefit from GraphQL’s flexibility (field selection, nested queries, introspection), the 15% overhead is negligible. Direct execution is an optimization for endpoints where the query shape is fixed and known at deploy time.
Complex queries narrow the gap. The simpler the query, the larger the relative overhead of GraphQL parsing/validation. For queries with multiple joins, nested objects, and complex filters, the SQL execution time dominates and the path difference becomes noise.
When to use direct execution
Section titled “When to use direct execution”Direct execution makes sense for:
- High-traffic read endpoints where every millisecond of p99 matters (dashboards, mobile feeds)
- Public APIs where you want REST semantics (caching, ETags, OpenAPI spec) without the GraphQL layer
- Microservice-to-microservice calls where the query shape is fixed
It doesn’t make sense for:
- Exploratory APIs where clients need field selection flexibility
- Low-traffic endpoints where the difference is unmeasurable
- Endpoints that already use GraphQL clients (Apollo Client, urql) — no reason to add a REST layer
Reproducing the GraphQL numbers
Section titled “Reproducing the GraphQL numbers”The GraphQL baseline comes from VelocityBench:
git clone https://github.com/fraiseql/velocitybench.gitcd velocitybenchdocker compose up -d./bench.sh --framework fraiseqlResults are written to reports/ in Markdown and JSON. The REST direct execution projection is based on allocation profiling with perf and DHAT, not a dedicated VelocityBench scenario — a proper REST-vs-GraphQL benchmark scenario is planned but not yet implemented.