Skip to content

fix: make scaling_decisions cumulative via Redis sorted set history#24

Merged
sylvesterdamgaard merged 11 commits into
mainfrom
fix-cumulative-decisions
May 6, 2026
Merged

fix: make scaling_decisions cumulative via Redis sorted set history#24
sylvesterdamgaard merged 11 commits into
mainfrom
fix-cumulative-decisions

Conversation

@sylvesterdamgaard
Copy link
Copy Markdown
Contributor

Summary

Resolves #23scaling_decisions[] in the cluster summary was per-cycle (rebuilt every ~5s evaluation tick, lost when activity stops). Dashboard cards like "Total Decisions" showed 0 on idle clusters that had been scaling for hours.

  • Persist decisions to Redis sorted set — Every non-hold scaling decision is zadd'd to queue-autoscale:cluster:{app-id}:decisions:history with microtime(true) as score
  • Dual-bound pruning — Time-based (zremrangebyscore, default 1 hour) + count-based (zremrangebyrank, default 10k entries) prevents unbounded growth
  • Atomic writes — Record + prune uses a single Lua eval to minimize Redis round-trips
  • Rolling history in summarybuildClusterSummary now reads from recentDecisions() instead of the ephemeral per-cycle array
  • Configurabledecision_history_seconds and decision_history_max config keys with env var support

No API break. scaling_decisions[] keeps the same structure, just gains a recorded_at float and historical entries. queue-monitor dashboard cards will populate immediately on upgrade — no consumer-side changes needed.

Test plan

  • 10 new unit tests for ClusterStore::recordDecision() / recentDecisions() (record, retrieve, empty state, timestamp, accumulation, time pruning, count pruning, key pattern, score range, malformed JSON, Lua atomicity)
  • 1 integration test verifying historical decisions flow through buildClusterSummary
  • Full suite: 560 tests, 1667 assertions passing
  • PHPStan clean
  • Pint clean

🤖 Generated with Claude Code

sylvesterdamgaard and others added 11 commits April 30, 2026 15:47
…ision_history_max)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ecentDecisions)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…luster summary

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SystemMetrics::limits()->availableCpuCores() returns int, not float.
Correcting the property type eliminates a PHPStan property.unusedType error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Batch zadd, zremrangebyscore, and zremrangebyrank into a single eval
round-trip to reduce Redis latency per decision recording.
CPU cores are fractional (0.5, 1.8, etc.) — the ?float type is
correct. Add property.unusedType to the PHPStan baseline instead.
@sylvesterdamgaard sylvesterdamgaard merged commit dfde45a into main May 6, 2026
19 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.

scaling_decisions log appears windowed, not cumulative — dashboard 'Total Decisions' card cannot accumulate

1 participant