Skip to content

atelet,ateom: restore local pause checkpoints directly from LocalSnap - #1876

Merged
Max Smythe (maxsmythe) merged 2 commits into
agent-substrate:mainfrom
chw120:pause-restore-direct-local-checkpoint
Oct 3, 2026
Merged

Max Smythe (maxsmythe) merged 2 commits into
agent-substrate:mainfrom
chw120:pause-restore-direct-local-checkpoint

Conversation

@chw120

@chw120 Chenyi Wang (chw120) commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

For a local pause resume (CHECKPOINT_TYPE_LOCAL), atelet no longer stages the snapshot into restore-state/. It verifies the files in local-checkpoint/<snapshotName>/ and passes that directory to ateom as ActorDirs.restore_dir with preserve_restore_dir = true. The external/GCS restore-state/ flow is unchanged.

Why

main currently hard-links the snapshot into restore-state/, and the only thing stopping ateom from mutating the actor's local restore point is the Nlink > 1 side effect of that hard-link. This PR replaces that implicit guard with an explicit preserve_restore_dir flag: when set, ateom never deletes or rewrites files in restore_dir (no post-eager-restore drop of memory-ranges, copy-merge instead of in-place merge on the next OnDemand checkpoint, no config.json rewrite on a same-actor resume).

This also removes the staging step and its cross-filesystem copy fallback, and gives lifecycle v2, where local snapshots are kept and reused as a cache after upload, a clear read-only contract. Resume latency and disk usage are on par with the current hard-link path.

Follow-ups:

Original benchmark vs the pre-hardlink full-copy baseline

Setup: glutton, 1 user, 512Mi working set, pause/resume lifecycle. gVisor actors use 1536Mi so pages.img stays ~100% cached after pause (same as MicroVM); MicroVM actors use 768Mi.

ResumeActor P50 download (copy) ateom_restore
gVisor 1536Mi, full-copy baseline 740ms ~310ms ~370ms
gVisor 1536Mi, this PR 430ms 0 ~370ms
MicroVM 768Mi, full-copy baseline 2900ms ~1.8s (1.4–2.1s) ~0.97s (0.78–1.1s)
MicroVM 768Mi, this PR 600ms 0 ~0.57s (0.54–0.82s)

ResumeActor P50 is client-side (locust). download and ateom_restore are server-side medians from atelet's "Restore timing breakdown" log (6–8 resumes per run).

  • Tests pass
  • Appropriate changes to documentation are included in the PR

Comment thread cmd/atelet/main.go Resolved
@chw120
Chenyi Wang (chw120) force-pushed the pause-restore-direct-local-checkpoint branch 2 times, most recently from a43e46e to 13039c8 Compare September 25, 2026 22:40
Comment thread cmd/atelet/main.go Resolved
Comment thread cmd/ateom-microvm/checkpoint.go Resolved
Comment thread cmd/ateom-gvisor/main.go Resolved
Comment thread cmd/ateom-microvm/restore.go Resolved
Davanum Srinivas (dims) pushed a commit to dims/substrate that referenced this pull request Sep 26, 2026
While benchmarking pause actor I ran into multiple bottlenecks.

I'm currently importing
agent-substrate#935 into my branch but
agent-substrate#1876 is another
alternative to solve the first bottleneck.

The 2nd bottleneck (primary target of this PR) is due to fsyncs being
called in writeFileAtomic() for sandbox-assets.json and SystemInfoVolume
files. Because these are new files that require ext4 filesystem metadata
updates, and we have ext4 mounted in ordered mode, calling fsync on the
file + directory causes all other pending writes (such as other
checkpoints) to be flushed. So the file creation ends up being blocked
behind other actor's checkpoint syncs.

These files do not actually need to be flushed to disk. SystemInfo is
recreatable. sandbox-assets.json is written during Run/Resume(), and
read during Checkpoint(). But once the checkpoint is finished, the
manifest is saved with the snapshot. If the node crashes between
Run/Resume and Checkpoint, the actor is crashed anyway.

### Single-Node Pause/Resume Benchmark Summary
**Configuration:** 17 workers, 15 actors, `glutton` (`512 MiB` RAM
capacity, `32 MiB` churn per cycle), `120s` run, `1.0s` wait, `100%`
pause (`0%` suspend)

