diff --git a/.cargo/config.toml b/.cargo/config.toml index 2e822958..9547cfc0 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,3 +2,9 @@ [env] TS_RS_EXPORT_DIR = { value = "clients/javascript/lib/gen_types", relative = true } + +[target.x86_64-unknown-linux-gnu] +rustflags = ["-C", "link-arg=-fuse-ld=mold"] + +[target.aarch64-unknown-linux-gnu] +rustflags = ["-C", "link-arg=-fuse-ld=mold"] diff --git a/.github/workflows/js-client-test.yml b/.github/workflows/js-client-test.yml index 749d6337..c4d7ac1b 100644 --- a/.github/workflows/js-client-test.yml +++ b/.github/workflows/js-client-test.yml @@ -44,6 +44,11 @@ jobs: with: toolchain: ${{ env.RUST_VERSION }} + - name: Install mold + run: | + sudo apt-get update + sudo apt-get install -y mold + - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: diff --git a/.github/workflows/server-code-coverage.yml b/.github/workflows/server-code-coverage.yml index de37db1d..08531f7a 100644 --- a/.github/workflows/server-code-coverage.yml +++ b/.github/workflows/server-code-coverage.yml @@ -23,6 +23,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Install mold + run: | + sudo apt-get update + sudo apt-get install -y mold + - uses: actions/cache@v2 with: path: | diff --git a/.github/workflows/server-test.yml b/.github/workflows/server-test.yml index 876ba067..f8d21ac9 100644 --- a/.github/workflows/server-test.yml +++ b/.github/workflows/server-test.yml @@ -46,6 +46,11 @@ jobs: toolchain: ${{ env.RUST_VERSION }} components: clippy + - name: Install mold + run: | + sudo apt-get update + sudo apt-get install -y mold + - uses: actions/cache@v4 with: path: | diff --git a/Cargo.toml b/Cargo.toml index ce2b62e7..aa99d7e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,11 +21,13 @@ unwrap_used = "deny" expect_used = "deny" [profile.release] -# Can improve slightly the performance at the cost of increased compile time -# https://doc.rust-lang.org/cargo/reference/profiles.html#lto lto = "thin" -# https://doc.rust-lang.org/cargo/reference/profiles.html#codegen-units codegen-units = 1 -# Add basic debug information, at the cost of binary size -# See https://doc.rust-lang.org/cargo/reference/profiles.html#debug +strip = "symbols" + +[profile.release-dd] +inherits = "release" +lto = "thin" +codegen-units = 1 debug = 1 +strip = "none" diff --git a/README.md b/README.md index 919132e2..10583c78 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,12 @@ This project uses [Just](https://github.com/casey/just) as a task manager. To in brew install just ``` +On Linux, including WSL, install [`mold`](https://github.com/rui314/mold) too. The repository Cargo config uses it as the linker for Linux builds. + +```sh +sudo apt install mold +``` + Once Rust and Just are installed, a few more utility tools can be installed by running ```sh diff --git a/alpine.Dockerfile b/alpine.Dockerfile index c1065ac9..bdd52629 100644 --- a/alpine.Dockerfile +++ b/alpine.Dockerfile @@ -1,38 +1,47 @@ # Usage: -# docker buildx build -f alpine.Dockerfile -t image:tag --build-arg='ARCH=x86_64' --platform linux/amd64 . -# docker buildx build -f alpine.Dockerfile -t image:tag --build-arg='ARCH=aarch64' --platform linux/arm64 . +# docker buildx build -f alpine.Dockerfile -t image:tag --platform linux/amd64 . +# docker buildx build -f alpine.Dockerfile -t image:tag --platform linux/arm64 . -ARG ARCH=x86_64 -FROM messense/rust-musl-cross:${ARCH}-musl AS builder +ARG TARGETARCH=amd64 + +FROM messense/rust-musl-cross:x86_64-musl AS builder-amd64 +ENV MUSL_TARGET=x86_64-unknown-linux-musl + +FROM messense/rust-musl-cross:aarch64-musl AS builder-arm64 +ENV MUSL_TARGET=aarch64-unknown-linux-musl + +FROM builder-${TARGETARCH} AS builder ARG ARCH=x86_64 ARG APP_NAME=nittei ARG RUST_VERSION=1.96.0 +ENV BUILD_PROFILE=release # Install and set the specific Rust version RUN rustup install ${RUST_VERSION} && rustup default ${RUST_VERSION} # Install the musl target for the correct architecture -RUN rustup target add ${ARCH}-unknown-linux-musl +RUN rustup target add ${MUSL_TARGET} # Verify the Rust and target setup RUN rustc --version && rustup show # Copy source code +COPY .cargo .cargo COPY ./Cargo.toml ./Cargo.lock ./ COPY ./crates ./crates COPY ./bins ./bins COPY ./clients/rust ./clients/rust -# Build application -RUN cargo build --release --target ${ARCH}-unknown-linux-musl && \ - cp ./target/${ARCH}-unknown-linux-musl/release/${APP_NAME} /${APP_NAME} +RUN cargo build --locked --profile ${BUILD_PROFILE} --target ${MUSL_TARGET} \ + --bin nittei \ + --bin nittei-migrate -# Build migrate binary -RUN cargo build --release --bin nittei-migrate --target ${ARCH}-unknown-linux-musl && \ - cp ./target/${ARCH}-unknown-linux-musl/release/nittei-migrate /nittei-migrate +RUN mkdir -p /out \ + && cp ./target/${MUSL_TARGET}/${BUILD_PROFILE}/nittei /out/nittei \ + && cp ./target/${MUSL_TARGET}/${BUILD_PROFILE}/nittei-migrate /out/nittei-migrate -#Create a new stage with a minimal image +# Create a new stage with a minimal image FROM alpine:3.23.4 # Set the git repository url and commit hash for DD @@ -47,10 +56,6 @@ ENV DD_SOURCE_CODE_PATH_MAPPING="/app/nittei/bins:/bins,/app/nittei/crates:/crat ARG RUST_BACKTRACE=1 ENV RUST_BACKTRACE=${RUST_BACKTRACE} -ARG APP_NAME=nittei -ENV APP_NAME=${APP_NAME} - -COPY --from=builder /${APP_NAME} /${APP_NAME} -COPY --from=builder /nittei-migrate /nittei-migrate +COPY --from=builder /out/ / -CMD ["/bin/sh", "-c", "exec /${APP_NAME}"] +CMD ["/nittei"] diff --git a/alpineWithDD.Dockerfile b/alpineWithDD.Dockerfile index 8bddb504..4e2ee22e 100644 --- a/alpineWithDD.Dockerfile +++ b/alpineWithDD.Dockerfile @@ -1,50 +1,59 @@ # This Dockerfile is based on the alpine.Dockerfile and adds the ddprof tool to the image. # # Usage: -# docker buildx build -f alpineWithDD.Dockerfile -t image:tag --build-arg='ARCH=x86_64' --platform linux/amd64 . -# docker buildx build -f alpineWithDD.Dockerfile -t image:tag --build-arg='ARCH=aarch64' --platform linux/arm64 . +# docker buildx build -f alpineWithDD.Dockerfile -t image:tag --platform linux/amd64 . +# docker buildx build -f alpineWithDD.Dockerfile -t image:tag --platform linux/arm64 . -ARG ARCH=x86_64 -FROM messense/rust-musl-cross:${ARCH}-musl AS builder +ARG TARGETARCH=amd64 -ARG ARCH=x86_64 -ARG APP_NAME=nittei +FROM messense/rust-musl-cross:x86_64-musl AS builder-amd64 +ENV MUSL_TARGET=x86_64-unknown-linux-musl + +FROM messense/rust-musl-cross:aarch64-musl AS builder-arm64 +ENV MUSL_TARGET=aarch64-unknown-linux-musl + +FROM builder-${TARGETARCH} AS builder + +ARG TARGETARCH ARG RUST_VERSION=1.96.0 +ENV BUILD_PROFILE=release-dd +ENV RUSTFLAGS="-C force-frame-pointers=yes" # Install and set the specific Rust version RUN rustup install ${RUST_VERSION} && rustup default ${RUST_VERSION} # Install the musl target for the correct architecture -RUN rustup target add ${ARCH}-unknown-linux-musl +RUN rustup target add ${MUSL_TARGET} # Verify the Rust and target setup RUN rustc --version && rustup show # Copy source code +COPY .cargo .cargo COPY ./Cargo.toml ./Cargo.lock ./ COPY ./crates ./crates COPY ./bins ./bins COPY ./clients/rust ./clients/rust -# Build application -RUN cargo build --release --target ${ARCH}-unknown-linux-musl && \ - cp ./target/${ARCH}-unknown-linux-musl/release/${APP_NAME} /${APP_NAME} +RUN cargo build --locked --profile ${BUILD_PROFILE} --target ${MUSL_TARGET} \ + --bin nittei \ + --bin nittei-migrate -# Build migrate binary -RUN cargo build --release --bin nittei-migrate --target ${ARCH}-unknown-linux-musl && \ - cp ./target/${ARCH}-unknown-linux-musl/release/nittei-migrate /nittei-migrate +RUN mkdir -p /out \ + && cp ./target/${MUSL_TARGET}/${BUILD_PROFILE}/nittei /out/nittei \ + && cp ./target/${MUSL_TARGET}/${BUILD_PROFILE}/nittei-migrate /out/nittei-migrate # Install ddprof -RUN ARCH_IN_URL=$(case "${ARCH}" in \ - x86_64) echo "amd64" ;; \ - aarch64) echo "arm64" ;; \ +RUN ARCH_IN_URL=$(case "${TARGETARCH}" in \ + amd64) echo "amd64" ;; \ + arm64) echo "arm64" ;; \ *) echo "unsupported-arch" && exit 1 ;; \ esac) && \ curl -Lo ddprof-linux.tar.xz https://github.com/DataDog/ddprof/releases/latest/download/ddprof-${ARCH_IN_URL}-linux.tar.xz && \ tar xvf ddprof-linux.tar.xz && \ mv ddprof/bin/ddprof /ddprof -#Create a new stage with a minimal image +# Create a new stage with a minimal image FROM alpine:3.23.4 # Set the git repository url and commit hash for DD @@ -59,11 +68,7 @@ ENV DD_SOURCE_CODE_PATH_MAPPING="/app/nittei/bins:/bins,/app/nittei/crates:/crat ARG RUST_BACKTRACE=1 ENV RUST_BACKTRACE=${RUST_BACKTRACE} -ARG APP_NAME=nittei -ENV APP_NAME=${APP_NAME} - -COPY --from=builder /${APP_NAME} /${APP_NAME} -COPY --from=builder /nittei-migrate /nittei-migrate +COPY --from=builder /out/ / COPY --from=builder /ddprof /ddprof -CMD ["/bin/sh", "-c", "exec /ddprof --preset cpu_live_heap /${APP_NAME}"] +CMD ["/ddprof", "--preset", "cpu_live_heap", "/nittei"] diff --git a/debian.Dockerfile b/debian.Dockerfile index e8bde3eb..23ec0115 100644 --- a/debian.Dockerfile +++ b/debian.Dockerfile @@ -1,25 +1,32 @@ # Usage: -# docker buildx build -f debian.Dockerfile -t image:tag --build-arg='ARCH=x86_64' --platform linux/amd64 . -# docker buildx build -f debian.Dockerfile -t image:tag --build-arg='ARCH=aarch64' --platform linux/arm64 . +# docker buildx build -f debian.Dockerfile -t image:tag --platform linux/amd64 . +# docker buildx build -f debian.Dockerfile -t image:tag --platform linux/arm64 . FROM rust:1.96.0-slim-trixie AS builder WORKDIR /app/nittei +ARG TARGETARCH +ENV BUILD_PROFILE=release +# cargo-sonic intentionally does not reuse payload RUSTFLAGS / CARGO_*_RUSTFLAGS +# for its small generated loader build. Tell it to use mold for the loader too, +# matching the payload link step configured in .cargo/config.toml. +ENV CARGO_SONIC_LOADER_RUSTFLAGS="-C link-arg=-fuse-ld=mold" RUN apt update \ - && apt install -y --no-install-recommends curl openssl ca-certificates pkg-config build-essential libssl-dev \ + && apt install -y --no-install-recommends curl openssl ca-certificates pkg-config build-essential libssl-dev mold \ && apt clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -COPY ./Cargo.toml ./Cargo.lock .cargo ./ +RUN cargo install cargo-sonic --version 0.2.0 --locked + +COPY .cargo .cargo +COPY ./Cargo.toml ./Cargo.lock ./ COPY ./clients/rust ./clients/rust COPY ./crates ./crates COPY ./bins ./bins +COPY ./scripts ./scripts -RUN cargo build --locked --release && \ - cp ./target/release/nittei /nittei && \ - cargo build --locked --release --bin nittei-migrate && \ - cp ./target/release/nittei-migrate /nittei-migrate +RUN ./scripts/build_release.sh # Use the distroless base image for final image FROM gcr.io/distroless/cc-debian13 @@ -38,7 +45,6 @@ ENV RUST_BACKTRACE=${RUST_BACKTRACE} USER nonroot:nonroot -COPY --from=builder --chown=nonroot:nonroot /nittei /nittei -COPY --from=builder --chown=nonroot:nonroot /nittei-migrate /nittei-migrate +COPY --from=builder --chown=nonroot:nonroot /tmp/nittei-build/ / CMD ["/nittei"] diff --git a/debianWithDD.Dockerfile b/debianWithDD.Dockerfile index 06f3dd55..a7c0d6c9 100644 --- a/debianWithDD.Dockerfile +++ b/debianWithDD.Dockerfile @@ -1,35 +1,42 @@ # This Dockerfile is based on the debian.Dockerfile and adds the ddprof tool to the image. # Usage: -# docker buildx build -f debianWithDD.Dockerfile -t image:tag --build-arg='ARCH=x86_64' --platform linux/amd64 . -# docker buildx build -f debianWithDD.Dockerfile -t image:tag --build-arg='ARCH=aarch64' --platform linux/arm64 . +# docker buildx build -f debianWithDD.Dockerfile -t image:tag --platform linux/amd64 . +# docker buildx build -f debianWithDD.Dockerfile -t image:tag --platform linux/arm64 . FROM rust:1.96.0-slim-trixie AS builder WORKDIR /app/nittei -ARG ARCH=x86_64 - +ARG TARGETARCH +ENV BUILD_PROFILE=release-dd +ENV RUSTFLAGS="-C force-frame-pointers=yes -C link-arg=-fuse-ld=mold" +# cargo-sonic intentionally does not reuse payload RUSTFLAGS / CARGO_*_RUSTFLAGS +# for its small generated loader build. Tell it to use mold for the loader too, +# matching the payload link step. Frame pointers are not needed on the launcher +# (ddprof profiles the payload process, not the loader). +ENV CARGO_SONIC_LOADER_RUSTFLAGS="-C link-arg=-fuse-ld=mold" RUN apt update \ - && apt install -y --no-install-recommends curl openssl ca-certificates pkg-config build-essential libssl-dev \ + && apt install -y --no-install-recommends curl openssl ca-certificates pkg-config build-essential libssl-dev mold \ && apt clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -COPY ./Cargo.toml ./Cargo.lock .cargo ./ +RUN cargo install cargo-sonic --version 0.2.0 --locked + +COPY .cargo .cargo +COPY ./Cargo.toml ./Cargo.lock ./ COPY ./clients/rust ./clients/rust COPY ./crates ./crates COPY ./bins ./bins +COPY ./scripts ./scripts -RUN cargo build --locked --release && \ - cp ./target/release/nittei /nittei && \ - cargo build --locked --release --bin nittei-migrate && \ - cp ./target/release/nittei-migrate /nittei-migrate +RUN ./scripts/build_release.sh # Install ddprof -RUN ARCH_IN_URL=$(case "${ARCH}" in \ - x86_64) echo "amd64" ;; \ - aarch64) echo "arm64" ;; \ +RUN ARCH_IN_URL=$(case "${TARGETARCH:-$(uname -m)}" in \ + amd64|x86_64) echo "amd64" ;; \ + arm64|aarch64) echo "arm64" ;; \ *) echo "unsupported-arch" && exit 1 ;; \ esac) && \ curl -Lo ddprof-linux.tar.xz https://github.com/DataDog/ddprof/releases/latest/download/ddprof-${ARCH_IN_URL}-linux.tar.xz && \ @@ -53,8 +60,7 @@ ENV RUST_BACKTRACE=${RUST_BACKTRACE} USER nonroot:nonroot -COPY --from=builder --chown=nonroot:nonroot /nittei /nittei -COPY --from=builder --chown=nonroot:nonroot /nittei-migrate /nittei-migrate +COPY --from=builder --chown=nonroot:nonroot /tmp/nittei-build/ / COPY --from=builder --chown=nonroot:nonroot /ddprof /ddprof CMD ["/ddprof", "--preset", "cpu_live_heap", "/nittei"] diff --git a/docs/deployment.md b/docs/deployment.md index 5203bddf..13a661ab 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -8,6 +8,18 @@ Or build from source: cargo run --release ``` +### mold + +The production Docker builds install [`mold`](https://github.com/rui314/mold) and configure Rust to use it as the linker. This keeps release and profiling builds faster, especially when LTO is enabled. + +### cargo-sonic + +The Debian Docker images use [`cargo-sonic`](https://docs.rs/crate/cargo-sonic/latest) to build CPU-dispatched binaries. The image contains a baseline payload plus an optimized payload for the target architecture: `x86-64-v3` on `amd64`, and `neoverse-n1` on `arm64`. + +The build uses `--loader=bundle`, so the final image must include both the launcher binaries and their adjacent `.bundle` directories. + +### Database + Then set up a postgres db with the init script specified [here](../crates/infra/migrations/dbinit.sql). Lastly provide the following environment variables to the `nittei` server: diff --git a/scripts/build_release.sh b/scripts/build_release.sh new file mode 100755 index 00000000..b9c7a3cb --- /dev/null +++ b/scripts/build_release.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -euo pipefail + +profile="${BUILD_PROFILE:-release}" +target_dir="${CARGO_TARGET_DIR:-target}" +out_dir="${OUT_DIR:-/tmp/nittei-build}" +os="$(uname -s)" +parallelism="${CARGO_SONIC_PARALLELISM:-2}" + +mkdir -p "$out_dir" + +if [[ "$os" == "Linux" ]]; then + arch="${TARGETARCH:-$(uname -m)}" + + # Non-baseline target CPUs to build payloads for. The architecture baseline + # (x86-64 / generic) is always built implicitly by cargo-sonic; do not list it. + # + # Each extra CPU is a full extra payload build, so size and build time scale + # linearly with the list. + case "$arch" in + amd64|x86_64) + # v3 = Haswell+/Zen+ (AVX2, BMI2). v4 = AVX-512 (Sapphire Rapids, Zen 4+). + target_cpus="x86-64-v3,x86-64-v4" + ;; + arm64|aarch64) + # n1 = AWS Graviton2 / Ampere Altra. v1 = AWS Graviton3 (SVE). + target_cpus="neoverse-n1,neoverse-v1" + ;; + *) + echo "Unsupported architecture: $arch" >&2 + exit 1 + ;; + esac + + # cargo-sonic builds a CPU-dispatched fat binary: a small launcher plus + # per-target-CPU payloads in an adjacent .bundle/ directory. + # NOTE: --release and --profile are mutually exclusive in modern cargo, so + # only --profile is passed (covers both `release` and `release-dd`). + # Only the server binary benefits from CPU dispatch; the migration binary is + # a one-shot tool and is built with plain cargo to avoid artifact collisions + # in the sonic output directory. + cargo sonic \ + --target-cpus="$target_cpus" \ + --loader=bundle \ + --parallelism="$parallelism" \ + build \ + --package nittei \ + --bin nittei \ + --locked \ + --profile "$profile" + + cargo build \ + --package nittei \ + --bin nittei-migrate \ + --locked \ + --profile "$profile" + + # Final launchers live at exactly: target/sonic/// + # Bundle dirs live alongside them as: target/sonic///.bundle/ + # Pin mindepth/maxdepth to avoid picking up intermediate per-payload artifacts. + find "$target_dir/sonic" -mindepth 3 -maxdepth 3 -type f -name 'nittei' \ + -exec cp {} "$out_dir/" \; + + find "$target_dir/sonic" -mindepth 3 -maxdepth 3 -type d -name '*.bundle' \ + -exec cp -a {} "$out_dir/" \; + + cp "$target_dir/$profile/nittei-migrate" "$out_dir/nittei-migrate" +else + # cargo-sonic only supports Linux x86_64/aarch64 (memfd_create + execveat). + # On other platforms (e.g. macOS for local dev), fall back to a plain build. + echo "Non-Linux host ($os) detected; falling back to plain 'cargo build' (cargo-sonic is Linux-only)." >&2 + + cargo build \ + --package nittei \ + --bin nittei \ + --bin nittei-migrate \ + --locked \ + --profile "$profile" + + cp "$target_dir/$profile/nittei" "$out_dir/nittei" + cp "$target_dir/$profile/nittei-migrate" "$out_dir/nittei-migrate" +fi