mirror of
https://github.com/agent-substrate/substrate.git
synced 2026-11-11 10:23:17 +08:00
# Benchmark DurDir Fixes #673 Adds a `DurDirUser` load-generation workload that exercises the DurableDir suspend/resume loop end to end, verifies every served byte against a SHA-256 digest, and emits separable latency percentiles for each step. ## The loop per VU, first iteration: create -> resume -> WriteDisk -> ReadDisk+verify steady state: suspend -> resume -> ReadDisk+verify (cold) -> ReadDisk+verify (warm) -> WriteDisk (overwrite, TRUNCATE) Under `onCommit: Data` the container cold-boots from the OCI image and process memory is discarded, so a matching digest after resume can only have come from the restored DurableDir. That is the durability assertion. ## Results All six scenarios ran on GKE/gvisor at 1 VU for 1m each: `Data` vs `Full` snapshot scope, explicit vs implicit resume, and a 5/10/64 MiB size sweep. **Zero failures across every run**, so every served byte matched its digest in all six. **Snapshot growth over repeated overwrites:** `SuspendActor` latency stayed flat across consecutive overwrite-and-suspend cycles on the same DurableDir volume. The loop overwrites with `WRITE_MODE_TRUNCATE`, so the file is exactly X bytes after every write and the captured directory contents are the same size every cycle. Nothing accumulates across suspends. That is the growth question the issue asks about. Latency percentiles per step are in the run artifacts. ## Change surface - **glutton:** `WriteDisk` returns size + sha256; new `ReadDisk` with a `READ_MODE_DIGEST_ONLY` mode for measuring restore cost without paying wire transfer; disk RPCs exposed over HTTP mode. - **manifests:** two new ActorTemplates, `glutton-durdir-{data,full}`, with a `durableDir` volume and `onPause: Full` / `onCommit: {Data,Full}`. - **boomer:** shared actor-lifecycle plumbing extracted from the ping task, then a `DurDirUser` task on top of it; a general `resume_mode` knob. - **harnesses:** `--workload` selects the task at deploy time; `durdir.py` stub, typed dynconfig flags, six nightly scenarios. The two boomer commits above are incremental extractions made as the second workload landed. The final package layout for `internal/benchmarking/boomer` lands as a follow-up PR. ## Testing - `go build ./...`, `go test -race ./cmd/benchmarking/... ./internal/benchmarking/...` clean, no race warnings. - `hack/verify-all.sh`: all nine checks pass. Python protos regenerated, tree clean. - Both ActorTemplates reach `Ready`; golden snapshots confirmed in the bucket. - **Manual durability proof:** 200 MiB payload written, suspended, resumed, and re-read with matching sha256 across every read. Process memory discarded and container cold-booted in between. - **Scale validation:** DurableDir persistence verified up to 1 GiB with 0 failures. - **Regression gate:** `glutton_baseline_5_users` ran 919 requests with 0 failures and 7 ms ping latency post-rebase. No regression on the existing benchmark. ## Deliberately out of scope - **Image size over time:** `ActorSnapshot` has no size field, so there is nothing for a client to read. The issue permits deferring this. `SuspendActor` latency is the available proxy; `atelet.snapshot.size` is the server-side one. - **boomer package restructure:** landing as a follow-up so this PR's files stay reviewable in place. - **RAM-backed variant:** glutton already has `WriteRAM`; small follow-up.