Class: Pgbus::Streams::PoolAutoscaler
- Inherits:
-
Object
- Object
- Pgbus::Streams::PoolAutoscaler
- Defined in:
- lib/pgbus/streams/pool_autoscaler.rb
Overview
Self-tuning decision logic for the dedicated streams DB pool (issue #323).
A PURE DECISION OBJECT — it owns no thread and no connection. Two callers drive it, both feeding it a live-headroom reading:
* the streamer's Listener, as a periodic maintenance check on its idle
LISTEN connection (see Maintenance below); and
* a publisher (Client#send_stream_message), throttled, on the job pool.
#evaluate reads the streams pool's busy ratio, decides, and resizes via the #325 hot-swap primitive.
Cadence is slow (streams_pool_autoscale_interval, default 5 min), so the interval itself is the debounce: it acts on each check with no multi-sample hysteresis and no cooldown. A sustained burst converges over a few checks (one grow step each); a swap's transient (a freshly-swapped connection_pool is lazy → reads busy ≈ 0) simply means "no shrink this check" and is gone by the next check minutes later.
Decision priority per check:
1. EMERGENCY SHRINK — DB free connections critically low → resize to
baseline now (protect the DB; overrides the busy signal).
2. GROW — pool saturated AND a fair share of real headroom exists.
3. SHRINK — pool idle → step toward baseline.
4. HOLD.
SAFETY (proven in the #323 design; independent of cadence):
* No multi-process exhaustion: four stacked grow guards (GROW_RESERVE
gate, SAFETY peer inflation, STEP_MAX, per-process floor(free/2)).
* No grow↔emergency limit cycle: GROW_RESERVE (0.20·maxc) ≥ 4×
EMERGENCY_MARGIN (0.05·maxc) → a ~15% dead-zone.
Defined Under Namespace
Classes: Maintenance
Constant Summary collapse
- GROW_THRESHOLD =
busy_ratio to grow
0.85- SHRINK_THRESHOLD =
busy_ratio to shrink
0.30- FAIR_FRACTION =
claim only ¼ of the computed fair share per grow
0.25- SAFETY =
inflate peer count → deflate fair share (undercount guard)
1.5- STEP_MAX =
hard cap on connections added per single grow
4- HEADROOM_SQL =
SQL a caller runs on a connection to gather headroom. Public so the Listener/publisher can issue it; $1 is the application_name LIKE pattern.
<<~SQL SELECT current_setting('max_connections')::int AS maxc, count(*) AS used, count(DISTINCT application_name) FILTER (WHERE application_name LIKE $1) AS peers FROM pg_stat_activity SQL
Instance Method Summary collapse
-
#evaluate(headroom, allow_shrink: true) ⇒ Object
One maintenance decision.
-
#initialize(client:, config:, logger: Pgbus.logger) ⇒ PoolAutoscaler
constructor
A new instance of PoolAutoscaler.
Constructor Details
#initialize(client:, config:, logger: Pgbus.logger) ⇒ PoolAutoscaler
Returns a new instance of PoolAutoscaler.
51 52 53 54 55 56 |
# File 'lib/pgbus/streams/pool_autoscaler.rb', line 51 def initialize(client:, config:, logger: Pgbus.logger) @client = client @config = config @logger = logger @baseline = config.streams_pool_size end |
Instance Method Details
#evaluate(headroom, allow_shrink: true) ⇒ Object
One maintenance decision. headroom is used:, peers: from a
caller's query, or nil if it failed (→ HOLD). Returns the action taken
(:emergency_shrink / :grow / :shrink / :hold) — handy for tests + logging.
allow_shrink: gates the NORMAL idle shrink (priority 3). The streamer
passes true (it sees the sustained idle picture across replay reads). A
publisher passes false: it only ever reacts to its OWN publish pressure
(grow), and leaves idle-shrink to the streamer/consumer. Emergency shrink
is NEVER gated — a DB running out of connections must always be relieved,
whoever notices first.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/pgbus/streams/pool_autoscaler.rb', line 68 def evaluate(headroom, allow_shrink: true) return :hold if headroom.nil? free = headroom[:maxc] - headroom[:used] # PRIORITY 1 — EMERGENCY SHRINK (keys off DB `free`, an external fact). return emergency_shrink(free, headroom[:maxc]) if free < emergency_margin(headroom[:maxc]) size, busy_ratio = pool_busy return :hold if size.nil? if busy_ratio >= GROW_THRESHOLD maybe_grow(size, free, headroom) elsif allow_shrink && busy_ratio < SHRINK_THRESHOLD maybe_shrink(size) else :hold end end |