java.lang.Object
net.i2p.router.JobQueueScaler
- All Implemented Interfaces:
Runnable
Dynamic job runner scaling controller for the JobQueue.
Monitors queue metrics (ready job count, lag) and automatically scales the number of JobQueueRunner threads up or down based on load conditions.
Scaling Algorithm:
- Scale UP when: readyJobs > activeRunners * scaleUpJobsRatio OR maxLag > scaleUpLagThreshold
- Scale DOWN when: readyJobs < activeRunners AND avgLag < scaleDownLagThreshold (sustained)
- Bounds: min runners = max(4, cores), max runners = min(2 *
router.maxJobRunners, RAM-based limit)
Resource Saturation Gates: Scale-up is suppressed (not just limited) when the bottleneck is resource saturation rather than thread count. Emergency signals (critical lag, wedged jobs, dropped jobs) override the suppression.
- CPU saturation:
router.cpuLoad> 85% - GC storm:
router.gcPauseTime> 50 ms/min - Network congestion:
tunnel.buildSuccessRate< 50% - Bandwidth saturation: bwLimiter.outboundDelayedTime > 500ms
Feedback Mechanism:
- Captures pre-scale metrics before adding runners
- Compares post-scale metrics after 2 check intervals
- If lag, maxLag, message delay, or ready jobs INCREASED after scaling, rolls back
- After 5 failed scale attempts, circuit breaker opens — stops scaling up
- Tracks memory usage to prevent OOM conditions
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate classAuto-release timer event — safety net for stranded requests.private static classSnapshot of metrics taken before scaling up.Field Summary
FieldsModifier and TypeFieldDescriptionprivate doubleBacklog ratio needed to scale up.private StringConfig string the cached ratio was parsed from, kept to avoid re-parsing on the hot path.private intChecks run since the last scale event.private intRunner limit from the config.private intScale-ups in a row that did not help.private intChecks in a row that wanted to scale down.private intChecks in a row that wanted to scale up.private final RouterContextThe router context.private intRunner limit in effect right now.private final AtomicIntegerRunner floor requested by subsystems.private final ConcurrentHashMap<String, int[]> Runner requests by subsystem name, with expiry times.private booleanFalse once the scaler has shut down.private booleanTrue while the failure cooldown runs.private booleanTrue while the scaler job is scheduled.private final JobQueueThe queue whose runners are scaled.private longWhen the queue last recovered from an overload.private longWhen the last scale event happened.private longWhen the current overload episode began.private final LogLogger for scaling decisions.private long[]Recent message delay samples for trend tracking.private intSamples held in the delay history.private intWrite index into the delay history.private intTotal checks run, never reset by scale events.private JobQueueScaler.PreScaleSnapshotState captured before the last scale, for feedback.private longMessage delay seen at the previous check.private intReady jobs seen at the previous check.private booleanCircuit breaker set after repeated failures.private static final doubleDelay over the baseline median that triggers action.private static final intCPU percent above which scale-up is skipped.private static final longThe default interval between checks is 2 seconds.private static final longThe default wait between scale events is 10 seconds.private static final intThe default lag low enough to scale down is 5 ms.private static final intRunners removed per scale-down, faster than we grow.private static final doubleDefault backlog ratio needed to scale up.private static final intThe default lag that justifies scaling up is 10 ms.private static final intThe default message delay that justifies scaling up is 200 ms.private static final intRunners added per scale-up.private static final longCooldown multiplier after a failed scale.private static final intChecks after a scale before judging its result.private static final longGC pause ms per minute above which scale-up is skipped.private static final doubleLag growth tolerated after a scale before rollback.private static final intFailed scale-ups before the circuit breaker opens.private static final doubleShare of the heap allowed for runner threads.private static final longOne megabyte, for the memory limits.private static final intDelay samples kept, a 12 second window at 2s checks.private static final StringConfig key turning dynamic scaling on or off.private static final StringConfig key turning the post-scale feedback check on or off.private static final StringConfig key for the minimum runner count.private static final StringConfig key for the interval between checks.private static final StringConfig key for the wait between scale events.private static final StringConfig key for the lag low enough to scale down.private static final StringConfig key for the backlog ratio needed to scale up.private static final StringConfig key for the lag that justifies scaling up.private static final StringConfig key for the message delay that justifies scaling up.private static final intJobs added per check that count as backlog growth.private static final doubleBacklog growth tolerated after a scale before rollback.private static final longA runner thread is estimated at 2 MB of heap.private static final intChecks that must want a scale-up before acting.private static final intChecks that must want a scale-down before acting.Constructor Summary
ConstructorsConstructorDescriptionJobQueueScaler(RouterContext context, JobQueue jobQueue) Create a new JobQueueScaler.Method Summary
Modifier and TypeMethodDescriptionprivate intcalculateMaxRunnersBasedOnRAM(int configuredMax) Calculate max runners based on available RAM.private voidCheck current conditions and scale if necessary.private voidcheckScaleDown(int activeRunners, int readyJobs, long maxLag, long avgLag, int minRunners, boolean inCooldown) Check if we should scale down.private longCompute the median of the message delay history ring buffer.private voidevaluateScalingFeedback(int currentRunners, int currentReadyJobs, long currentMaxLag, long currentAvgLag, long currentMessageDelay, boolean currentDroppedJobs) Evaluate whether the last scale-up actually helped.private longCheck interval in milliseconds.private longCooldown period between scale events.intCurrent maximum runner limit (may be up to 2x configured or RAM-limited).private intEffective minimum runners including external requests.private doubleMemory usage percentage.(package private) intMinimum number of runners (floor), dynamic - reads property each time.private intLag threshold for scaling down.private doubleJobs-to-runners ratio threshold for scaling up.private intLag threshold for scaling up.private intMessage delay threshold for scaling up.private longCurrent used memory in bytes.booleanisAlive()Check if the scaler is currently running.private booleanCheck if dynamic scaling is enabled.private booleanCheck if feedback mechanism is enabled.private doubleRead the 60s rolling average of tunnel build success rate (0-100 scale).private voidRecalculate the external floor from all outstanding requests.private voidRecalculate max runners based on current memory conditions.voidreleaseRunners(int count, String reason) Release previously requested runners.voidrequestRunners(int count, String reason, long autoReleaseMs) Request additional runners for an anticipated burst.voidrun()Main scaling loop.private voidscaleDown(int count, int readyJobs, long maxLag) Scale down by removing runners.private voidscaleUp(int count, int readyJobs, long maxLag, long avgLag) Scale up by adding runners, and snapshot the current queue metrics so the feedback check can judge whether the change helped.voidshutdown()Shutdown the scaler gracefully.voidstartup()Start the scaler thread.voidupdateMaxRunners(int configuredMax) Update the maximum runner limit (called when configuration changes).
Field Details
_log
Logger for scaling decisions._jobQueue
The queue whose runners are scaled._context
The router context._isRunning
private volatile boolean _isRunningTrue while the scaler job is scheduled._isAlive
private volatile boolean _isAliveFalse once the scaler has shut down._consecutiveScaleUpChecks
private int _consecutiveScaleUpChecksChecks in a row that wanted to scale up._consecutiveScaleDownChecks
private int _consecutiveScaleDownChecksChecks in a row that wanted to scale down._lastScaleTime
private long _lastScaleTimeWhen the last scale event happened._configuredMaxRunners
private int _configuredMaxRunnersRunner limit from the config._currentMaxRunners
private int _currentMaxRunnersRunner limit in effect right now._preScaleSnapshot
State captured before the last scale, for feedback._checksSinceLastScale
private int _checksSinceLastScaleChecks run since the last scale event._periodicCheckCount
private int _periodicCheckCountTotal checks run, never reset by scale events._consecutiveFailedScaleUps
private int _consecutiveFailedScaleUpsScale-ups in a row that did not help._isInExtendedCooldown
private boolean _isInExtendedCooldownTrue while the failure cooldown runs._scalingUpDisabled
private boolean _scalingUpDisabledCircuit breaker set after repeated failures._cachedScaleJobsRatioProp
Config string the cached ratio was parsed from, kept to avoid re-parsing on the hot path._cachedScaleJobsRatio
private double _cachedScaleJobsRatioBacklog ratio needed to scale up._previousReadyJobs
private int _previousReadyJobsReady jobs seen at the previous check._previousMessageDelay
private long _previousMessageDelayMessage delay seen at the previous check._messageDelayHistory
private long[] _messageDelayHistoryRecent message delay samples for trend tracking._msgDelayHistoryIndex
private int _msgDelayHistoryIndexWrite index into the delay history._msgDelayHistoryCount
private int _msgDelayHistoryCountSamples held in the delay history._externalFloor
Runner floor requested by subsystems._externalRequests
Runner requests by subsystem name, with expiry times._loadEpisodeStart
private long _loadEpisodeStartWhen the current overload episode began._lastRecoveryTime
private long _lastRecoveryTimeWhen the queue last recovered from an overload.FEEDBACK_CHECKS_AFTER_SCALE
private static final int FEEDBACK_CHECKS_AFTER_SCALEChecks after a scale before judging its result.- See Also:
MAX_CONSECUTIVE_FAILED_SCALES
private static final int MAX_CONSECUTIVE_FAILED_SCALESFailed scale-ups before the circuit breaker opens.- See Also:
EXTENDED_COOLDOWN_MULTIPLIER
private static final long EXTENDED_COOLDOWN_MULTIPLIERCooldown multiplier after a failed scale.- See Also:
LAG_INCREASE_THRESHOLD
private static final double LAG_INCREASE_THRESHOLDLag growth tolerated after a scale before rollback.- See Also:
READY_JOBS_INCREASE_THRESHOLD
private static final double READY_JOBS_INCREASE_THRESHOLDBacklog growth tolerated after a scale before rollback.- See Also:
MSG_DELAY_HISTORY_SIZE
private static final int MSG_DELAY_HISTORY_SIZEDelay samples kept, a 12 second window at 2s checks.- See Also:
BASELINE_MULTIPLIER
private static final double BASELINE_MULTIPLIERDelay over the baseline median that triggers action.- See Also:
QUEUE_GROWTH_RATE_THRESHOLD
private static final int QUEUE_GROWTH_RATE_THRESHOLDJobs added per check that count as backlog growth.- See Also:
MB
RUNNER_MEMORY_ESTIMATE
private static final long RUNNER_MEMORY_ESTIMATEA runner thread is estimated at 2 MB of heap.- See Also:
MAX_MEMORY_PERCENTAGE
private static final double MAX_MEMORY_PERCENTAGEShare of the heap allowed for runner threads.- See Also:
DEFAULT_SCALE_CHECK_INTERVAL
private static final long DEFAULT_SCALE_CHECK_INTERVALThe default interval between checks is 2 seconds.- See Also:
DEFAULT_SCALE_COOLDOWN
private static final long DEFAULT_SCALE_COOLDOWNThe default wait between scale events is 10 seconds.- See Also:
DEFAULT_SCALE_UP_LAG_THRESHOLD
private static final int DEFAULT_SCALE_UP_LAG_THRESHOLDThe default lag that justifies scaling up is 10 ms.- See Also:
DEFAULT_SCALE_UP_MESSAGE_DELAY_THRESHOLD
private static final int DEFAULT_SCALE_UP_MESSAGE_DELAY_THRESHOLDThe default message delay that justifies scaling up is 200 ms.- See Also:
DEFAULT_SCALE_DOWN_LAG_THRESHOLD
private static final int DEFAULT_SCALE_DOWN_LAG_THRESHOLDThe default lag low enough to scale down is 5 ms.- See Also:
DEFAULT_SCALE_UP_JOBS_RATIO
private static final double DEFAULT_SCALE_UP_JOBS_RATIODefault backlog ratio needed to scale up.- See Also:
DEFAULT_SCALE_UP_STEP
private static final int DEFAULT_SCALE_UP_STEPRunners added per scale-up.- See Also:
DEFAULT_SCALE_DOWN_STEP
private static final int DEFAULT_SCALE_DOWN_STEPRunners removed per scale-down, faster than we grow.- See Also:
SUSTAINED_CHECKS_REQUIRED
private static final int SUSTAINED_CHECKS_REQUIREDChecks that must want a scale-up before acting.- See Also:
SUSTAINED_CHECKS_REQUIRED_DOWN
private static final int SUSTAINED_CHECKS_REQUIRED_DOWNChecks that must want a scale-down before acting.- See Also:
PROP_DYNAMIC_SCALING
Config key turning dynamic scaling on or off.- See Also:
PROP_SCALE_UP_LAG
Config key for the lag that justifies scaling up.- See Also:
PROP_SCALE_DOWN_LAG
Config key for the lag low enough to scale down.- See Also:
PROP_SCALE_JOBS_RATIO
Config key for the backlog ratio needed to scale up.- See Also:
PROP_SCALE_CHECK_INTERVAL
Config key for the interval between checks.- See Also:
PROP_SCALE_COOLDOWN
Config key for the wait between scale events.- See Also:
PROP_SCALE_UP_MSG_DELAY
Config key for the message delay that justifies scaling up.- See Also:
PROP_MIN_RUNNERS
Config key for the minimum runner count.- See Also:
PROP_FEEDBACK_ENABLED
Config key turning the post-scale feedback check on or off.- See Also:
CPU_SATURATION_THRESHOLD
private static final int CPU_SATURATION_THRESHOLDCPU percent above which scale-up is skipped.- See Also:
GC_STORM_THRESHOLD
private static final long GC_STORM_THRESHOLDGC pause ms per minute above which scale-up is skipped.- See Also:
Constructor Details
JobQueueScaler
Create a new JobQueueScaler.- Parameters:
context- the router contextjobQueue- the job queue to scale
Method Details
calculateMaxRunnersBasedOnRAM
private int calculateMaxRunnersBasedOnRAM(int configuredMax) Calculate max runners based on available RAM. Prevents OOM by limiting runners to use max 10% of heap.- Parameters:
configuredMax- the configured maximum from properties- Returns:
- the lower of configuredMax*2 or RAM-based limit
recalculateMaxRunners
private void recalculateMaxRunners()Recalculate max runners based on current memory conditions. Called periodically to adapt to changing memory pressure.getUsedMemory
private long getUsedMemory()Current used memory in bytes.- Returns:
- the used memory
getMemoryUsagePercent
private double getMemoryUsagePercent()Memory usage percentage.- Returns:
- the memory usage percent
computeMedianBaseline
private long computeMedianBaseline()Compute the median of the message delay history ring buffer.- Returns:
- the median of the recorded message delays in ms, or 0 when no samples have been recorded yet
readBuildSuccessRate
private double readBuildSuccessRate()Read the 60s rolling average of tunnel build success rate (0-100 scale). NaN if no data available.- Returns:
- the average tunnel build success percentage for the last minute, or NaN when the rate or its 1-minute rate has no data
startup
public void startup()Start the scaler thread.shutdown
public void shutdown()Shutdown the scaler gracefully.isDynamicScalingEnabled
private boolean isDynamicScalingEnabled()Check if dynamic scaling is enabled.- Returns:
- whether dynamic scaling enabled
isFeedbackEnabled
private boolean isFeedbackEnabled()Check if feedback mechanism is enabled.- Returns:
- whether feedback enabled
getMinRunnersDynamic
int getMinRunnersDynamic()Minimum number of runners (floor), dynamic - reads property each time. On router startup, returns more runners to handle startup load.- Returns:
- the configured minimum, floored at 1, raised to 8 in the first 3 minutes
getCheckInterval
private long getCheckInterval()Check interval in milliseconds.- Returns:
- the check interval
getCooldownPeriod
private long getCooldownPeriod()Cooldown period between scale events.- Returns:
- the cooldown period
getScaleUpLagThreshold
private int getScaleUpLagThreshold()Lag threshold for scaling up.- Returns:
- the scale up lag threshold
getScaleUpMessageDelayThreshold
private int getScaleUpMessageDelayThreshold()Message delay threshold for scaling up.- Returns:
- the scale up message delay threshold
getScaleDownLagThreshold
private int getScaleDownLagThreshold()Lag threshold for scaling down.- Returns:
- the scale down lag threshold
getScaleUpJobsRatio
private double getScaleUpJobsRatio()Jobs-to-runners ratio threshold for scaling up.- Returns:
- the scale up jobs ratio
run
checkAndScale
private void checkAndScale()Check current conditions and scale if necessary.checkScaleDown
private void checkScaleDown(int activeRunners, int readyJobs, long maxLag, long avgLag, int minRunners, boolean inCooldown) Check if we should scale down.- Parameters:
activeRunners- the current number of JobQueueRunner threadsreadyJobs- jobs ready to run now, used for the job-to-runner ratiomaxLag- ms the oldest ready job has waited, logged with the actionavgLag- ms average lag of the ready jobs, compared with the scale-down lag thresholdminRunners- the floor the runner count may not be reduced belowinCooldown- true when a scale action happened recently enough to suppress another one
evaluateScalingFeedback
private void evaluateScalingFeedback(int currentRunners, int currentReadyJobs, long currentMaxLag, long currentAvgLag, long currentMessageDelay, boolean currentDroppedJobs) Evaluate whether the last scale-up actually helped. Checks multiple signals: avg lag, max lag, ready jobs, message delay, and dropped jobs. If any signal worsened beyond threshold, rolls back.- Parameters:
currentRunners- runner count now, caps how many added runners roll backcurrentReadyJobs- ready jobs now, compared with the pre-scale snapshotcurrentMaxLag- ms, worst ready-job lag now, compared with the snapshotcurrentAvgLag- ms average lag now, compared with the pre-scale averagecurrentMessageDelay- ms message delay now, checked against pre-scale lagcurrentDroppedJobs- true when the queue has dropped jobs since the snapshot, which forces a rollback
scaleUp
private void scaleUp(int count, int readyJobs, long maxLag, long avgLag) Scale up by adding runners, and snapshot the current queue metrics so the feedback check can judge whether the change helped.- Parameters:
count- runners to add, already capped at the maximum by the callerreadyJobs- ready job count now, logged and stored in the snapshotmaxLag- ms of worst ready-job lag now, stored in the snapshotavgLag- ms of average lag now, the snapshot's lag baseline
scaleDown
private void scaleDown(int count, int readyJobs, long maxLag) Scale down by removing runners. The queue retires idle runners only, so fewer than requested may be removed.- Parameters:
count- runners to removereadyJobs- ready job count now, logged with the actionmaxLag- ms of worst ready-job lag now, logged with the action
requestRunners
Request additional runners for an anticipated burst. Called by subsystems (e.g., Tuner) that know demand is imminent. The floor prevents the scaler from reducing below active + requested until auto-decay or explicit release.- Parameters:
count- number of additional runners to reservereason- label for tracking and loggingautoReleaseMs- if > 0, auto-decay after this many ms
releaseRunners
Release previously requested runners. Idempotent — releasing more than requested just floors the per-reason count at 0.- Parameters:
count- number of runners to releasereason- must match the reason used in requestRunners
recalcExternalFloor
private void recalcExternalFloor()Recalculate the external floor from all outstanding requests.getEffectiveMinRunners
private int getEffectiveMinRunners()Effective minimum runners including external requests.- Returns:
- base min runners + external floor
isAlive
public boolean isAlive()Check if the scaler is currently running.- Returns:
- true between
startup()andshutdown(), and false when dynamic scaling is disabled, sostartup()may leave it false
getCurrentMaxRunners
public int getCurrentMaxRunners()Current maximum runner limit (may be up to 2x configured or RAM-limited).- Returns:
- the current max runners
updateMaxRunners
public void updateMaxRunners(int configuredMax) Update the maximum runner limit (called when configuration changes).- Parameters:
configuredMax- the new limit from configuration, before the RAM cap is applied