| Metric | Baseline (`upstream/main`) | + Commit 1 (`os.Link` local
checkpoint) | + Commit 2 (remove `fsync` on ephemeral volumes) |
| :--- | :---: | :---: | :---: |
| **Completed Cycles** (`120s`) | `106.5` (`112` pauses / `101` resumes)
| `160.0` (`165` pauses / `155` resumes) | **`212.5`** (`214` pauses /
`211` resumes) |
| **Cycle Throughput** | `53.3 cycles/min` (`0.89/s`) | `80.0
cycles/min` (`1.33/s`, **+50.2%**) | **`106.3 cycles/min`** (`1.77/s`,
**2.00x**) |
| **`ResumeActor` p50** | `6,000 ms` | `4,100 ms` | **`210 ms`**
(**28.6x faster**) |
| **`ResumeActor` avg** | `7,023 ms` | `4,867 ms` | **`246 ms`**
(**28.5x faster**) |
| **`ResumeActor` p90 / p99** | `15,000 ms` / `19,000 ms` | `9,400 ms` /
`20,000 ms` | **`350 ms` / `590 ms`** |


- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR

---------

Co-authored-by: Jefftree <[email protected]>
@chw120
Chenyi Wang (chw120) force-pushed the pause-restore-direct-local-checkpoint branch 2 times, most recently from f6ee4c8 to 1601fdd Compare September 26, 2026 08:38
Comment thread cmd/atelet/main.go Resolved
Comment thread cmd/atelet/main.go Resolved
Comment thread cmd/atelet/main.go Resolved
@chw120
Chenyi Wang (chw120) force-pushed the pause-restore-direct-local-checkpoint branch 2 times, most recently from 37cec75 to 59de1a3 Compare September 30, 2026 16:43
@chw120
Chenyi Wang (chw120) force-pushed the pause-restore-direct-local-checkpoint branch 2 times, most recently from 6057bdd to 7de37ad Compare October 2, 2026 01:38
…esume

Point ActorDirs.restore_dir directly to LocalSnapshotDir for pure local
restores (when not combining with a golden snapshot), avoiding staging
into RestoreStateDir. Keep staging to RestoreStateDir for local
DATA_ON_GOLDEN restores so golden files are cleaned up on restore/reset.
@chw120
Chenyi Wang (chw120) force-pushed the pause-restore-direct-local-checkpoint branch from 7de37ad to 2b061ad Compare October 2, 2026 23:56
@chw120
Chenyi Wang (chw120) force-pushed the pause-restore-direct-local-checkpoint branch from 2b061ad to 5c42bd3 Compare October 3, 2026 02:45

