Skip to content

perf(driver-docker): 250 ms supervisor healthcheck costs ~12% of a CPU core per idle sandbox #4729

Closed

Description

@KelvinLin1016

Summary

With the Docker compute driver, every sandbox's supervisor container gets a Docker healthcheck that runs every 250 ms, both during the start period and after it. Each check is a docker exec of /openshell-supervisor health --socket /run/openshell/health.sock, so an idle sandbox triggers four execs per second for its whole lifetime. On our hosts this costs about 12.5% of one CPU core per idle sandbox in the supervisor's cgroup. dockerd and containerd add more on top, about 5% each with two idle sandboxes.

The driver reads the health status only while waiting for startup readiness (poll_docker_supervisor_ready). After the supervisor is healthy, nothing in the driver depends on a fast interval. That makes the steady-state 250 ms cost avoidable.

Environment

  • OpenShell v0.1.4 (deb install, user-level gateway), Docker compute driver
  • Docker Engine 29.9.1, Ubuntu 24.04, kernel 6.8.0, x86_64 (4 vCPU / 8 GB VM)
  • The constant is unchanged on main as of 2026-10-19

Reproduction

  1. Install OpenShell with the Docker driver and create two sandboxes:
    openshell sandbox create --name a --detach
    openshell sandbox create --name b --detach
    
  2. Leave them idle: no client connected, nothing running.
  3. Inspect the supervisor container's healthcheck:
    docker inspect -f '{{json .Config.Healthcheck}}' <supervisor container>
    
    Result:
    {"Test":["CMD","/openshell-supervisor","health","--socket","/run/openshell/health.sock"],
     "Interval":250000000,"Timeout":2000000000,"StartPeriod":60000000000,"StartInterval":250000000,"Retries":3}
  4. Measure the CPU each supervisor cgroup uses over 30 s (usage_usec delta from /sys/fs/cgroup/system.slice/docker-<id>.scope/cpu.stat).

Measurements (idle sandboxes)

What CPU (share of one core)
Supervisor container cgroup, sandbox A (30 s window) 12.4%
Supervisor container cgroup, sandbox B (30 s window) 12.7%
The long-running openshell-supervisor process inside it ~0.6% average (29 s of CPU in 78 min)
dockerd, with 2 idle sandboxes (30 s window) 5.7%
containerd, with 2 idle sandboxes (30 s window) 4.7%

docker stats showed the same thing across five samples: 11–17% per idle supervisor container. The long-running supervisor process is nearly idle; the cost is the short-lived health execs started in the container's cgroup four times a second.

At density this adds up. Twenty idle sandboxes would spend roughly 2.5 cores on healthchecks in the supervisor cgroups alone, before the daemons' share.

Where it comes from

crates/openshell-driver-docker/src/lib.rs (v0.1.4):

const SUPERVISOR_HEALTH_INTERVAL_NS: i64 = 250_000_000;
const SUPERVISOR_HEALTH_TIMEOUT_NS: i64 = 2_000_000_000;
const SUPERVISOR_HEALTH_START_PERIOD_NS: i64 = 60_000_000_000;
...
healthcheck: Some(HealthConfig {
    ...
    interval: Some(SUPERVISOR_HEALTH_INTERVAL_NS),
    start_period: Some(SUPERVISOR_HEALTH_START_PERIOD_NS),
    start_interval: Some(SUPERVISOR_HEALTH_INTERVAL_NS),
}),

The only reader of the health status we found is poll_docker_supervisor_ready, which waits for HEALTHY at startup.

Suggested fix

  • Keep fast startup, slow the steady state: leave start_interval at 250 ms so readiness is detected quickly, and set interval to something like 30 s. Docker uses start_interval only during the start period, so startup latency is unchanged.
  • Make it configurable: expose the steady-state interval in the driver config, for example [openshell.drivers.docker] supervisor_health_interval, for operators who want a different trade-off.
  • Or drop the periodic check after readiness: if the gateway already detects a lost supervisor through its session, the steady-state healthcheck may not be needed at all.

Related

Activity

  1. added
    state:acceptedA maintainer decided OpenShell should pursue this issue
    and removed
    state:triage-neededOpened without agent diagnostics and needs triage
    on Oct 21, 2026
  2. TaylorMutch commented on Oct 21, 2026

    @TaylorMutch
    Collaborator

    📋 triage-agent

    Classification: Validated performance bug
    Disposition: Accepted

    Current main still assigns the 250 ms interval to both startup and steady-state Docker supervisor healthchecks. The driver reads Docker health only during startup; afterward it monitors container exit.

    A local Docker probe using the actual supervisor health binary independently reproduced frequent process execution and 4.97–7.50% CPU usage. The reporter’s exact Linux measurement remains environment-dependent, but the underlying overhead is confirmed. No duplicate or open fix PR was found.

    Keep start_interval at 250 ms and use a substantially slower steady-state interval, with focused test coverage preventing the two values from being coupled again.

    Maintainer decision: pursue this issue.

  3. letv1nnn commented on Oct 21, 2026

    @letv1nnn
    Contributor

    Hi @KelvinLin1016, I'd like to take this one if nobody's working on it.

  4. lunarwhite commented on Oct 21, 2026

    @lunarwhite
    Contributor

    Hi @letv1nnn, I'm working on this.. Sorry I should have given a heads-up earlier 🙌

  5. lunarwhite commented on Oct 21, 2026

    @lunarwhite
    Contributor

    PR is ready for review: #4802, cc @TaylorMutch

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

    area:computestate:acceptedA maintainer decided OpenShell should pursue this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions