Skip to content

feat: capacity resilience — ordered subnet/AZ + instance-type fallback#53

Merged
kurok merged 1 commit into
mainfrom
feat/capacity-fallback
Jul 2, 2026
Merged

feat: capacity resilience — ordered subnet/AZ + instance-type fallback#53
kurok merged 1 commit into
mainfrom
feat/capacity-fallback

Conversation

@kurok

@kurok kurok commented Jul 2, 2026

Copy link
Copy Markdown

Closes #40 — roadmap (#49) Phase 1. Multiplies the value of spot (#39); independent of it for on-demand users.

Problem

One subnet-id + one ec2-instance-type = single point of failure. When that AZ has no capacity for that type (routine for larger/GPU types), RunInstances fails and the workflow fails with it. Capacity errors are retriable placement problems — the action should own the retry.

Solution

Both inputs accept comma-separated ordered fallback lists (single values behave byte-identically):

ec2-instance-type: c7i.4xlarge,c6i.4xlarge,m7i.4xlarge
subnet-id: subnet-aaa,subnet-bbb   # different AZs
  • Order: for each type, try every subnet/AZ before downgrading the type (placement is cheaper than a hardware change).
  • Capacity errors (InsufficientInstanceCapacity, InsufficientHostCapacity, Unsupported, …) → advance to the next cell.
  • Non-capacity errors (invalid AMI, auth, quota InstanceLimitExceeded) → abort immediately so a misconfig doesn't burn the whole matrix; quota gets a targeted message.
  • Transient errors (throttling, InternalError) → retried within the cell via withRetry's new shouldRetry predicate.
  • Each failed placement logs a warn line (type, subnet, code); full exhaustion fails with a summary of every attempt.

Outputs

New instance-type-used / subnet-id-used report what actually launched.

Tests / build

  • tests/fallback.test.js: classifyRunError buckets + launchWithFallback ordering — first-try success, mid-chain success (exact subnet-first-then-type call order), full-matrix exhaustion summary, fatal-abort, quota message. Plus parseCsv and withRetry shouldRetry. 142 tests pass (was 128). Lint clean, dist rebuilt (verify-dist green).

Wall-clock bound

Capacity errors advance without per-cell retry; only transient errors retry (bounded by retry.js). A 9-cell chain of fast capacity rejections resolves in seconds, not minutes.

Note on spot

Spot × fallback composition (run the chain for spot, then on-demand) lands with #39; the capacity codes and chain structure are already spot-ready. This PR is complete for on-demand.

Compatibility

Single-value ec2-instance-type/subnet-id inputs collapse to a one-cell chain — byte-identical to today.

…nstance types

A single subnet-id + ec2-instance-type is a single point of failure: when that
AZ has no capacity for that type, RunInstances fails and the whole workflow with
it. Both inputs now accept comma-separated ordered fallback lists (single values
behave byte-identically):

  ec2-instance-type: c7i.4xlarge,c6i.4xlarge,m7i.4xlarge
  subnet-id: subnet-aaa,subnet-bbb   # different AZs

- launchWithFallback walks type × subnet in order — every subnet/AZ is tried
  before downgrading the instance type (placement is cheaper than a hardware
  change). Capacity errors advance the chain; non-capacity errors (invalid AMI,
  auth, quota InstanceLimitExceeded) abort immediately so a misconfig doesn't
  burn the whole matrix; transient API errors retry within the cell.
- classifyRunError buckets RunInstances errors into capacity / transient / fatal
  (spot capacity codes already included for #39).
- retry.js withRetry gains an optional shouldRetry predicate (default unchanged),
  so a cell retries only transient errors.
- New outputs instance-type-used / subnet-id-used report what actually launched.
- Each failed placement logs a warn line (type, subnet, code); exhaustion fails
  with a summary of every attempt.

Tests: classifyRunError buckets, launchWithFallback ordering (first-try, mid-
chain, full-matrix exhaustion, fatal-abort, quota message), parseCsv, and
withRetry shouldRetry (142 tests total). README "Capacity fallback" section +
inputs/outputs tables and action.yml updated.

Note: spot × fallback composition lands with #39 (spot support); the capacity
codes and chain are already spot-ready.

Closes #40

Signed-off-by: kurok <22548029+kurok@users.noreply.github.com>
@kurok kurok merged commit f520d09 into main Jul 2, 2026
5 checks passed
@kurok kurok deleted the feat/capacity-fallback branch July 2, 2026 18:56
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.

Capacity resilience: ordered fallback across subnets/AZs and instance types

1 participant