Skip to content

Benchmarks ​

The xqdb repository includes two benchmark harnesses: a Node.js suite and a Python suite. Each compares xqdb with every other q IPC client that can be measured legally and technically, all against one fixed q server. Every number on this page is a client-side measurement. None of it is a claim about kdb+ or KDB-X performance.

These numbers come from the v0.1.3 codec

The reports in benchmarks/results/ at the v0.1.8 tag were measured on the v0.1.3 codec. The Node.js suite recorded @xbbg/xqdb 0.1.3, and the Python suite recorded a build of the v0.1.3 commit. Release 0.1.4 changed nothing the suite measures. Releases 0.1.5 to 0.1.8 did change the codec: fallible allocations, 40-bit message framing, lossy text decoding, symbol lists validated in one pass instead of two, and empty dictionaries. The checked-in reports have not been updated for those releases.

The release notes for 0.1.5 include a separate regression check against kola in Python, with 100,000 rows and 50 iterations, compared within each run. It measured xqdb against kola at 43.3 ms vs 96.3 ms for read wide, 12.4 ms vs 15.7 ms for read trade, 14.3 ms vs 17.1 ms for read depth, and 17.9 ms vs 22.4 ms for send trade.

Results at a glance ​

These results compare the fastest median for each operation, exactly as measured:

  • Node.js. xqdb had the fastest median on all six table operations. The next fastest client took between 1.31× (send wide) and 33.8× (read wide) as long. jkdb had the fastest scalar round trip, 0.346 ms against xqdb's 0.351 ms, a difference of 5 µs.
  • Python. xqdb with the PyArrow backend had the fastest median on all seven operations. In the scalar round trip, which measures the latency floor of a single request, the xqdb backends and kola finished within 0.02 ms of each other.

In each chart below, the filled bar is the median and the outlined bar is the 90th percentile (p90), both in milliseconds. Lower is better. The highlighted row has the fastest median for that operation.

Node.js ​

Reads ​

Query a 100,000-row table and decode it into the client's own representation.

Trade table (14 columns)

  1. xqdb0.1.3(fastest median)
    median: 24.8 ms p90: 26.9 ms
  2. jkdb1.4.0
    median: 186.9 ms p90: 203.9 ms
  3. node-q2.7.0
    median: 198.1 ms p90: 215.5 ms

Wide table (64 columns)

  1. xqdb0.1.3(fastest median)
    median: 91.0 ms p90: 102.9 ms
  2. jkdb1.4.0
    median: 3,073 ms p90: 3,298 ms
  3. node-q2.7.0
    median: 3,198 ms p90: 3,368 ms

Depth table (nested lists)

  1. xqdb0.1.3(fastest median)
    median: 26.3 ms p90: 28.7 ms
  2. jkdb1.4.0
    median: 203.0 ms p90: 211.8 ms
  3. node-q2.7.0
    median: 201.6 ms p90: 216.0 ms

Sends ​

Send the client's own decoded table back to q. node-q is excluded because its decoded tables do not re-encode to a q-identical value.

Trade table (14 columns)

  1. xqdb0.1.3(fastest median)
    median: 26.7 ms p90: 70.1 ms
  2. jkdb1.4.0
    median: 46.4 ms p90: 88.1 ms
  3. node-q2.7.0
    median: not comparable

Wide table (64 columns)

  1. xqdb0.1.3(fastest median)
    median: 137.1 ms p90: 144.2 ms
  2. jkdb1.4.0
    median: 179.4 ms p90: 194.6 ms
  3. node-q2.7.0
    median: not comparable

Depth table (nested lists)

  1. xqdb0.1.3(fastest median)
    median: 28.8 ms p90: 71.8 ms
  2. jkdb1.4.0
    median: 54.9 ms p90: 95.1 ms
  3. node-q2.7.0
    median: not comparable

Scalar round trip ​

Scalar round trip

  1. xqdb0.1.3
    median: 0.351 ms p90: 0.377 ms
  2. jkdb1.4.0(fastest median)
    median: 0.346 ms p90: 0.380 ms
  3. node-q2.7.0
    median: 0.361 ms p90: 0.404 ms
OperationPayloadxqdb 0.1.3jkdb 1.4.0node-q 2.7.0
read trade10.5 MiB★ 24.8 ms · 424 MiB/s186.9 ms (7.55×)198.1 ms (8.00×)
read wide48.6 MiB★ 91.0 ms · 535 MiB/s3,073 ms (33.8×)3,198 ms (35.2×)
read depth10.8 MiB★ 26.3 ms · 409 MiB/s203.0 ms (7.71×)201.6 ms (7.66×)
send trade10.5 MiB★ 26.7 ms · 393 MiB/s46.4 ms (1.74×)not comparable
send wide48.6 MiB★ 137.1 ms · 355 MiB/s179.4 ms (1.31×)not comparable
send depth10.8 MiB★ 28.8 ms · 374 MiB/s54.9 ms (1.90×)not comparable
scalar round tripnone0.351 ms★ 0.346 ms (0.99×)0.361 ms (1.03×)

