Skip to content

v0.1.7: max_workspaces_per_sandbox — the second #216 control, blocked on pair 1 (#179) #219

Description

@yifanxuaaa

Current focus (owner direction, 2026-09-21)

Two cases are the optimization targets, and each is to be matched against
v0.1.6
rather than argued about from memory:

case family route timer size hops to the Store
1 namespace-10000 init_namespace namespace layerstack_init_ns 10,000 files / 300 MB + 100 MB anchor = 400 MB direct, host-side
2 payload-create-100m payload_create_read workspace pure_call_sum_ns 100 MB payload container FUSE + daemon to the host service

Acceptance bar — APPROVED (owner, 2026-09-21). namespace-10000 must
(1) reproduce ≤ 578.245 ms (≥ 691.8 MB/s) with a declared cache contract and a
passing verification, (2) pair against the v0.1.6 reference arm — same seed,
fresh-output, topology, harness and image identity, same declared cache state — with
the candidate median ≤ the reference median, both with spread, and (3) be met by
one declared sample per arm, never by selecting the fastest of several runs. The
executing plan is
issue219-namespace-10000-plan.md.

Case naming. In init_namespace the number in the case id is a file count,

not megabytes**: namespace-{100,1000,10000,100000} at 100 files / 5 MB (+1 MB
anchor), 1,000 / 20 MB (+5 MB), 10,000 / 300 MB (+100 MB) and 100,000 / 500 MB
(+100 MB). There is no namespace-100mb case; the 100 MB is the anchor file
(NAMESPACE_ANCHOR_BYTES), which only the two largest tiers carry. Case 1 is
therefore the 10,000-file tier with the 100 MB anchor, and namespace-100000 is its
larger sibling with the same anchor.

Neither focus case has a baseline arm (per init_namespace case: 100 = 11
candidate / 3 baseline, 1,000 = 6 / 0, 10,000 = 8 / 0, 100,000 = 32 / 15;
payload_create_read = none at all), so both must produce their reference arm.
namespace-100000 is the substitute if a case with an existing pair is preferred.

Case 1 writes the namespace into the host-owned Store directly (the runner stages
it under payload/, and namespace/store-footprint are the routes that take no
payload/input staging and no daemon hop). Case 2 carries its payload from the
container through the daemon and the host service into the same Store
(LAYERFS_EXEC_TRANSPORT=daemon, LAYERFS_FUSE_TRANSPORT=daemon; the receipt's
scope line is "Linux daemon/FUSE container command window; host coordinator/Store
process CPU/RSS/IO reported separately"). Both end in one Store commit.

The namespace-init multi-worker count (4 = SMALL_CONTENT_WORKERS,
crates/layerfs-layerstack-store/src/objects.rs; 8 =
construction_worker_limit(), crates/layerfs-workspace/src/changes.rs) is
recorded and is not treated as the explanation for our slowness: it is the
cheapest thing to refute by measurement, and the RCA continues past it.

Exploration, pairing and gap-finding come first; no optimization is authorized by
this section. The prompt is
issue219-two-case-rca-prompt.md.

Status: Blocked; not implemented. Nothing in the product admits, counts or refuses Workspace creation today.

Why this exists

#216 defined two primary operator-facing concurrency controls. Its writer half — max_concurrent_writes_per_store — landed in #217 (merge 1f34ac939) and is measured and tested. The other half, max_workspaces_per_sandbox, could not be implemented there: there is no Workspace, sandbox or FUSE lifecycle component in this tree, because pair 1 (#179) owns it and is not merged. #216 was closed as completed by owner decision; this issue carries the remaining acceptance row so it is not lost.

The control, as defined in #216

Setting max_workspaces_per_sandbox (proposed name)
Meaning maximum live Workspace instances owned by one sandbox
Unit a live instance, not a Branch, Commit or stored root. A frozen generation and its live successor are one instance
Enforcement owner pair 1 sandbox/Workspace lifecycle
Reservation capacity is checked and reserved before the instance becomes visible
Teardown an incomplete teardown keeps counting until its resources are released
Aggregation separate sandboxes have independent counts; this needs a trusted sandbox identity and an explicit rule if more than one caller can create Workspaces

Default, supported range, validation and configuration-change behavior are open and must be selected with the implementation. The previously discussed 16 was illustrative and is not an approved default. The writer control's own precedent is available: one persisted value, an explicit refusal at the bound, retained ownership that a lowering never releases, and a documented change path (Store::set_max_concurrent_writes).

Acceptance

  • Exactly one operator-facing Workspace-count control is documented with scope, default, supported range, validation and configuration-change behavior; no hidden cap prevents the selected setting.
  • Creation admits up to the limit, refuses the next without partial visibility, and releases capacity correctly on teardown; separate sandboxes have independent counts.
  • A frozen generation and its live successor count once.
  • Retained/incomplete-teardown instances keep counting and cannot be bypassed by changing the configuration.
  • The writer control is unchanged by this work, and both controls are described together in one place.

Boundaries

Blocked on #179; do not invent a Workspace lifecycle here, and do not enforce the limit in a component that does not own creation. No new durability, no third-party patching, no aggregate gate.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions