Skip to content

Latest commit

 

History

History
186 lines (148 loc) · 9.39 KB

remote-strategy.md

File metadata and controls

186 lines (148 loc) · 9.39 KB

Remote strategy

Why justrust should compile remotely, what remote builds can and cannot speed up, and how the product and licensing should be structured. The machine itself is covered in remote.md. The phased plan is in toolchain.md.

Where compile time goes (measured)

Measured on justrust itself (47 dependencies, 16-core laptop) with JUSTRUST_PASSES=always JUSTRUST_DEPCACHE=0. Codegen means rustc generating LLVM IR plus LLVM's own work.

Scenario Codegen share LLVM alone Link
Clean debug, local crate 60% (3.7 of 6.2s) ~15% (IR generation was 2.8s) 8%
Clean debug, all crates 29% (10.8 of 36.9s) ~9% 3%
Clean release, local crate 80% (9.6 of 12.0s) ~70% (passes 3.4s, ThinLTO 5.0s) 2%
Clean release, all crates 50% (47 of 94s) ~45% 1%
Debug rebuild after touch 17% small 51%

Conclusions:

  • The LLVM optimizer only dominates release builds.
  • The debug edit loop is mostly the frontend (single-threaded, re-checks more than the edit touched), rustc's IR generation, and linking.
  • Clean builds are dominated by dependencies: 17.6s of 36.9s of rustc time.
  • Today justrust's codegen bucket merges IR generation with LLVM. Splitting them is a follow-up.

What remote can and cannot win

Round trip: about 60 ms in our own measurement, and 29 to 42 ms to us-west-2 per remote.md. Planning uses 60 ms as the worst case. With a persistent connection an edit costs 2 to 3 round trips (120 to 180 ms). Latency is not the limit.

Workload Remote gain Why
Single-crate edit loop Small, 1.2 to 2x Mostly a single-threaded frontend. More cores barely help, but a faster, unthrottled CPU does
check, clippy, remote test Good when local runs take more than ~0.2s Only diagnostics and results come back, a few KB
build that runs locally (Desktop hot reload) Limited by bandwidth The artifact must come back. Use binary diffs and split debug info
Clean builds, new checkouts, agent slots, CI Large, near instant Dependencies come from the shared cache
Many agents building at once Large Builds scale on the server instead of fighting over 16 laptop cores
Release builds and tests Medium to large LLVM, ThinLTO, and tests parallelize well

The pitch is not "compile my edit faster." It is: never compile dependencies, scale out parallel agents, and offload heavy builds and storage.

Economies of scale: the shared cache

Most of the value comes from caching across users and projects.