Each cell gives the median. ★ marks the fastest median. Ratios are each client's median divided by xqdb's. Throughput is the payload size divided by xqdb's median.

Python ​

Reads ​

Each client materializes its own frame type: kola returns Polars and qconnect returns pandas. xqdb is shown with all three backends.

Trade table (14 columns)

  1. xqdbpyarrow(fastest median)
    median: 14.6 ms p90: 15.7 ms
  2. xqdbpolars
    median: 15.3 ms p90: 16.6 ms
  3. xqdbpandas
    median: 17.6 ms p90: 20.0 ms
  4. kola2.5.1
    median: 17.7 ms p90: 19.6 ms
  5. qconnect0.1.6
    median: 97.7 ms p90: 104.1 ms

Wide table (64 columns)

  1. xqdbpyarrow(fastest median)
    median: 50.3 ms p90: 55.1 ms
  2. xqdbpolars
    median: 51.2 ms p90: 56.3 ms
  3. xqdbpandas
    median: 54.7 ms p90: 70.6 ms
  4. kola2.5.1
    median: 105.8 ms p90: 139.5 ms
  5. qconnect0.1.6
    median: 191.2 ms p90: 239.3 ms

Depth table (nested lists)

  1. xqdbpyarrow(fastest median)
    median: 18.3 ms p90: 22.8 ms
  2. xqdbpolars
    median: 19.1 ms p90: 22.4 ms
  3. xqdbpandas
    median: 35.7 ms p90: 43.7 ms
  4. kola2.5.1
    median: 20.9 ms p90: 25.3 ms
  5. qconnect0.1.6
    median: 11,711 ms (beyond the chart scale) p90: 14,311 ms
The scale is clipped at 60 ms. qconnect's bars fade out at the edge; its values are 11,711 ms (median) and 14,311 ms (p90).

Sends ​

kola is excluded from send depth because it aborts the interpreter when it sends a frame with list columns.

Trade table (14 columns)

  1. xqdbpyarrow(fastest median)
    median: 20.3 ms p90: 60.8 ms
  2. xqdbpolars
    median: 20.6 ms p90: 62.9 ms
  3. xqdbpandas
    median: 23.3 ms p90: 64.1 ms
  4. kola2.5.1
    median: 24.5 ms p90: 65.8 ms
  5. qconnect0.1.6
    median: 64.0 ms p90: 104.3 ms

Wide table (64 columns)

  1. xqdbpyarrow(fastest median)
    median: 111.8 ms p90: 133.8 ms
  2. xqdbpolars
    median: 115.7 ms p90: 147.4 ms
  3. xqdbpandas
    median: 131.5 ms p90: 183.3 ms
  4. kola2.5.1
    median: 124.0 ms p90: 162.1 ms
  5. qconnect0.1.6
    median: 262.9 ms p90: 338.6 ms

Depth table (nested lists)

  1. xqdbpyarrow(fastest median)
    median: 23.6 ms p90: 66.2 ms
  2. xqdbpolars
    median: 23.8 ms p90: 66.6 ms
  3. xqdbpandas
    median: 54.1 ms p90: 95.5 ms
  4. kola2.5.1
    median: not comparable
  5. qconnect0.1.6
    median: 2,186 ms (beyond the chart scale) p90: 2,568 ms
The scale is clipped at 120 ms. qconnect's bars fade out at the edge; its values are 2,186 ms (median) and 2,568 ms (p90).

Scalar round trip ​

Scalar round trip

  1. xqdbpyarrow(fastest median)
    median: 0.347 ms p90: 0.417 ms
  2. xqdbpolars
    median: 0.366 ms p90: 0.395 ms
  3. xqdbpandas
    median: 0.352 ms p90: 0.401 ms
  4. kola2.5.1
    median: 0.350 ms p90: 0.419 ms
  5. qconnect0.1.6
    median: 0.400 ms p90: 0.467 ms
Operationxqdb pyarrowxqdb polarsxqdb pandaskola 2.5.1qconnect 0.1.6
read trade★ 14.6 ms · 717 MiB/s15.3 ms (1.04×)17.6 ms (1.20×)17.7 ms (1.21×)97.7 ms (6.68×)
read wide★ 50.3 ms · 967 MiB/s51.2 ms (1.02×)54.7 ms (1.09×)105.8 ms (2.10×)191.2 ms (3.80×)
read depth★ 18.3 ms · 590 MiB/s19.1 ms (1.05×)35.7 ms (1.95×)20.9 ms (1.14×)11,711 ms (642×)
send trade★ 20.3 ms · 517 MiB/s20.6 ms (1.01×)23.3 ms (1.15×)24.5 ms (1.21×)64.0 ms (3.15×)
send wide★ 111.8 ms · 435 MiB/s115.7 ms (1.04×)131.5 ms (1.18×)124.0 ms (1.11×)262.9 ms (2.35×)
send depth★ 23.6 ms · 457 MiB/s23.8 ms (1.01×)54.1 ms (2.30×)not comparable2,186 ms (92.7×)
scalar round trip★ 0.347 ms0.366 ms (1.05×)0.352 ms (1.01×)0.350 ms (1.01×)0.400 ms (1.15×)