@maxsmythe Max Smythe (maxsmythe) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@maxsmythe
Max Smythe (maxsmythe) added this pull request to the merge queue Oct 3, 2026
Merged via the queue into agent-substrate:main with commit 269df77 Oct 3, 2026
10 checks passed
@chw120
Chenyi Wang (chw120) deleted the pause-restore-direct-local-checkpoint branch October 7, 2026 06:18
Davanum Srinivas (dims) pushed a commit to dims/substrate that referenced this pull request Oct 16, 2026
… actor cgroup OOM (agent-substrate#1917)

## Summary

When a gVisor (`runsc`) sandbox is paused or suspended under a tight
memory limit (`ActorMemory`), writing the snapshot (`pages.img`) inside
the actor's own cgroup doubles memory pressure (`guest RAM + snapshot
page cache`), causing severe swap thrashing or cgroup OOM-kills.

This PR decouples snapshot I/O from the actor's memory cgroup and
deterministically reclaims snapshot page cache after restore:

1. **Checkpoint Cgroup Isolation**: Run `runsc checkpoint` in a
dedicated sibling cgroup(-_ckpt) so snapshot page cache is not charged
against the actor's memory limit during pause or suspend.
2. **Post-Restore Cache Reclamation**: After `runsc wait -restore`
completes, evict snapshot page cache (guarded by `cachestat(2)` so
`FADV_DONTNEED` never forces writeback). Staged files in `restore_dir`
are removed only when `preserve_restore_dir` is unset (GCS
`restore-state/`); for local resume (agent-substrate#1876) the files are kept and only
their cache is evicted.

## Notes

Rebased on agent-substrate#1876: file cleanup now keys off `preserve_restore_dir`
instead of `Nlink`, since a local restore no longer has a hard-link.
`DATA_ON_GOLDEN` case dropped after agent-substrate#2105.

## Benchmark

### 1. Memory-Constrained Local Pause/Resume
(`glutton_1user_500mi_gvisor_pause`)
1 user, 500 MiB active guest RAM (64 MiB dirtied before each pause), 600
MiB actor memory cgroup limit (`ActorMemory`), 1m duration.

| Metric | Baseline (`main`) | With Fix (agent-substrate#1917) | Improvement |
| :--- | :---: | :---: | :---: |
| **Completed Cycles (in 60s)** | 6 (1 actor OOM-crashed) | **23** |
**3.8x throughput** |
| **`PauseActor` Failure Rate** | 25.0% (2/8 failed, `runsc checkpoint`
OOM) | **0.0% (0/23)** | **Eliminates checkpoint OOM** |
| **`PauseActor` P50 / Avg / P95** | 3,400 ms / 3,245 ms / 5,800 ms |
**620 ms / 980 ms / 2,900 ms** | **-82% P50 (-70% Avg, -50% P95)** |
| **`ResumeActor` P50 / Avg / P95** | 3,000 ms / 2,957 ms / 3,000 ms |
**370 ms / 404 ms / 660 ms** | **-88% P50 (8.1x faster)** |

### 2. Memory-Constrained GCS Suspend/Resume
(`glutton_1user_500mi_gvisor_suspend`)
1 user, 500 MiB active guest RAM (64 MiB dirtied before each suspend),
600 MiB actor memory cgroup limit (`ActorMemory`), 1m duration.

| Metric | Baseline (`main`) | With Fix (agent-substrate#1917) | Improvement |
| :--- | :---: | :---: | :---: |
| **Completed Cycles (in 60s)** | 2 (5 actors OOM-crashed) | **9 (10
suspends)** | **4.5x–5x throughput** |
| **`SuspendActor` Failure Rate** | 83.3% (10/12 failed, `runsc
checkpoint` OOM) | **0.0% (0/10)** | **Eliminates checkpoint OOM** |
| **`SuspendActor` Latency (successful)** | 7,700 ms – 8,844 ms |
**2,800 ms P50 / 3,105 ms Avg / 4,400 ms P95** | **-64% to -68%** |
| **`ResumeActor` P50 / Avg / P95** | 1,800 ms / 1,715 ms / 1,815 ms
($N=2$) | **1,500 ms / 1,605 ms / 2,300 ms ($N=9$)** | **-17% P50 (-6%
Avg)** |

#### 3. Multi-User Local Pause/Resume
(`glutton_mem_512mi_4_user_gvisor_pause`)
4 concurrent users, 512 MiB active guest RAM (64 MiB dirtied before each
pause), 768 MiB actor memory cgroup limit (`ActorMemory`), 1m duration.

| Metric | Baseline (`main`) | With Fix (`agent-substrate#1917`) | Improvement |
| :--- | :---: | :---: | :---: |
| **Completed Cycles (in 60s)** | `20` (2 actors OOM-crashed) | **`27`**
| **+35% throughput** |
| **`PauseActor` Failure Rate** | **`15.4%`** (4/26 failed, `runsc
checkpoint` OOM) | **`0.0%`** (0/30) | **Eliminates checkpoint OOM** |
| **`PauseActor` P50 / Avg / P95** | `3,100 ms / 3,272 ms / 8,600 ms` |
**`1,000 ms / 1,008 ms / 1,400 ms`** | **-68% P50 (-69% Avg, -84% P95)**
|
| **`ResumeActor` P50 / Avg / P95** | `3,800 ms / 3,698 ms / 6,000 ms`
($N=20$) | **`5,000 ms / 4,365 ms / 5,600 ms`** ($N=27$) | **-7% P95** |

> **Note:** `ResumeActor` P50/Avg is slightly higher because `main`
OOM-crashes 2 of the 4 actors during `PauseActor`, whereas with `agent-substrate#1917`
all 4 actors stay alive and share the worker pod's 2-vCPU limit
concurrently (+35% more completed cycles).

- [x] Tests pass
- [ ] Appropriate changes to documentation are included in the PR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/gvisor area/microVM area/node area/storage kind/feature An enhancement / feature request or implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants