Skip to content

Performance ​

vs node-firebird 2.x ​

Measured against node-firebird 2.10.0 (the current, substantially modernized 2.x line — re-measured 2026-07-18) on Firebird 5 with an in-process latency proxy (pnpm --filter @fast-firebird/benchmarks bench), defaults vs defaults. Columns are one-way link delay (RTT ≈ 2×):

Scenario0ms2ms10ms
warm select ×200 (open tx)3.0×3.1×3.1×
10k-row scan1.1×1.3×1.7×
300-row insert (1 tx)1.7×1.6×1.9×
1MB blob write+read21×115×152×
scan 300 rows × 8KB blobs33×124×140×

Blobs dominate because node-firebird reads each blob sequentially through a per-blob open/read/close cycle in 1KB chunks (its default blobReadChunkSize) — ~1000 round trips per MB; fast-firebird uses 64KB segments with deep pipelining — and on FB 5.0.2+ small blobs ride inline with the rows for zero extra round trips.

The one case where fast-firebird is slower — bare connect+detach on a 0ms link (0.8×) — is because it encrypts the wire by default (SRP256 + op_crypt); pass wireCrypt: 'disabled' to match.

Reproduce it

The benchmark harness lives in the repository (packages/benchmarks) — same compose file as the test suite, methodology in the source. Run pnpm --filter @fast-firebird/benchmarks bench against the pnpm fb:up container matrix.

Where the blob speedup comes from ​

The same 300×8KB scan run under each of fast-firebird's blob strategies (round trips include prepare/execute/fetch; the scan itself is ~21):

Strategy0ms2ms10ms
eager + FB5 inline blobs (default)32 ms (21 RT)159 ms (21 RT)582 ms (21 RT)
eager, inline off (pipelined open/read/close)60 ms (217 RT)400 ms (217 RT)1740 ms (217 RT)
queryStream lazy, on-demand314 ms (607 RT)3700 ms (607 RT)14695 ms (607 RT)
queryStream lazy + blobReadAhead, 10ms/row consumer work3295 ms3867 ms14566 ms
queryStream lazy on-demand, 10ms/row consumer work3725 ms7352 ms17902 ms

Reading the table: FB5 inline blobs eliminate blob round trips entirely (21 vs 217); eager pipelining amortizes open/read/close across the batch (~0.7 RT per blob vs 2 sequential ones on the lazy path). blobReadAhead doesn't cut round trips — it overlaps them with your per-row processing: with 10ms of consumer work per row (a disk write, an image resize), read-ahead hides the blob fetches almost completely at 2ms link delay (3.9s ≈ the 3.7s pure-fetch floor, vs 7.4s on-demand). Use eager (the default) when rows fit in memory; stream + read-ahead when they don't.

Round-trip design ​

Why the driver wins on ordinary queries too:

  • statement allocate+prepare pipelined into one round trip (lazy-send)
  • execute+first-fetch and execute+record-counts coalesced into single packets
  • deferred op_free_statement/op_close_blob (they ride the next packet)
  • adaptive fetch batching sized from the described row width
  • executeBatch: thousands of DML rows per round trip

Attachment.roundTrips exposes the flush counter for your own budget assertions — many of the driver's own tests assert exact counts.

MIT Licensed. Contains data derived from the Firebird project under IDPL/IPL.