All skills
clickhouse avatar

/clickhouse-js-node-rowbinary

@faa5b11 official
by clickhouseclickhouse/agent-skills543 stars
39

Generate TypeScript/JavaScript code that reads/decodes AND writes/encodes ClickHouse RowBinary streams for the ClickHouse HTTP server. Use this skill whenever a user wants to parse or produce `RowBinary`, `RowBinaryWithNames`, or `RowBinaryWithNamesAndTypes`. Node.js only, doesn't cover browsers.

Use this Skill: https://skilld.dev/gh/clickhouse/agent-skills/clickhouse-js-node-rowbinary

This session only. Nothing lands on disk.

case-studiesledger-rowbinary-vs-json.md

≈1.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Case study: RowBinary vs JSON on a financial ledger (wide ints & decimals)

TL;DR — When every column is wider than a JS number can hold (UInt128, Int64, Decimal128(18), UInt256), RowBinary wins twice over. Stock JSON.parse is not merely slow here — it is silently wrong, rounding every value to a float64. The only correct JSON path quotes the values server-side and re-parses each string into a bigint/decimal pair by hand, which is ~5x slower than the optimized RowBinary reader over a 2.1–2.6x larger wire. RowBinary reads each value exactly, straight off the wire.

This is the workload the SKILL's format-choice guidance calls out explicitly: "RowBinary clearly wins when the result is dominated by wide numerics — Int128/Int256/UInt128/UInt256, Decimal128/Decimal256."

Reproduce: npx vitest bench --run tests/ledger.bench.ts (against a live ClickHouse server). Source: tests/ledger.bench.ts, reader: src/examples/ledger.ts.

The data

A financial ledger — every column exceeds IEEE-754 double's 53-bit exact range:

txn_id   UInt128         -- 16 bytes
account  Int64           --  8 bytes  (values past 2^53)
amount   Decimal128(18)  -- 16 bytes  (~32 significant digits)
balance  Decimal128(18)  -- 16 bytes
fee      Decimal64(4)    --  8 bytes
volume   UInt256         -- 32 bytes

50,000 rows, fixed-width (96 bytes/row), fetched from a live server.

The correctness trap

ClickHouse emits these types as bare, unquoted JSON numbers. So stock JSON.parse parses them as float64 and silently corrupts every one — measured on row 0 of the live result:

Column Exact value (RowBinary) JSON.parse of bare JSON
txn_id 340282366920938463463374607431768200000 340282366920938463463374607431768211456 ✗ off by 11 456
account 9007199254740993 9007199254740992 ✗ off by 1
amount 98765432109876.123456789012345678 98765432109876.12 ✗ lost 16 digits

No exception, no warning — just wrong numbers. For money and IDs, that is a correctness bug, not a performance footnote.

Making JSON correct costs extra work

The only way to get exact values through JSON is to quote them server-side so they arrive as strings, then re-parse each one:

... SETTINGS output_format_json_quote_64bit_integers = 1,
             output_format_json_quote_decimals = 1
txn_id:  BigInt(r.txn_id),                 // string -> bigint
amount:  parseDecimal(r.amount, 18),       // string -> [unscaled, scale]

That per-field BigInt(...) / decimal parse is work RowBinary doesn't do — it reads the exact bigint directly with two DataView reads — and it lands on top of a larger wire (strings are longer than the binary words).

Wire size (correct paths quote wide values as strings)

Format Size vs RowBinary
RowBinary 4.80 MB 1.0x
JSONCompactEachRow (quoted) 9.88 MB 2.1x
JSONEachRow (quoted) 12.28 MB 2.6x

Decode throughput (full 50k-row decode; higher = faster)

Decoder ops/s ms/decode ≈ rows/s speedup correct?
RowBinary — optimized 130 7.71 ~6.5 M 1.0x ✅
RowBinary — API combinators 80 12.50 ~4.0 M 0.62x ✅
JSONEachRow bare — JSON.parse only 44 22.74 ~2.2 M 0.34x ❌ corrupt
JSONCompactEachRow quoted — parse + BigInt/decimal 26 37.78 ~1.3 M 0.20x ✅
JSONEachRow quoted — parse + BigInt/decimal 25 40.70 ~1.2 M 0.19x ✅

Node 24 / V8. Your numbers will vary; run npm run bench on your own hardware.

Takeaways

  • The fast JSON path is the wrong one. Bare JSON.parse is JSON's quickest option and it is still 2.95x slower than RowBinary — and it silently corrupts every wide value. There is no "fast and correct" JSON here.
  • The correct JSON path is ~5x slower. Quote + per-field BigInt/decimal parsing is the price of correctness, on top of a 2.1–2.6x larger wire.
  • RowBinary is correct by construction. Each value is composed from 64-bit words read at constant offsets (high word signed for the signed types), yielding an exact bigint or [unscaled, scale] pair — no rounding, no string re-parsing.
  • Contrast with the IoT case study: there the numbers fit a float64 and the win was purely throughput (3.5x). Here the values don't fit, so the win is correctness first, throughput second. Match the format to the shape of the data.

Source: SKILL.md on GitHub

No alerts20d3 checks · Risk SAFE
  • Gen Agent Trust Hub20d

    This skill is a legitimate library for generating high-performance ClickHouse RowBinary decoders and encoders, authored by the official ClickHouse organization. Security analysis found the implementation to be safe, using structured parsing for data ingestion and containing no malicious patterns. It includes benchmarking experiments with WebAssembly and standard network connectivity for integration tests, both of which are consistent with its purpose as a database utility.

  • Socket20d

    No alerts

  • Snyk20d

    Risk: LOW · No issues

Signed by skilld at faa5b11. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 3 months ago

README badge

README badge for clickhouse/agent-skills/clickhouse-js-node-rowbinary