Skip to content

perf(app): read the version list and the running version in one round trip per host - #164

Merged
mhenrixon merged 1 commit into
mainfrom
perf/stale-containers-single-capture
Sep 12, 2026
Merged

perf(app): read the version list and the running version in one round trip per host#164
mhenrixon merged 1 commit into
mainfrom
perf/stale-containers-single-capture

Conversation

@mhenrixon

@mhenrixon mhenrixon commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

A fold, not a behaviour change. dash app stale_containers asked each (host, role) two questions in two SSH round trips — every version with a container, then the version running now. The deploy runs it with --stop before every boot, so every deploy paid twice per host.

  • lib/dash/commands/app.rbDash::Commands::App#stale_state chains list_versions, an echo of BOOT_STATE_SEPARATOR, and current_running_version, the same shape as boot_state. New Dash::Commands::App.split_state(output) returns the two raw halves either side of the separator line.
  • lib/dash/cli/app.rbstale_containers captures stale_state once per (host, role) and splits. Stopping stays one execute per stale version; on a healthy fleet that list is empty.
  • lib/dash/cli/app/boot.rbcapture_boot_state uses the same split_state, so the separator's escape/anchor logic lives once, beside the constant that defines it.

Which containers count as stale, the stop arguments, raise_on_non_zero_exit: false, the lock behaviour, and the printed lines are unchanged. The cost-guard sequence in test/cli/main_test.rb is untouched (app:stale_containers is stubbed there).

Closes #162

Measured

Integration harness, app_with_roles (web on vm1+vm2, workers on vm3 = 3 host/role pairs), same machine, origin/main (90c6663) vs this branch.

before after
deploy Detect stale containers 0.1s 6 ssh 0.4s Detect stale containers 0.1s 3 ssh 0.3s
redeploy Detect stale containers 0.2s 6 ssh 0.5s Detect stale containers 0.1s 3 ssh 0.2s

One round trip per (host, role), as the issue asked. The issue's own 4-host baseline (8 ssh) becomes 4 on that topology by the same arithmetic; that fleet was not re-run here. Wall-clock on the harness is noise-level either way — the count is the measured quantity.

Test plan

  • test/commands/app_test.rb — exact stale_state string; split_state halves, including that only a whole separator line splits
  • test/cli/app_test.rb — stale detected and stopped exactly as before; empty output on both halves means nothing stale; --quiet drops the host header; one capture per (host, role) on deploy_with_roles.yml (4 pairs → 4 captures, was 8)
  • bundle exec rubocop --parallel — no offenses
  • unit suite — 1879 runs, 0 failures
  • bin/test — 1897 runs, 0 failures (Docker + published proxy image)

Deviations & judgment calls

  • Discovery: --quiet never silenced the stale lines. puts_by_host(quiet:) (lib/dash/sshkit_with_ext.rb:43) only drops the App Host: <host> header; the finding always prints. The issue's gate said "--quiet still silent" — it was never silent. The test pins the real contract instead (header gone, "Detected stale container …" still printed). Behaviour is unchanged by this PR either way.
  • Judgment call: extracted Dash::Commands::App.split_state rather than repeating the partition(/^#{Regexp.escape(SEPARATOR)}$/) line in a second file. The issue allowed either ("extract a tiny shared helper if both call sites end up identical, otherwise three lines is fine"); the split line is identical, and keeping it beside the constant means the escape and anchor logic exists once. Post-processing stays per-caller: boot wants two presences, stale wants a list minus one. This touches lib/dash/cli/app/boot.rb, which the issue did not list.
  • Judgment call: no wall-clock re-measurement of the issue's own 4-host row — that fleet is not reproducible here. Measured on the harness's 3-pair topology instead (table above) and pinned the count in a unit test.

https://claude.ai/code/session_015NuGuQJZn6d7wYvWizV1Lw


Summary by cubic

Halves the SSH round trips dash app stale_containers makes per (host, role) by reading the version list and the running version in a single capture instead of two. Stale-detection behavior, stop arguments, lock handling, and printed lines are unchanged, so every deploy that runs with --stop now pays half the SSH cost.

Refactors

  • Extracts Dash::Commands::App.split_state and reuses it in boot-state capture so the separator parsing lives in one place.

Closes #162.

Written for commit 8688f5d. Summary will update on new commits.

Review in cubic

… trip per host

`dash app stale_containers` asked each (host, role) two questions in two SSH
round trips: every version with a container, then the version running now.
The deploy runs this with --stop before every boot, so a 4-host fleet paid
8 round trips to produce 4 small lists.

Dash::Commands::App#stale_state chains the two builders around the same
separator #boot_state uses; the CLI captures it once and splits. The split
now lives beside the separator as Dash::Commands::App.split_state, shared
with Boot#capture_boot_state, so the escape and anchor logic exists once.

Which containers count as stale, the stop arguments, the lock behaviour and
the printed lines are unchanged.

Refs #162

Claude-Session: https://claude.ai/code/session_015NuGuQJZn6d7wYvWizV1Lw

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 5 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@mhenrixon
mhenrixon merged commit 61c740e into main Sep 12, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detect stale containers: read the version list and the running version in one round trip per host

1 participant