Skip to content

Publish Docker image variant with V8 pointer compression (-pc tags)#566

Draft
kriszyp wants to merge 2 commits into
mainfrom
kris/919-pointer-compression
Draft

Publish Docker image variant with V8 pointer compression (-pc tags)#566
kriszyp wants to merge 2 commits into
mainfrom
kris/919-pointer-compression

Conversation

@kriszyp

@kriszyp kriszyp commented Jul 12, 2026

Copy link
Copy Markdown
Member

Summary

Adds a pointer-compression variant of the Docker image — harperfast/harper-pro:<version>-pc — whose runtime Node is compiled with --experimental-enable-pointer-compression: ~40% lower JS heap memory (43% measured on a 2M-object graph: 376→215 MB heapUsed; idle container RSS 2.07→1.7 GiB) with a 4 GB heap cap per isolate (per worker thread — Node uses per-isolate cages, and Harper's automatic worker heap sizing never exceeds 4 GB). For heap-bound edge deployments this is roughly a 40% density win. Closes HarperFast/harper#919.

Pieces:

  • build-tools/node-pointer-compression.Dockerfile + publish-node-pc-image.yaml (dispatch-only): builds harperfast/node-pointer-compression:<major> — the standard node image with the node binary rebuilt from source, plus uWebSockets.js and @datadog/pprof binaries rebuilt against the pointer-compression V8 ABI. Dispatch-only so the 1–2 h Node compile never slows a release; re-run when the Node version / uWS pin / pprof version bumps.
  • Dockerfile: new RUN_IMAGE build arg; on a pointer-compression base, swaps in the rebuilt uWS + pprof binaries (with .pointer-compression-build markers), drops version-specific direct-V8 addon variants (node.abi*.node) in favor of their Node-API builds, and fails the build if any raw-V8-ABI binary that could load on the runtime remains (build-tools/check-native-abi-pointer-compression.js).
  • publish-docker.yaml: explicit build matrix with pc legs (default Node version only) and a merge-pc job pushing -pc tags, with a release-time assertion that pointer compression is on and the rebuilt uWS constructs in the pushed image.
  • analytics/profile.ts: on a pointer-compression runtime without the pprof marker, profiling is skipped with a warning instead of crashing.

Why the uWS/pprof rebuilds are necessary

Both link the raw V8 C++ ABI (not Node-API); their upstream prebuilds load cleanly on a pointer-compression node and then segfault on first use — both verified live (pprof crashed PID 1 at boot +1s; uWS.App() SIGSEGVs). The pointer-compression ABI is set by header-level defines, so rebuilding with matching defines fixes them: uWS follows its own build.c recipe with the PC defines added; pprof is built from the DataDog/pprof-nodejs repo (its npm package ships no C++ sources) simply by running node-gyp under the pointer-compression node — node-gyp derives its defines from the running node's process.config, which is also why in-image source-built addons (segfault-handler) are automatically correct. All other production native modules (rocksdb-js, lmdb, msgpackr/cbor-extract, argon2, bufferutil, utf-8-validate) are Node-API and unaffected.

Validated end-to-end (local amd64 builds)

Booted the pc image with HARPER_UWS_HTTP=true and a 15 s profiling capture period: uWS serves (uWebSockets: 20 response header), profiler captures repeatedly, insert/read green, no segfaults; negative test (marker removed) degrades to a per-worker warning with the instance healthy.

Attention / open items

  • Rollout ordering: after merge, dispatch Publish Node Pointer-Compression Base Image once before the next release; until then the pc build legs would fail (standard legs unaffected, but the Slack success message gates on both merge jobs).
  • Version pins to keep in sync manually (documented in-file): PPROF_VERSION (package-lock) and UWS_SOURCE_COMMIT (the source_commit file inside the pinned uWS binaries tarball).
  • The pc variant intentionally trades the direct-V8 fast paths of cbor-extract (node.abi*.node) for its Node-API build.
  • Base image is currently node:24 (bookworm); once fix: base Docker image on Debian trixie for uWebSockets.js glibc 2.38 #564 (trixie) lands it should follow — the pc variant is glibc-self-consistent either way since its V8-ABI addons are compiled in-image.
  • Companion PR Refuse uWS backend on pointer-compression Node builds unless rebuilt for that ABI harper#1765 adds the core-side uWS guard honoring the marker (not required for this image to work — the rebuilt addon simply works — it protects standalone/npm installs on pointer-compression nodes).
  • Pre-existing, unrelated: tsc reports Numeric-vs-number errors in analytics/profile.ts sample processing (pprof-format types); the package build does not gate on them.

🤖 Generated with Claude Code (Fable 5)

…r#919)

Adds a -pc image variant (harperfast/harper-pro:<version>-pc) whose
runtime Node binary is compiled with V8 pointer compression: ~40% lower
JS heap memory (43% measured on a 2M-object graph), 4 GB heap cap per
isolate (per worker thread — Node uses per-isolate cages).

The base image (harperfast/node-pointer-compression, built by the new
dispatch-only publish-node-pc-image workflow so releases are not slowed
by the 1-2h Node compile) also carries uWebSockets.js and @datadog/pprof
binaries rebuilt against the pointer-compression V8 ABI — both link the
raw V8 ABI (not Node-API) and their upstream prebuilds segfault on a
pointer-compression node (verified live). The main Dockerfile swaps them
in and marks the packages with .pointer-compression-build; version-
specific direct-V8 addon variants (node.abi*.node) are dropped in favor
of their Node-API builds; and a build-time ABI scan
(check-native-abi-pointer-compression.js) fails the image if any raw-V8-
ABI binary that could load on the runtime remains.

analytics/profile.ts skips profiling with a warning (instead of
crashing) on pointer-compression runtimes where pprof lacks the marker.

All other production native modules (rocksdb-js, lmdb, msgpackr/cbor
extract, argon2, bufferutil, utf-8-validate) are Node-API and unaffected;
segfault-handler is source-built in-image and picks up the correct ABI
from the running node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for a Node.js runtime variant compiled with V8 pointer compression. It adds a dedicated Dockerfile to build the custom Node.js image, rebuilds incompatible native modules (uWebSockets.js and @datadog/pprof) against the pointer-compression ABI, and implements an ABI guard script to verify native module compatibility during the build. Additionally, the profiling logic is updated to safely disable profiling if pointer compression is active but the compatible @datadog/pprof binary is missing. Feedback on the changes highlights two key issues: first, the ABI guard script could silently bypass checks if the nm utility is missing from the system; second, using __filename in analytics/profile.ts will throw a ReferenceError in ES module environments, causing profiling to be silently disabled. Both comments provide actionable code suggestions to resolve these issues.

Comment thread build-tools/check-native-abi-pointer-compression.js
Comment thread analytics/profile.ts
@claude

claude Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

Per-file nm errors are tolerated (non-ELF files), so an absent nm binary
would have silently passed the scan without checking anything.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docker image with V8 pointer compression enabled

1 participant