Skip to content
About
Main content

JDBC, ODBC, REST and GraphQL

Why do row cursors break on large results, and who absorbs the mismatch in REST versus GraphQL?

JDBC and ODBC

JDBC (Java) and (a C-based standard) are standard database driver interfaces. They exist because of an , not a data problem. Every client tool against every engine would be a combinatorial explosion of integrations. A standard interface in the middle turns . (For example, 20 tools and 15 engines: 20 × 15 = 300 integrations without a standard, 20 + 15 = 35 with one.) This is the constructive form of Principle B: a shared standard is valuable because many parties adopt it.

What they force: the cursor

They impose a abstraction: connect, prepare, execute, fetch (row by row or in small batches), close. This shape is not neutral:

  • It is , so a engine must transpose its results to fit.
  • It is stateful: the connection holds session context (transaction, temporary objects, settings). Losing the connection loses that context.
  • It is synchronous: a slow client puts on the server, which keeps holding the result.

The result everyone eventually meets: this interface is excellent for thousands of small results and structurally bad for one huge result.

Result egress: only three ways out

There are exactly three ways a large result can leave an engine:

WayHow it worksCost behaviour
Through the cursorRows are fetched through the sessionPer-row overhead; holds a session; client pace sets the speed
Columnar RPCBatches of columns are sent intact from end to endLess per-row overhead; still a live connection
The engine writes files to shared storage (such as object storage) and gives the client a pointerCost does not depend on how fast the client can read

Unload is the only one whose cost does not scale with the client's ability to keep up. It decouples production from consumption by putting durable storage between them. Note what that is: buffering, reappearing at the boundary because the two parties cannot be made to run at the same speed. The mismatch is not removed. It is absorbed by a third medium.

-- Typical shape of an unload (syntax differs between engines):
COPY (SELECT * FROM shipments WHERE ship_date >= '2026-01-01')
TO 's3://harborline-exports/2026/' (FORMAT PARQUET);
-- The client then receives a list of files and reads them in parallel.

In one sentence: a row cursor is a contract to go at the client's speed, which is fine until the result is bigger than the client's patience.


REST and GraphQL

Here the constraint is different: the callee does not know who the callers are or how many there will be. Under that constraint, stops being a style preference and becomes the only way to survive. If no request depends on an earlier one:

  • any server can answer any request,
  • servers can be added and removed freely,
  • a dead connection costs only a .

Every scaling property that people attribute to comes from that one choice.

The price is paid twice

  1. Context is re-sent on every request. Authentication, identity and position travel again and again.
  2. Statelessness plus a fixed response shape means the caller often needs several requests to build one answer. When the caller is far away, multiplies by the number of round trips.

Under-fetching (not enough data, so more calls) and over-fetching (too much data per call) are two faces of one fact: the callee chose the shape, and the callers' needs vary.

GraphQL: let the caller choose the shape

reverses exactly that:

{
  shipment(id: 42) {
    status
    carrier { name }
    port { code }
    customer { name }
  }
}

The gain is one round trip for any need. The cost: the server can no longer predict what it will be asked, so the cost of one request is unbounded and unpredictable. Unpredictable cost per request ruins capacity planning (how many servers you need) and the isolation between tenants (one customer's heavy query slows everyone).

So every mature GraphQL service grows the same defences: depth limits, , cost estimation, . The last one is the tell: it ends by fixing the set of query shapes again, which is to say it re-creates the endpoint. This is the idea that variance costs more than volume, as an interface property: a service can handle a lot of load of a known shape but not a little load of unknown shapes.

In one sentence: . The server pays in round trips, or the client pays in unpredictable server cost.