Ratios are each subject's median divided by the median of xqdb with PyArrow. Because kola and qconnect produce different frame types, the report also compares each of them with the xqdb backend that builds the same frame type:

Operationkola vs xqdb polarsqconnect vs xqdb pandas
read trade1.16×5.55×
read wide2.06×3.49×
read depth1.10×328×
send trade1.19×2.75×
send wide1.07×2.00×
send depthnot comparable40.4×
scalar round trip0.96×1.14×

A ratio below 1 means the other client was faster. kola's scalar round trip median was 4% lower than that of xqdb with Polars.

Fixture ​

The server is KDB-X 5.0 (build 2026.07.23) running in a container on the same machine as the client, reached over loopback TCP. Its init.q sets the random seed with \S 42 and publishes three tables of 100,000 rows each:

TableColumnsWhat it stressesPayload (count -8!t)
trade14Narrow table: a symbol, a timestamp, a long, a char vector, and 10 floats11,000,178 bytes (10.5 MiB)
wide64Column count51,000,768 bytes (48.6 MiB)
depth5Nested lists: ask and bid hold a list of 5 floats per row11,300,077 bytes (10.8 MiB)

Before any timing, every client is asked for the row count, the q version, and the seed, and the run stops unless all clients agree.

SuiteRuntimeClients
Node.jsNode.js 26.3.0 and apache-arrow 21.2.0 on Windows x64@xbbg/xqdb 0.1.3; jkdb 1.4.0 with useBigInt: true; node-q 2.7.0 with flipTables: false
PythonCPython 3.12.13 on Windows x64, with narwhals 2.25.0, pyarrow 25.0.1, pandas 3.0.5, polars 1.44.1, and numpy 2.5.2xqdb (v0.1.3 codec) with its PyArrow, Polars, and pandas backends; kola 2.5.1; qconnect 0.1.6 with TLS disabled for parity

Method ​

OperationWork timed
scalarA 6f*7f round trip, the latency floor of a single request
read <table>Query the table and build the client's own frame type
send <table>Send the client's own decoded frame to {[x]count x}
  • Rounds. 3 warm-up runs, then 50 measured rounds per client per operation. Each round runs every client once, in an order reshuffled from seed 42. A simple rotation is not enough, because it keeps each client behind the same predecessor every round.
  • Timing. process.hrtime.bigint() or time.perf_counter_ns() around a single request, with one request in flight per client. Percentiles use the nearest rank of the raw durations.
  • Validation. Every measured result is checked after its timed interval.
  • Throughput. The payload size, the server's count -8!t for the table, divided by the median duration. It is one logical size shared by every client, not the bytes observed on the wire.

Comparability ​

The suite ranks speed only when clients do the same work:

  • scalar must return the exact value.
  • read is ranked for every client that can decode the table, because the server sends the same bytes to all of them.
  • send is ranked only for clients whose decoded frame re-encodes to a value that q reports as identical (~) and that has the same count -8! size. Otherwise the client would be timed encoding a different value.
  • Exactness of int64 values and nanosecond timestamps is checked before timing and is never timed itself.

Fidelity findings ​

  • xqdb. Round-trips all three tables to q-identical values. Python raises ValueError rather than silently truncating a sub-microsecond timestamp atom into a datetime.
  • jkdb 1.4.0. Round-trips all three tables to q-identical values. Sub-millisecond timestamps need includeNanosecond: true, which turns temporal values into text that its own encoder then rejects, so that check runs on a second connection.
  • node-q 2.7.0. Decodes int64 values to doubles (9007199254740993 reads back as 9007199254740992) and timestamps to millisecond Date values. None of its decoded tables re-encodes to a q-identical value, so it is excluded from every send. Its other documented modes restore exactness but roughly double decode time.
  • kola 2.5.1. Rounds nanosecond timestamps to microseconds without warning. When sending a frame with list columns at 100,000 rows, its serializer panics and aborts the process, so it is excluded from send depth. Its depth read is unaffected, but the read's round trip could not be verified.
  • qconnect 0.1.6. Decodes int64 values and nanosecond timestamps exactly.

Not measured ​

CandidateReason
PyKXThe license for its bundled q runtime forbids publishing performance comparisons.
qpython, qpython3They need numpy below 1.20 and Python 3.9 or older. qconnect is the maintained fork and is measured instead.
PyQIt embeds Python inside q rather than acting as a client.
npm kx, qnodeUnrelated packages that happen to use those names. KX publishes no Node.js q client on npm.

Reproduce ​

The harnesses, the fixture, and the raw JSON reports are in benchmarks/ in the repository. Building the fixture container needs your own KX download token and KDB-X license. Each run prints a summary and writes a JSON report. The reports also record a memory diagnostic, which is not shown here because it measures the whole process and is too noisy to compare libraries.

xbbg is independent open-source software and is not affiliated with, endorsed by, sponsored by, or approved by Bloomberg Finance L.P.