Status (implemented): src/depcache_remote.rs here (client) and src/cache.js in the private ~/justrust-cloud (server, R2). After a local depcache miss, eligible registry and git units look up build.jcode.sh/v1/build/cache and download a bundle on a match. Successful compiles queue an upload that a detached justrust cache --sync sends after the run. Two tiers: shared, written only by the prebuild fleet (JUSTRUST_CACHE_PUBLISH_TOKEN), read by every signed-in account, and private, per account (its other machines, CI, hosted hosts). Keys are portable across machines (scheme 2: the cargo home is a placeholder, and proc macros and bins key the glibc version). Verified end to end with two isolated machines (different cargo homes): a cold build of serde+itoa on the second machine was 12/12 remote hits, byte-identical rlibs, a runnable binary, 1.0s instead of 6.1s; another account could not see private entries or publish to the shared tier. Remaining: the prebuild fleet that fills the shared tier from the top crates, a remap of the cargo home in debug info (entries still name the publishing machine's paths in panic locations and debug line tables, like the OUT_DIR case in depcache.rs), and deploying (R2 bucket, CACHE_ENABLED).

Artifact Shared across Value
Popular crates.io dependencies All users Large
Proc macros and build script outputs All users High (syn-heavy crates are slow)
.rmeta only, for check/clippy All users Very high, small to transfer
Whole dependency set keyed by lockfile hash Users of one project One download replaces the dependency build
Own crates at a commit One team, its agents, CI Big for swarms
Own crate after an edit Nobody Stays in the edit loop

Why it improves with scale:

  • Usage follows a power law. A few thousand crates cover most builds, so hit rates rise quickly with users.
  • The marginal cost of a hit is storage and egress only.
  • justrust sets the flags, so it can standardize toolchains, profiles, and remapped paths. Fragmentation is the main enemy of hit rate.
  • A self-hosted copy starts with an empty cache. The full global cache is the moat.

Pre-building:

  • On demand: the first miss builds once, everyone after gets it free.
  • Proactive: on each stable rustc release, build the top N crates using the feature combinations users actually request, learned from misses.

Hard parts:

  • Hermeticity. Some build scripts probe system libraries, read the environment, or embed absolute paths. The cache key must capture every input that can change the output, and units that cannot be made hermetic stay out of the shared tier.
  • Poisoning. The shared tier holds only artifacts our servers built from crates.io sources. Never artifacts uploaded by users. Private code is isolated per organization.

Artifact size: users stop managing target dirs

Rust artifacts are huge. Measured on the dev laptop with du:

Directory Size
jcode-desktop/target 940 GB
jcode/target 920 GB
~/.justrust (shared dependency cache) 22 GB
justrust/target 4.5 GB

The partition (btrfs) reports only 842 GB used in total, so these directories share blocks and du counts the shared blocks once per directory. Even so, build output is likely the largest single use of the disk.

Causes: debug info, incremental caches that keep growing, monomorphized generics copied into each crate, and one copy per target dir, profile, and agent slot. Swarms multiply all of it.

Remote moves storage to the server:

  • Dependencies, .rmeta files, incremental caches, release builds, and test binaries live on the server.
  • check, clippy, and remote test leave almost nothing on the local disk.
  • Only artifacts the user runs locally come back, as binary diffs where possible.
  • Users never need cargo clean or cargo sweep, and swarms stop filling disks.

What we take on: server storage costs, LRU eviction, expiry of old incremental state, deduplication, and a local fallback for offline work.

Follow-up: break down jcode-desktop/target into incremental caches, debug info, and duplicate slots to measure what a zero-target-dir mode saves.

Open source and licensing

Decision: open core, licensed by layer. The exact rules and file layout are in LICENSING.md.

Layer License Why
Client and local tool (today's crate) MIT Adoption. Companies avoid restrictive licenses on build tools, and it is already public under MIT
Wire protocol and cache key format MIT Third-party clients and integrations (CI, sccache or Bazel bridges) make justrust the standard
Clean-room compiler (main branch, whole repository) FSL-1.1-ALv2, inbound Apache 2.0 All new work and the core asset. Usable for anything except a competing service. Each release becomes Apache 2.0 after two years
Self-hostable single-tenant server (server/, future) FSL-1.1-ALv2 Readable, self-hostable, modifiable. Nobody may sell it as a competing service. Each release becomes Apache 2.0 after two years
Multi-tenant cloud: global shared cache, prebuild fleet, isolation, poisoning defenses, scheduling, billing Closed, separate private repo Unproven security surface, and it is the revenue layer. Revisit FSL once it is hardened

Reasoning:

  • The value is in operations and in the network effect of the cache, not in the code. A self-hosted copy starts empty.
  • FSL on the server blocks the one real threat: someone hosting our server as a competing paid service.
  • Opening the poisoning and isolation layer before it is proven publishes the attack surface. Closing it is for security, not secrecy.
  • Comparable products follow this split: Databricks (Apache 2.0 Spark, Delta Lake, and MLflow, closed Photon and managed platform), Turborepo with Vercel Remote Cache, Nx with Nx Cloud, Bazel with BuildBuddy or EngFlow, and Sentry (FSL).
  • Contributing has no friction: no CLA, bot, or sign-off. Contributions to server/ come in under Apache 2.0 (stated in CONTRIBUTING.md and the pull request template), so the maintainer can relicense them and use them in the hosted service. Switch to a click-through CLA if legal review requires it.

Share versus total: open source captures a smaller share of the value created but likely a larger total. Local build speedups were never chargeable. Hosted cache, compute, and team features are.

Before launching the paid service, have a lawyer review the FSL text and its interaction with the MIT parts.

Next steps

  1. Split justrust's codegen bucket into IR generation and LLVM.
  2. Time a real one-line edit in jcode-desktop-ui with full pass timings.
  3. Measure the composition of jcode-desktop/target.
  4. Use recorded runs to measure how much dependency build time a global cache would have served.
  5. Design cache keys and hermeticity rules for a server-built shared tier.