https://github.com/platformatic/node-caged
https://github.com/platformatic/node-caged
Last synced: 14 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/platformatic/node-caged
- Owner: platformatic
- Created: 2026-02-06T18:01:03.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-03-18T13:44:02.000Z (2 months ago)
- Last Synced: 2026-05-03T12:02:31.020Z (27 days ago)
- Language: Shell
- Size: 1.69 MB
- Stars: 104
- Watchers: 1
- Forks: 3
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Node.js Pointer Compression Experiments
[](https://github.com/platformatic/node-caged/actions/workflows/build-publish.yml)
[](https://hub.docker.com/r/platformatic/node-caged)
This repository contains experiments for building Node.js with V8 pointer compression enabled. Pointer compression is a V8 optimization that reduces memory usage by using 32-bit compressed pointers instead of full 64-bit pointers.
## Docker Images
Pre-built multi-architecture images (amd64/arm64) are available on DockerHub:
```bash
# Pull the latest image (Debian trixie, recommended)
docker pull platformatic/node-caged:latest
# Or use a specific variant
docker pull platformatic/node-caged:trixie # Full Debian
docker pull platformatic/node-caged:slim # Minimal Debian
docker pull platformatic/node-caged:alpine # Alpine Linux (experimental)
# Pin to a major Node.js version (recommended for most users)
docker pull platformatic/node-caged:26
docker pull platformatic/node-caged:26-slim
docker pull platformatic/node-caged:25
docker pull platformatic/node-caged:25-slim
# Pin to an exact Node.js version
docker pull platformatic/node-caged:25.6.1
docker pull platformatic/node-caged:25.6.1-slim
```
### Available Tags
Both Node.js 25.x and 26.x are published. Floating tags (`latest`, `trixie`, `slim`, `alpine`) track the highest published major (currently `26`).
| Tag | Description |
|-----|-------------|
| `latest`, `trixie` | Latest build on Debian trixie for the highest major (recommended) |
| `slim` | Minimal Debian trixie-slim runtime for the highest major |
| `alpine` | Alpine Linux with musl libc for the highest major (experimental) |
| `{major}` | Latest patch of major version on trixie (e.g., `25`, `26`) |
| `{major}-{variant}` | Latest patch of major version with variant (e.g., `25-slim`, `26-alpine`) |
| `{version}` | Exact Node.js version on trixie (e.g., `26.1.0`) |
| `{version}-{variant}` | Exact version and variant (e.g., `26.1.0-alpine`) |
### Variant Comparison
| Variant | Base Image | Size | Compatibility |
|---------|-----------|------|---------------|
| `trixie` | debian:trixie | ~250MB | Full glibc, best compatibility |
| `slim` | debian:trixie-slim | ~150MB | Minimal glibc runtime |
| `alpine` | alpine:3.21 | ~100MB | musl libc, experimental |
## Quick Start
Build the Docker image:
```bash
docker build --network=host -t node-pointer-compression .
```
Run the tests:
```bash
./run-tests.sh
```
## Benchmark Results
Memory comparison between standard Node.js 22 and pointer-compressed Node.js 25 (with `--expose-gc`):
| Data Structure | Standard Node 22 | Pointer Compressed | Savings |
|----------------|------------------|-------------------|---------|
| **Array of Objects** (1M items) | 40.47 MB (42.43 B/item) | 20.24 MB (21.22 B/item) | **50%** |
| **Nested Objects** (500K items) | 50.21 MB (105.29 B/item) | 24.64 MB (51.68 B/item) | **51%** |
| **Linked List** (500K items) | 19.08 MB (40.01 B/item) | 9.54 MB (20.01 B/item) | **50%** |
| **Array of Arrays** (500K items) | 38.76 MB (81.28 B/item) | 19.38 MB (40.64 B/item) | **50%** |
### Key Findings
- Pointer compression delivers consistent **~50% memory reduction** across all pointer-heavy data structures
- Bytes-per-item is almost exactly halved, matching the theoretical expectation (32-bit vs 64-bit pointers)
- Baseline memory usage is also lower (2.11 MB vs 3.74 MB)
### Tradeoffs
- **Heap limit**: 4GB per V8 isolate. Each worker thread has its own 4GB limit, so you can exceed 4GB total using multiple workers (e.g., main + 4 workers = 20GB max)
- **Compatibility**: Requires building Node.js from source with `--experimental-enable-pointer-compression`
### Native Addon Compatibility
**N-API addons work correctly** with pointer compression. Tested and verified:
| Addon | Type | Status |
|-------|------|--------|
| `bcrypt` | N-API | ✓ Works |
| `sharp` | N-API | ✓ Works |
| `@napi-rs/uuid` | Rust N-API | ✓ Works |
| `@node-rs/argon2` | Rust N-API | ✓ Works |
**Non-N-API native addons may crash.** Addons using the older V8 native addon API (like `better-sqlite3`) are not compatible with pointer compression and can segfault. Packages that rely on `nan` are especially affected. Always prefer N-API-based alternatives.
### Workarounds for Non-N-API Addons
If your dependency uses `nan` and does not provide binaries built for this runtime, you must force a local rebuild of its native addon.
If the package in `node_modules` still contains source files, try a normal rebuild:
```bash
cd node_modules/
pnpm install --ignore-scripts
pnpm run rebuild
```
Some packages on npm do not ship source files needed to rebuild. In these cases, you may need to fetch the source from the git repository, copy it back into `node_modules`, and run a manual rebuild.
#### Known affected package example
##### `@datadog/pprof`
This was tested against [@datadog/pprof](https://www.npmjs.com/package/@datadog/pprof) 5.13.5.
```bash
cd node_modules/@datadog/pprof
pnpm install --ignore-scripts
PPROF_VERSION=$(node -p "require('./package.json').version")
PPROF_REPO=$(node -p "require('./package.json').repository.url.replace('git+', '')")
git clone -q --depth=1 --branch=v$PPROF_VERSION $PPROF_REPO /tmp/pprof-nodejs > /dev/null 2>&1
mv /tmp/pprof-nodejs/{binding.gyp,bindings} .
rm -rf /tmp/pprof-nodejs
pnpm run rebuild
cd ../../..
```
## How It Works
The Dockerfiles build Node.js from the v25.x or v26.x branch (selected via the `NODE_VERSION` build arg) with the `--experimental-enable-pointer-compression` configure flag. This enables V8's pointer compression feature which uses 32-bit offsets from a base address instead of full 64-bit pointers.
## Test Scripts
- `tests/verify-pointer-compression.js` - Verifies pointer compression is enabled by checking heap limits
- `tests/memory-benchmark.js` - Benchmarks memory usage with pointer-heavy data structures
- `tests/worker-heap-limits.js` - Verifies each worker thread has its own 4GB heap limit
- `tests/napi-addon-test.js` - Tests N-API native addon compatibility (requires npm install)
## Building Locally
For local development, use the root Dockerfile:
```bash
# Build for local architecture
docker build --network=host -t node-pointer-compression .
# Run interactively
docker run -it node-pointer-compression
# Run a script
docker run -v $(pwd):/app node-pointer-compression node /app/your-script.js
```
To build a specific variant locally:
```bash
# Build trixie variant
docker build -f docker/trixie/Dockerfile -t node-pointer-compression:trixie .
# Build slim variant
docker build -f docker/slim/Dockerfile -t node-pointer-compression:slim .
# Build alpine variant
docker build -f docker/alpine/Dockerfile -t node-pointer-compression:alpine .
```
## CI/CD
The GitHub Actions workflow builds and publishes multi-architecture images:
- **Trigger**: Manual only (`workflow_dispatch`)
- **Major selection**: Choose `25`, `26`, or `all` (default) when dispatching
- **Version detection**: Automatically detects the latest release for each selected major
- **Duplicate check**: Skips a major if its current version already exists on DockerHub
- **Force rebuild**: Option to bypass the version check and rebuild
Images are built natively on both amd64 and arm64 runners for optimal build performance.