Skip to content

Commit 6593cb5

Browse files
lz4: replace unmaintained lz4 with maintained lz4-napi (frame-compatible)
Root cause of the Node 22+ CI failures: `lz4@0.6.5` (2021, unmaintained) is a node-gyp native addon with NO prebuilt binaries. It fails to compile on Node 22+, gets silently skipped as an optional dep, then tests that require('lz4') crash with "Cannot find module 'lz4'". Node 20 passed; 22/24/26 failed — confirmed via fail-fast:false per-version run. Replaces it with `lz4-napi@2.9.0` (maintained, napi-rs, ships prebuilds -> no local toolchain build, works on all Node versions). Verified locally that lz4-napi's compressFrameSync/decompressFrameSync are byte-compatible with the LZ4 *frame* format (magic 0x184D2204) the old package used and the Databricks server sends — round-trip and cross-decode both directions, incl. large buffers. lib/utils/lz4.ts now wraps lz4-napi behind the same { encode, decode } codec, so call sites are unchanged and the load-failure-tolerant contract is kept. Also fixes #293 (deprecated Buffer() warning + xxhash build-resolution error, both from the old lz4 package). DIAGNOSTIC (this commit): CI temporarily uses `npm install` instead of `npm ci` so the JFrog registry resolves lz4-napi + its @lz4-napi/* platform packages (the dev npm proxy doesn't mirror those scoped packages, so the lock couldn't be generated locally). If CI resolves them, I'll pin a proper lock and restore `npm ci`. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com>
1 parent 2c194ad commit 6593cb5

7 files changed

Lines changed: 103 additions & 296 deletions

File tree

‎.github/workflows/main.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
${{ runner.os }}-
5252
- name: Check code style
5353
run: |
54-
npm ci
54+
npm install --no-audit --no-fund
5555
npm run prettier
5656
npm run lint
5757
@@ -90,7 +90,7 @@ jobs:
9090
${{ runner.os }}-${{ matrix.node-version }}-
9191
- name: Run unit tests
9292
run: |
93-
npm ci
93+
npm install --no-audit --no-fund
9494
npm run test
9595
- run: tar -cvf ${{ env.NYC_REPORT_DIR }}.tar ${{ env.NYC_REPORT_DIR }}
9696
- name: Store coverage report
@@ -150,7 +150,7 @@ jobs:
150150
${{ runner.os }}-${{ matrix.node-version }}-
151151
- name: Run e2e tests
152152
run: |
153-
npm ci
153+
npm install --no-audit --no-fund
154154
NODE_OPTIONS="--max-old-space-size=4096" npm run e2e
155155
- run: tar -cvf ${{ env.NYC_REPORT_DIR }}.tar ${{ env.NYC_REPORT_DIR }}
156156
- name: Store coverage report

‎lib/utils/lz4.ts‎

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,56 @@
1-
import type LZ4Namespace from 'lz4';
2-
3-
type LZ4Module = typeof LZ4Namespace;
1+
// LZ4 frame codec, backed by `lz4-napi`.
2+
//
3+
// Historical note: this driver previously depended on the unmaintained
4+
// `lz4` package (last published 2021), a node-gyp native addon with no
5+
// prebuilt binaries. It failed to compile on Node 22+, so it was silently
6+
// skipped as an optional dependency and LZ4-compressed results could not be
7+
// decoded. `lz4-napi` is maintained and ships prebuilt binaries for all
8+
// supported platforms/Node versions, so no local toolchain build is needed.
9+
//
10+
// The Databricks server sends LZ4-compressed Arrow/CloudFetch payloads in
11+
// the LZ4 **frame** format (magic 0x184D2204). `lz4-napi`'s
12+
// `compressFrameSync`/`decompressFrameSync` are byte-compatible with the
13+
// frames the old `lz4` package produced/consumed — verified by round-trip
14+
// and cross-decoding both directions. We expose the same `{ encode, decode }`
15+
// surface the rest of the driver already calls, so call sites are unchanged.
16+
17+
type Lz4Napi = {
18+
compressFrameSync(data: Buffer): Buffer;
19+
decompressFrameSync(data: Buffer): Buffer;
20+
};
21+
22+
// Stable interface used across the driver (lib call sites do `LZ4()!.decode(buf)`).
23+
export interface LZ4Codec {
24+
encode(data: Buffer): Buffer;
25+
decode(data: Buffer): Buffer;
26+
}
427

5-
function tryLoadLZ4Module(): LZ4Module | undefined {
28+
function tryLoadLZ4Module(): LZ4Codec | undefined {
29+
let napi: Lz4Napi;
630
try {
7-
return require('lz4'); // eslint-disable-line global-require
31+
napi = require('lz4-napi'); // eslint-disable-line global-require
832
} catch (err) {
9-
if (!(err instanceof Error) || !('code' in err)) {
10-
// eslint-disable-next-line no-console
11-
console.warn('Unexpected error loading LZ4 module: Invalid error object', err);
12-
return undefined;
13-
}
14-
15-
if (err.code === 'MODULE_NOT_FOUND') {
33+
// lz4-napi ships prebuilds so this should not normally happen, but keep
34+
// the load-failure-tolerant contract: if it can't load, LZ4 support is
35+
// reported as unavailable rather than crashing the driver.
36+
if (err instanceof Error && 'code' in err && err.code === 'MODULE_NOT_FOUND') {
1637
return undefined;
1738
}
18-
19-
if (err.code === 'ERR_DLOPEN_FAILED') {
20-
// eslint-disable-next-line no-console
21-
console.warn('LZ4 native module failed to load: Architecture or version mismatch', err);
22-
return undefined;
23-
}
24-
25-
// If it's not a known error, return undefined
2639
// eslint-disable-next-line no-console
27-
console.warn('Unknown error loading LZ4 module: Unhandled error code', err);
40+
console.warn('LZ4 module (lz4-napi) failed to load; LZ4-compressed results unavailable', err);
2841
return undefined;
2942
}
43+
44+
return {
45+
encode: (data: Buffer): Buffer => Buffer.from(napi.compressFrameSync(data)),
46+
decode: (data: Buffer): Buffer => Buffer.from(napi.decompressFrameSync(data)),
47+
};
3048
}
3149

32-
// The null is already tried resolving that failed
33-
let resolvedModule: LZ4Module | null | undefined;
50+
// The null means we already tried resolving and it failed.
51+
let resolvedModule: LZ4Codec | null | undefined;
3452

35-
function getResolvedModule() {
53+
function getResolvedModule(): LZ4Codec | undefined {
3654
if (resolvedModule === undefined) {
3755
resolvedModule = tryLoadLZ4Module() ?? null;
3856
}

0 commit comments

Comments
 (0)