feat(native) #5544 : experimental GraalVM native-image build of the ArcadeDB server - #5323
Conversation
|
Tick the box to add this pull request to the merge queue (same as
|
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Documentation | 1 minor |
| ErrorProne | 1 high |
| CodeStyle | 2 minor |
🟢 Metrics 7 complexity
Metric Results Complexity 7
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Code Review
This pull request introduces an experimental GraalVM native-image build for ArcadeDB, adding a new native Maven module, Dockerfiles for static and mostly-static builds, design/plan documentation, and comprehensive smoke-testing and tracing scripts. The feedback highlights a potential runtime issue in the scratch-based Dockerfile due to a missing /tmp directory, and identifies several instances in the exercise.sh test script where set -e will cause silent exits on command failures, bypassing the intended descriptive error messages.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| COPY config /home/arcadedb/config | ||
| COPY --chmod=0755 arcadedb /home/arcadedb/bin/arcadedb | ||
|
|
||
| WORKDIR /home/arcadedb |
There was a problem hiding this comment.
Since this image is built FROM scratch, it does not contain a /tmp directory by default. Many Java libraries (such as Lucene, Netty, or ArcadeDB itself) create temporary files or unpack native libraries into the system's temporary directory (java.io.tmpdir, which defaults to /tmp on Linux). The absence of /tmp can lead to runtime IOException failures when these libraries attempt to write temporary files.\n\nYou can easily create /tmp in a scratch image by using the WORKDIR instruction to target /tmp before setting the final working directory.
WORKDIR /tmp\nWORKDIR /home/arcadedb
There was a problem hiding this comment.
Fixed in fa6d755. Added WORKDIR /tmp before the app WORKDIR to materialize /tmp (java.io.tmpdir defaults to it). Created root-owned 0755, writable by the default root runtime user; a comment notes a non-root --user run should bind-mount a writable /tmp. Only the scratch/amd64 image is affected - the arm64 distroless base already ships /tmp.
| req() { curl -fsS -u "$DB_USER:$PASS" -H 'Content-Type: application/json' "$@"; } | ||
|
|
||
| echo "[exercise] Studio index" | ||
| OUT="$(req "http://$HOST:$HTTP/")" |
There was a problem hiding this comment.
Because set -e is enabled at the top of the script, any command that returns a non-zero exit status will cause the shell to exit immediately. During the assignment OUT="$(req ...)", if the request fails (e.g., connection refused or HTTP error), the script will exit immediately and silently. This bypasses the subsequent grep check and the custom error message echo "[exercise] FAIL: Studio index".\n\nAppending || true to the assignment allows the script to continue to the grep check, which will then fail and print your descriptive error message.
| OUT="$(req "http://$HOST:$HTTP/")" | |
| OUT=\"$(req \"http://$HOST:$HTTP/\")\" || true |
There was a problem hiding this comment.
Fixed in fa6d755. Appended || true to the five OUT=$(req ...) / psql assignments so a failed request reaches the custom [exercise] FAIL: ... message instead of a bare set -e exit. Pass/fail is unchanged: an empty OUT still fails the subsequent grep, so the assertion still fails loudly.
| -d '{"language":"sql","command":"CREATE DOCUMENT TYPE T"}' >/dev/null | ||
| req -X POST "http://$HOST:$HTTP/api/v1/command/$DB" \ | ||
| -d '{"language":"sql","command":"INSERT INTO T SET n = 42"}' >/dev/null | ||
| OUT="$(req -X POST "http://$HOST:$HTTP/api/v1/query/$DB" -d '{"language":"sql","command":"SELECT n FROM T"}')" |
There was a problem hiding this comment.
Similar to the issue on line 78, if the SQL query fails, the script will exit immediately and silently due to set -e, bypassing the custom error message. Appending || true ensures the script proceeds to the grep check and prints the descriptive error message.
| OUT="$(req -X POST "http://$HOST:$HTTP/api/v1/query/$DB" -d '{"language":"sql","command":"SELECT n FROM T"}')" | |
| OUT=\"$(req -X POST \"http://$HOST:$HTTP/api/v1/query/$DB\" -d '{\"language\":\"sql\",\"command\":\"SELECT n FROM T\"}')\" || true |
There was a problem hiding this comment.
Fixed in fa6d755 (same || true change applied to this assignment).
| OUT="$(req -X POST "http://$HOST:$HTTP/api/v1/command/$DB" \ | ||
| -d '{"language":"cypher","command":"CREATE (a:Person {name:\"Ada\"}) RETURN a.name AS n"}')" |
There was a problem hiding this comment.
Similar to the issue on line 78, if the Cypher command fails, the script will exit immediately and silently due to set -e, bypassing the custom error message. Appending || true ensures the script proceeds to the grep check and prints the descriptive error message.
| OUT="$(req -X POST "http://$HOST:$HTTP/api/v1/command/$DB" \ | |
| -d '{"language":"cypher","command":"CREATE (a:Person {name:\"Ada\"}) RETURN a.name AS n"}')" | |
| OUT=\"$(req -X POST \"http://$HOST:$HTTP/api/v1/command/$DB\" \n -d '{\"language\":\"cypher\",\"command\":\"CREATE (a:Person {name:\"Ada\"}) RETURN a.name AS n\"}')\" || true |
There was a problem hiding this comment.
Fixed in fa6d755 (same || true change applied to this assignment).
| OUT="$(req -X POST "http://$HOST:$HTTP/api/v1/command/$DB" \ | ||
| -d '{"language":"js","command":"40 + 2"}')" |
There was a problem hiding this comment.
Similar to the issue on line 78, if the JS command fails, the script will exit immediately and silently due to set -e, bypassing the custom error message. Appending || true ensures the script proceeds to the grep check and prints the descriptive error message.
| OUT="$(req -X POST "http://$HOST:$HTTP/api/v1/command/$DB" \ | |
| -d '{"language":"js","command":"40 + 2"}')" | |
| OUT=\"$(req -X POST \"http://$HOST:$HTTP/api/v1/command/$DB\" \n -d '{\"language\":\"js\",\"command\":\"40 + 2\"}')\" || true |
There was a problem hiding this comment.
Fixed in fa6d755 (same || true change applied to this assignment).
|
|
||
| echo "[exercise] Postgres-wire round-trip" | ||
| if command -v psql >/dev/null 2>&1; then | ||
| OUT="$(PGPASSWORD="$PASS" psql -h "$HOST" -p "$PG" -U "$DB_USER" -d "$DB" -tAc 'SELECT 1')" |
There was a problem hiding this comment.
Similar to the issue on line 78, if psql fails to connect or execute, the script will exit immediately and silently due to set -e, bypassing the custom error message. Appending || true ensures the script proceeds to the grep check and prints the descriptive error message.
| OUT="$(PGPASSWORD="$PASS" psql -h "$HOST" -p "$PG" -U "$DB_USER" -d "$DB" -tAc 'SELECT 1')" | |
| OUT=\"$(PGPASSWORD=\"$PASS\" psql -h \"$HOST\" -p \"$PG\" -U \"$DB_USER\" -d \"$DB\" -tAc 'SELECT 1')\" || true |
There was a problem hiding this comment.
Fixed in fa6d755 (same || true change applied to this assignment).
Review: experimental GraalVM native-image buildNice, well-scoped, and genuinely impressive engineering. The design is right: the module is kept out of the default reactor ( Correctness / potential issues
Security
Performance
Test coverage
Minor / style
Overall: solid, honest, and appropriately fenced off from the default build. My only "please consider before merge" item is #7 (make the Linux wire-protocol checks assert rather than warn) so CI actually defends the claim; the rest are good documented follow-ups. Reviewed against the repo's CLAUDE.md conventions. |
…se.sh FAIL diagnostics under set -e Addresses gemini-code-assist review on PR #5323: - Dockerfile.native.scratch: scratch has no /tmp; java.io.tmpdir defaults to /tmp and Lucene/Netty may write there. Create it via the WORKDIR trick. - exercise.sh: append '|| true' to the five OUT=$(req/psql ...) assignments so a failed request reaches the custom [exercise] FAIL message instead of a bare set -e exit. Pass/fail semantics unchanged (empty OUT still fails the grep).
📜 License Compliance Check✅ License check passed. See artifacts for full report. License Summary (first 50 lines) |
Code Review: experimental GraalVM native-image build (PR #5323)Overall this is a genuinely impressive, well-isolated addition. The native module is opt-in ( A few things worth addressing before merge: 🔴 Likely bug: release asset upload will fail on the required Linux legsIn gh release upload "${{ github.event.release.tag_name }}" \
native/target/arcadedb-*.tar.gz native/target/arcadedb-*.zip native/target/*.sha256 \
--clobberOn the Linux and macOS legs there is no Suggest globbing defensively, e.g. collect existing files first: shopt -s nullglob
assets=(native/target/arcadedb-*.tar.gz native/target/arcadedb-*.zip native/target/*.sha256)
gh release upload "${{ github.event.release.tag_name }}" "${assets[@]}" --clobberNote the release-upload path only runs on a real 🟡 ATTRIBUTIONS.md not updated for the new dependenciesCLAUDE.md requires: "When adding a dependency, you MUST update ATTRIBUTIONS.md". This PR pulls in several new artifacts ( 🟡 Security caveat is real (already disclosed, flagging for visibility)
🟢 Minor / nits
Test coverageThe smoke/exercise scripts are a solid acceptance gate and hard-assert HTTP/Studio/SQL/Cypher/JS while best-effort-exercising every wire protocol. The Nice work overall - the headline item to fix is the release-upload glob. 🤖 Generated with Claude Code |
📜 License Compliance Check✅ License check passed. See artifacts for full report. License Summary (first 50 lines) |
Addresses 5 review items on the native-image work: - exercise.sh: add WIRE_STRICT=1 opt-in mode that turns the Postgres/ Redis/Bolt/Mongo/gRPC best-effort WARN-skips into hard FAILs on a missing client tool, unreachable port, or wrong response. Wired into native-image.yml's two Linux CI legs (which enable every wire plugin), so a native regression that breaks a wire protocol now fails the build instead of staying green. Installs postgresql-client and grpcurl on both Linux legs (previously postgresql-client was amd64- only and grpcurl wasn't installed at all). - native-image.yml: make the "Locate native binary" step select by executable bit (.exe suffix on Windows) instead of excluding a fixed extension list, which could be tripped by future native-image report artifacts. - Dockerfile.native.scratch / Dockerfile.native.distroless: add a tiny busybox builder stage that pre-creates the databases/backups/ replication/log VOLUME dirs owned by the runtime UID (0 for scratch, 65532 for distroless), so a non-root `docker run` can actually persist to them. - Dockerfile.native.distroless: pin the distroless base image by digest, matching the JVM Dockerfile's supply-chain posture. - engine/pom.xml: document that graalvm.version must move in lockstep with native/pom.xml's native.graalvm.version and native-image.yml's setup-graalvm java-version.
|
Addressed in Correctness
Security Performance Test coverage Minor - confirmed no genuinely new dependency coordinate slipped in: the graal artifacts are transitive via |
📜 License Compliance Check✅ License check passed. See artifacts for full report. License Summary (first 50 lines) |
Review: experimental GraalVM native-image buildOverall this is a high-quality, well-scoped, and exceptionally well-documented PR. The core design decision is right: the Feedback below, ordered by importance. None are blocking for an experimental, opt-in add-on, but a few should be tracked before this is ever promoted toward production use. Security
Potential gaps
Minor
Test coverageReasonable for build infrastructure: the smoke/exercise scripts are the acceptance gate, they hard-assert HTTP/Studio/SQL/Cypher/JS, and Nice work reviving and completing this. The honesty about limitations in both the PR description and 🤖 Generated with Claude Code |
… JS multi-tenant caveat Addresses claude[bot] re-review on PR #5323: - docker job smoke now runs exercise.sh (HTTP/Studio/SQL/Cypher/JS hard-asserts) against the running container, not just /api/v1/ready, so image-packaging regressions (missing /tmp, bad config copy, volume perms) are caught. - docs/native-image.md: state explicitly the native image is not production- hardened for multi-tenant/untrusted JS, with opt-in/version-pin hardening options for future promotion.
|
Re-review addressed in Container smoke depth (potential gaps) - Fixed. The JS not production-hardened (security) - Added an explicit statement in Minor items
CI note (not this PR): the failing "Meterian client scan" is unrelated - its log shows |
📜 License Compliance Check✅ License check passed. See artifacts for full report. License Summary (first 50 lines) |
Review: experimental GraalVM native-image buildExcellent work. This is a carefully scoped, well-documented, opt-in add-on that keeps the JVM reactor entirely untouched ( Potential issue:
|
…, drop stale comment Addresses claude[bot] re-review on PR #5323: - Dockerfile.native.scratch: create /tmp as sticky world-writable (1777) from the builder stage instead of the WORKDIR trick (root-owned 0755), so the documented 'docker run --user 1000:1000' works without a /tmp bind mount. - native-image.yml: default permissions to contents:read; scope contents:write to the build job (only 'Attach to release' needs it). - smoke.sh: drop the stale ARCADEDB_HOME header comment (the script never reads it).
|
Addressed in
No automated native-build CI trigger - This one I'm deliberately leaving as-is and flagging for the maintainer rather than changing unilaterally: the add-on was explicitly scoped during design as experimental and not PR-gating, precisely to avoid slow native builds on every PR. Your narrower suggestion - a path-filtered Minor
The |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Documentation | 1 minor |
| ErrorProne | 1 high |
| CodeStyle | 2 minor |
🟢 Metrics 7 complexity
Metric Results Complexity 7
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Review: experimental GraalVM native-image build (PR #5323)Overall this is a high-quality, well-isolated addition. The native module stays out of the default reactor ( A few things worth addressing or at least discussing: Security
Repo hygiene
Robustness / minor
TestingThe shell smoke gate is a reasonable choice here (a native binary can't be exercised from the normal JUnit reactor), and it hard-asserts HTTP/Studio/SQL/Cypher/JS with wire protocols under Nice work overall. The security defaults (JS blocklist relaxation, root-by-default scratch image) are the two items I'd most want resolved or explicitly signed off before this is used for anything beyond experimentation. Reviewed by Claude Code. |
📜 License Compliance Check✅ License check passed. See artifacts for full report. License Summary (first 50 lines) |
|
Thanks for the thorough pass. Status on each: Security (the two "resolve or sign off" items) - both are deliberate security-default choices that belong to the maintainer, so flagging for explicit sign-off rather than flipping them here:
Repo hygiene
Robustness / minor
Net: the two security defaults are yours to sign off (recommend the scratch |
…se.sh FAIL diagnostics under set -e Addresses gemini-code-assist review on PR #5323: - Dockerfile.native.scratch: scratch has no /tmp; java.io.tmpdir defaults to /tmp and Lucene/Netty may write there. Create it via the WORKDIR trick. - exercise.sh: append '|| true' to the five OUT=$(req/psql ...) assignments so a failed request reaches the custom [exercise] FAIL message instead of a bare set -e exit. Pass/fail semantics unchanged (empty OUT still fails the grep).
Addresses 5 review items on the native-image work: - exercise.sh: add WIRE_STRICT=1 opt-in mode that turns the Postgres/ Redis/Bolt/Mongo/gRPC best-effort WARN-skips into hard FAILs on a missing client tool, unreachable port, or wrong response. Wired into native-image.yml's two Linux CI legs (which enable every wire plugin), so a native regression that breaks a wire protocol now fails the build instead of staying green. Installs postgresql-client and grpcurl on both Linux legs (previously postgresql-client was amd64- only and grpcurl wasn't installed at all). - native-image.yml: make the "Locate native binary" step select by executable bit (.exe suffix on Windows) instead of excluding a fixed extension list, which could be tripped by future native-image report artifacts. - Dockerfile.native.scratch / Dockerfile.native.distroless: add a tiny busybox builder stage that pre-creates the databases/backups/ replication/log VOLUME dirs owned by the runtime UID (0 for scratch, 65532 for distroless), so a non-root `docker run` can actually persist to them. - Dockerfile.native.distroless: pin the distroless base image by digest, matching the JVM Dockerfile's supply-chain posture. - engine/pom.xml: document that graalvm.version must move in lockstep with native/pom.xml's native.graalvm.version and native-image.yml's setup-graalvm java-version.
… JS multi-tenant caveat Addresses claude[bot] re-review on PR #5323: - docker job smoke now runs exercise.sh (HTTP/Studio/SQL/Cypher/JS hard-asserts) against the running container, not just /api/v1/ready, so image-packaging regressions (missing /tmp, bad config copy, volume perms) are caught. - docs/native-image.md: state explicitly the native image is not production- hardened for multi-tenant/untrusted JS, with opt-in/version-pin hardening options for future promotion.
ad86867 to
0846f82
Compare
…, drop stale comment Addresses claude[bot] re-review on PR #5323: - Dockerfile.native.scratch: create /tmp as sticky world-writable (1777) from the builder stage instead of the WORKDIR trick (root-owned 0755), so the documented 'docker run --user 1000:1000' works without a /tmp bind mount. - native-image.yml: default permissions to contents:read; scope contents:write to the build job (only 'Attach to release' needs it). - smoke.sh: drop the stale ARCADEDB_HOME header comment (the script never reads it).
📜 License Compliance Check✅ License check passed. See artifacts for full report. License Summary (first 50 lines) |
…adata repo Enables the GraalVM reachability-metadata repository in native-maven-plugin so Task 4's native build gets well-known library configs (Netty, Lucene, gRPC/protobuf, graphql-java) from the repo instead of hand-written config, and adds trace.sh, which launches the assembled JVM distribution under -agentlib:native-image-agent with every wire plugin enabled (Postgres/Redis/MongoDB/Bolt/gRPC) and records what the server actually touches at runtime. Refactors the HTTP/SQL/Cypher/Postgres assertions out of smoke.sh into exercise.sh so the exact same checks run against both smoke.sh's own server and trace.sh's instrumented one; exercise.sh also opportunistically drives a Redis PING, a Bolt handshake, a hand-rolled Mongo OP_MSG hello (via mongo_hello.py, no bson/pymongo dependency available), and a grpcurl reflection list, each skipped with a WARN if its port never opens.
Native-image build of the ArcadeDB server (GraalVM CE 25, macos/arm64) now compiles and boots. smoke.sh passes: HTTP/Studio, SQL and Cypher round-trips, and all wire protocols served natively (Redis PING->PONG, Bolt v5.4 handshake, MongoDB hello, gRPC reflection). Postgres plugin loads (wire assertion needs psql, verified in CI). GraalJS is embedded (Truffle runtime active). Blockers resolved: - wildfly-common Substitutions shadow: GraalVM 25 renamed the substitution target org.graalvm.compiler.* -> jdk.graal.compiler.*, breaking wildfly's bundled @TargetClass GraalDirectives/Branch hints; a stripped shadow class ahead on the classpath removes them (pure branch-probability hints, no behavioral effect). - Undertow/XNIO HostName -> --initialize-at-run-time. - slf4j API/helpers/spi/event/jul -> --initialize-at-build-time. - Netty -> --initialize-at-run-time=io.netty. - JLine -> dumb terminal at runtime. - JVector -> --add-modules=jdk.incubator.vector. - GraalJS Truffle blocklist relaxed (-H:-TruffleCheckBlockListMethods) so six Atomics/TypedArray.set builtins reaching MethodHandle.linkToStatic do not abort the build; those runtime-compiled JS paths are unverified (Task 5). Known follow-up: binary is 732MB due to -H:IncludeResources=.* embedding all classpath resources; tighten to specific patterns to shrink.
…chability risk Task 4 already embeds and runs GraalJS in the native binary. This makes that permanent: exercise.sh gains a hard JS assertion (same style as the SQL/Cypher checks, not a WARN-skip) verified against both the native binary and the JVM server.sh build. docs/native-image.md documents the outcome plus the investigation into the reviewer's Atomics/SharedArrayBuffer/TypedArray.set forward-flag: those builtins are reachable through ArcadeDB's polyglot Context sandboxing (which restricts host/IO/thread/process access, not the JS language surface itself), confirmed by direct functional tests against the running native binary, so the Truffle blocklist relaxation's residual risk is real and stays documented as an accepted, non-blocking risk.
Builds the GraalVM CE 25 native image on each OS/arch runner (native-image cannot cross-compile): linux/amd64 + linux/arm64 (required), macos + windows (best-effort via continue-on-error). Smoke-tests each binary, publishes compressed artifacts + SHA256, attaches to releases. Plumbs a native.static flag + musl-tools install for the Linux static build (made functional in the static profile in the next task). macos-13 was retired by GitHub; macos/amd64 uses macos-15-intel (a paid larger runner - entitlement to be confirmed by maintainer) and macos/arm64 macos-15.
…solver Fills in Task 6's native-static no-op profile: --static --libc=musl appended to the native-maven-plugin buildArgs via combine.children="append" (Maven's default profile/base plugin merge for repeated <buildArg> elements is positional, not a union, so an unmarked <buildArgs> would silently overwrite two base flags instead of adding two new ones). native-image.yml's musl-tools install step becomes a full musl+zlib toolchain setup: musl-dev (not just musl-tools) supplies the arch-prefixed compiler native-image's toolchain probe looks for by name, and a musl-linked static zlib is built from source with --includedir/--libdir pointed at the musl triplet's own include/lib dirs - musl-gcc's specs file passes -nostdinc/ -nostdlib and does not fall back to generic /usr/include or /usr/lib. Confirmed (via the actual graalvm-community-jdk-25.0.2 release tarballs) that linux-aarch64 ships lib/static/linux-aarch64/glibc but not the musl variant, so the arm64 static leg will fail at the native-image link stage until Oracle ships aarch64 musl static JDK libs (oracle/graal#4645) - documented in docs/native-image.md and inline in the workflow rather than silently masked. Also documents that ArcadeDB's native image never touches Netty's async DNS resolver (io.netty.resolver is absent from the whole dependency graph wired into native/pom.xml, and no real preferJdkResolver-style Netty system property exists to "force" a JDK resolver anyway), so no runtime DNS flag is needed for Task 8's Docker ENTRYPOINT.
…glibc arm64) + multi-arch manifest GraalVM CE cannot build a fully-static musl binary for linux/arm64 (no aarch64 musl JDK libs - oracle/graal#4645), so the two Linux native-image targets now use different build modes: linux/amd64 stays fully-static musl (FROM scratch), linux/arm64 switches to mostly-static glibc via a new native-mostly-static Maven profile (-H:+StaticExecutableWithDynamicLibC, FROM distroless/base glibc). Adds native-image.yml's docker and manifest jobs to build, smoke-test and publish per-arch images and stitch them into an arcadedata/arcadedb:<ver> -native manifest, gated so only a published release pushes to Docker Hub.
CI resolved the newest CE 25.x builder (java-version: "25"), which will skew against native/pom.xml's exact native.graalvm.version=25.0.2 Truffle pin as soon as a newer CE 25.x patch ships, breaking the native build at release time (NoSuchMethodError: OptimizedTruffleRuntime.getLoopNodeFactory()). Pin the builder to the same exact patch so it stays coupled with the pom.
…se.sh FAIL diagnostics under set -e Addresses gemini-code-assist review on PR #5323: - Dockerfile.native.scratch: scratch has no /tmp; java.io.tmpdir defaults to /tmp and Lucene/Netty may write there. Create it via the WORKDIR trick. - exercise.sh: append '|| true' to the five OUT=$(req/psql ...) assignments so a failed request reaches the custom [exercise] FAIL message instead of a bare set -e exit. Pass/fail semantics unchanged (empty OUT still fails the grep).
Addresses 5 review items on the native-image work: - exercise.sh: add WIRE_STRICT=1 opt-in mode that turns the Postgres/ Redis/Bolt/Mongo/gRPC best-effort WARN-skips into hard FAILs on a missing client tool, unreachable port, or wrong response. Wired into native-image.yml's two Linux CI legs (which enable every wire plugin), so a native regression that breaks a wire protocol now fails the build instead of staying green. Installs postgresql-client and grpcurl on both Linux legs (previously postgresql-client was amd64- only and grpcurl wasn't installed at all). - native-image.yml: make the "Locate native binary" step select by executable bit (.exe suffix on Windows) instead of excluding a fixed extension list, which could be tripped by future native-image report artifacts. - Dockerfile.native.scratch / Dockerfile.native.distroless: add a tiny busybox builder stage that pre-creates the databases/backups/ replication/log VOLUME dirs owned by the runtime UID (0 for scratch, 65532 for distroless), so a non-root `docker run` can actually persist to them. - Dockerfile.native.distroless: pin the distroless base image by digest, matching the JVM Dockerfile's supply-chain posture. - engine/pom.xml: document that graalvm.version must move in lockstep with native/pom.xml's native.graalvm.version and native-image.yml's setup-graalvm java-version.
… JS multi-tenant caveat Addresses claude[bot] re-review on PR #5323: - docker job smoke now runs exercise.sh (HTTP/Studio/SQL/Cypher/JS hard-asserts) against the running container, not just /api/v1/ready, so image-packaging regressions (missing /tmp, bad config copy, volume perms) are caught. - docs/native-image.md: state explicitly the native image is not production- hardened for multi-tenant/untrusted JS, with opt-in/version-pin hardening options for future promotion.
…, drop stale comment Addresses claude[bot] re-review on PR #5323: - Dockerfile.native.scratch: create /tmp as sticky world-writable (1777) from the builder stage instead of the WORKDIR trick (root-owned 0755), so the documented 'docker run --user 1000:1000' works without a /tmp bind mount. - native-image.yml: default permissions to contents:read; scope contents:write to the build job (only 'Attach to release' needs it). - smoke.sh: drop the stale ARCADEDB_HOME header comment (the script never reads it).
macos-15-intel is a paid Larger Runner not enabled here; it fails at job setup, so the macos/amd64 native-image leg was a permanently-red dead leg. Remove it - the matrix is now 4 targets (linux amd64+arm64 required; macos/arm64 + windows best-effort). Docs updated to match.
…native-image) The free macos-15 (Apple Silicon, ~7GB RAM) cannot build this native image without thrashing swap for 40+ min - native-image wants ~80% of RAM and the build image heap is ~630MB. Matrix is now linux/amd64 + linux/arm64 (required) + windows/amd64 (best-effort). macOS arm64 is a fast local build (mvn -Pnative); documented in docs/native-image.md. Also unblocks the docker jobs, which no longer wait on a slow best-effort macOS leg.
The container ENTRYPOINT sets -Djava.util.logging.config.file=arcadedb-log.properties, which names its handlers/formatters by string (java.util.logging.FileHandler + ConsoleHandler, com.arcadedb.log.LogFormatter, com.arcadedb.utility.AnsiLogFormatter). JUL loads them reflectively, but native-image's static analysis can't see a class referenced only as a config string, so FileHandler was absent from the image and threw ClassNotFoundException at startup (non-fatal - logging fell back to console). smoke.sh never set the JUL config file, so it wasn't caught until the container run. Register the four classes for reflection (no-arg constructor). Verified: the CNFE is gone and arcadedb.log is written to the logs directory.
exercise.sh can pass through a non-fatal boot error (e.g. a reflectively-loaded JUL log handler missing from the native image logs a ClassNotFoundException but only degrades logging). Scan the container startup log and fail on ClassNotFoundException / log-handler load failures so a missing-metadata regression cannot hide behind a green functional smoke.
… tuning Re-add macos/arm64 (best-effort) to test whether the free ~7GB macos-15 runner can build the image with memory constraints: new native-lowmem pom profile (-Dnative.lowmem=true) caps the builder heap (-J-Xmx5g), lowers compiler-thread parallelism to 2, and uses quick-build mode (-Ob); a macOS-only pre-build step runs 'mdutil -i off' + 'purge' to reclaim RAM. required:false so a memory failure never reddens the run.
…ve-image tuning" This reverts commit 2ad3ee5.
Build the macOS arm64 native binary on macos-15-xlarge (a paid Apple-Silicon Larger Runner with the RAM headroom the free macos-15 lacks - the free runner OOM-thrashes this GraalJS-embedded image). The matrix is now computed in a setup job so the paid leg is added only on a published release or a manual dispatch that ticks the new include_macos input; an ordinary dispatch never allocates the paid runner. The leg is best-effort (continue-on-error), uploads a release artifact only, and never feeds the Docker images. Also make the 'Locate native binary' step's array population portable (while-read instead of mapfile), since GitHub's macOS runners can resolve 'shell: bash' to the system bash 3.2, which lacks mapfile.
9f6f610 to
0229ec8
Compare
📜 License Compliance Check✅ License check passed. See artifacts for full report. License Summary (first 50 lines) |
Review: experimental GraalVM native-image buildThanks for this - the engineering quality here is high and the inline documentation is genuinely excellent. The opt-in isolation (out of the default reactor, 1. GraalVM version references are divergent and partly stale (maintainability)There are now several different GraalVM versions in play:
The new comment in 2. amd64 and arm64 images default to different users (security consistency)
3. PR description vs. actual workflow (stale description)
Not code issues, but worth aligning so a release manager reading the PR is not misled. 4. JS blocklist relaxation (accepted risk - flag for release notes)
5. IncludeResources=.* -> ~732MB binary (documented follow-up)Already flagged. Beyond size, the blanket Minor / nits
Overall a solid, well-isolated experimental add-on that cannot regress the default build. The version-reference cleanup (#1) and the amd64 root-by-default decision (#2) are the two I would most want resolved before a release relies on it. Nice work. |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Documentation | 1 minor |
| ErrorProne | 1 high |
| CodeStyle | 2 minor |
🟢 Metrics 7 complexity
Metric Results Complexity 7
🟢 Coverage ∅ diff coverage · -7.04% coverage variation
Metric Results Coverage variation ✅ -7.04% coverage variation Diff coverage ✅ ∅ diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (89e69f0) 146725 109537 74.65% Head commit (0229ec8) 178717 (+31992) 120846 (+11309) 67.62% (-7.04%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#5323) 0 0 ∅ (not applicable) Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
What does this PR do?
Adds an experimental GraalVM native-image build of the ArcadeDB server, delivered as an opt-in add-on that never gates or replaces the JVM distribution.
nativeMaven module, activated only by-Pnative(kept OUT of the default<modules>, somvn clean installis unchanged).tracingmodule are excluded from the native build.native/src/test/scripts/smoke.sh+exercise.sh) that is the single acceptance gate across local, CI, and container runs..github/workflows/native-image.yml): linux/amd64 + linux/arm64 (required), macos/arm64, macos/amd64, windows/amd64 (best-effort). Native-image cannot cross-compile, so each target builds on its own runner.FROM scratch; arm64 = mostly-static glibc (-H:+StaticExecutableWithDynamicLibC) ongcr.io/distroless/base-debian12(GraalVM CE ships no aarch64 musl static libs - Missing static JDK libraries compiled against musl oracle/graal#4645). Multi-arch manifest viaimagetools, published only on release.docs/native-image.mddocumenting the build, matrix, containers, and limitations.Motivation
A native binary gives fast cold start and low memory footprint, which suits edge / Kubernetes / serverless deployments where the JVM's startup and RAM overhead are costly. This revives and completes the abandoned
poc/native_imagebranch (last touched ~2 years ago; GraalVM 23.1.1 / v26.1.1), rebased onto currentmainand GraalVM CE 25.Related issues
Supersedes the stale
poc/native_imagebranch. Referencesoracle/graal#4645(no aarch64 musl static libs in GraalVM CE, which drove the split-arch Docker approach).Additional Notes
Verification. The native binary was built and smoke-tested locally on macos/arm64 (HTTP/Studio/SQL/Cypher/JS plus Redis PING->PONG, Bolt v5.4 handshake, MongoDB hello, gRPC reflection). The Linux static builds and Docker images are CI-only (not buildable on a macOS dev machine). Recommend one manual
workflow_dispatchrun before relying on a release: it--loads and smokes both containers locally and publishes nothing (Docker Hub login and--pushare gated to thereleaseevent, so a branch push / PR cannot publish toarcadedata/arcadedb).Known limitations / follow-ups (none blocking, all documented in
docs/native-image.md):-H:-TruffleCheckBlockListMethods) to embed GraalJS.Atomics/SharedArrayBuffer/TypedArray.prototype.setare reachable via any authenticated{"language":"js"}command and are unverified under AOT runtime compilation. This is a real residual risk for the experimental add-on; the JVM build already exposes the same JS surface.-H:IncludeResources=.*embedding all classpath resources. A size-tightening pass (restrict to Studio/config/Lucene patterns) is the obvious next optimization.macos-15-intelrunner (the freemacos-13was retired). This repo already disabled that runner elsewhere, so it is likely a dead leg; it iscontinue-on-error, so harmless, and can be commented out.jdk.incubator.vectormay fall back to scalar under native-image (perf only, not correctness).Design & plan. The full spec and implementation plan are committed on the branch:
docs/superpowers/specs/2026-07-18-native-image-design.mdanddocs/superpowers/plans/2026-07-18-native-image.md.Checklist
mvn clean packagecommand (the default reactor is unaffected; the native image builds viamvn -Pnative -pl native -am -DskipTests package, verified on macos/arm64)🤖 Generated with Claude Code