Custom datanode backoff monitor - #77
anthonyjdam wants to merge 10 commits into
Conversation
|
Went through the PR with AI to get a high level, and some of the comments stuck out as good ideas to me. Overall I think this is the right direction I have some experience building similar systems to this, like HBase's throttling system, and my biggest advice is that getting the throttling algorithm tuned is going to be pretty hard, so this is probably particularly impactful feedback. Likewise, visibility will be quite important as you try to tune the algorithm (metrics feedback). For some inspiration, you can check out this work that I did on HBase to improve its ability to estimate the cost of a scan, and inform its scan throttling system: apache/hbase#5713. Happy to answer any questions about how we landed on the approach there. |
|
I've added some changes as per the suggestions. Let me know if you disagree or agree with anything, thanks |
Bring the 3.3.6 adaptive decommission monitor in line with custom-datanode-backoff-monitor-3.4: - Skip adaptation on the standby NameNode. run() now gates on blockManager.isPopulatingReplQueues(), so the controller and its metrics/logs are active only on the NN that actually decommissions (the monitor thread starts on both via startCommonServices). - Normalize all tuning knobs under the dfs.namenode.decommission.backoff.monitor.adaptive.* namespace, so they are consistent with adaptive.enabled (only the enable flag previously carried the "adaptive" segment). Updates hdfs-default.xml and the TestDFSAdmin sorted reconfigurable-property assertion. - Log the pacing decision only when the effective limit actually changes; drop the per-tick smoothing diagnostic. Full per-tick state stays on the NameNodeActivity metrics. Ports 403053b and 19b71b8b0f3 from the 3.4 branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
After discussion, namenode stability is good right now and shipping this new monitor would incur a lot of effort and maintenance during future upgrades of Hadoop. Putting this on the back burner for now and marking as draft. |
Summary
Add a custom DatanodeAdminAdaptiveBackoffMonitor that will stop datanode decommissioning whenever namenode RPC queue length and avg processing time are unhealthy, and when under-replicated block count is high.
Requesting review on:
Testing
Context for reviewer agents
Intent: Give HDFS decommission a load-aware pace on our HBase clusters. The pluggable NameNode decommission monitor (
dfs.namenode.decommission.monitor.class) ships upstream as eitherDatanodeAdminDefaultMonitor(fixed blocks-per-interval, holds the FS write lock the whole tick) orDatanodeAdminBackoffMonitor(HDFS-14854, paces off its own in-flightpendingRepqueue up to a fixedpendingRepLimit, default 10000). Neither reacts to NameNode health, so decommission-driven replication competes with foreground traffic when the NN is busy and runs needlessly slowly when it's idle. This PR adds a third monitor,DatanodeAdminAdaptiveBackoffMonitor, which makes that pending limit dynamic via a closed-loop feedback controller driven by a smoothed NameNode-load signal (average RPC queue time). It ships dark behind a flag (default off) — identical to the stock backoff monitor until enabled — and every knob is runtime-reconfigurable. Implements https://github.com/HubSpotEngineering/HBasePlanning/issues/2591. Base branchhubspot-3.3.6.Changed files (11, +1417/−6):
ipc/metrics/RpcMetrics.java(+17) — expose the rolling RPC queue-time mean (hadoop-common).hdfs/DFSConfigKeys.java(+65) — the config keys + defaults.blockmanagement/DatanodeAdminAdaptiveBackoffMonitor.java(+442, new) — the monitor / controller.blockmanagement/DatanodeAdminManager.java(+156) — reconfiguration refresh/get methods.namenode/FSNamesystem.java(+45) — RPC load-signal accessors.namenode/NameNode.java(+127/−6) — RPC-server injection + reconfig wiring.resources/hdfs-default.xml(+134) — docs for every new key.test/.../blockmanagement/TestDatanodeAdminAdaptiveBackoffMonitor.java(+246, new) — pure-unit tests.test/.../hdfs/TestDecommissionWithAdaptiveBackoffMonitor.java(+55, new) — MiniDFSCluster e2e.test/.../namenode/TestNameNodeReconfigure.java(+105) — runtime-reconfig tests.test/.../tools/TestDFSAdmin.java(+26/−5) — updates the reconfigurable-property assertion.Diff shape:
RpcMetrics— addsgetQueueMean()/getQueueSampleCount()(rpcQueueTime.lastStat().mean()/.numSamples()), mirroring the pre-existinggetProcessingMean()/getProcessingSampleCount(). TherpcQueueTimeMutableRatepreviously had no public getter.FSNamesystem— aprivate volatile Server clientRpcServer(set fromNameNode.initialize()),setClientRpcServer(Server), and two null-safe readers:getAvgRpcQueueTimeMs()(→getQueueMean(), the primary signal) andgetAvgRpcProcessingTimeMs()(→getProcessingMean(), the optional processing-time override). Both return-1when the server is unwired or no samples exist in the interval.DatanodeAdminAdaptiveBackoffMonitor—extends DatanodeAdminBackoffMonitor; reuses all of the parent's tracking/backoff machinery and only changes howpendingRepLimitis chosen. OverridesprocessConf()(reads the keys,validateAndFixup(), derives the EWMA alpha) andrun()(if enabled:setPendingRepLimit(computeAdaptivePendingLimit()), thensuper.run()). The control math is factored into two pure,@VisibleForTestinghelpers —nextControllerLimit(current, smoothedSignal, forceMin)andsmoothSignal(prevEma, sample)— so it's testable without the lock-holdingrun().NameNode— injectsnamesystem.setClientRpcServer(rpcServer.getClientRpcServer())ininitialize(); adds the keys to thereconfigurablePropertiesset; adds anelse ifinreconfigurePropertyImpl→ newreconfigureDecommissionAdaptiveMonitorParameters(...)handler that parses/validates each key and dispatches toDatanodeAdminManager.DatanodeAdminManager— arequireHubSpotMonitor(key)guard (instanceof + cast) andrefresh*/get*per knob; addsensurePositiveLong/ensureNonNegativeLonghelpers (int knobs reuseensurePositiveInt; the two override knobs use the existingensureDisabledOrPositive).hdfs-default.xml— one<property>per new key (required byTestHdfsConfigFields).run()wiring;TestDecommissionWithAdaptiveBackoffMonitorruns the fullTestDecommissionsuite with the monitor enabled;TestNameNodeReconfigurecovers every knob + rejection when a non-adaptive monitor is active;TestDFSAdminre-sorts the reconfigurable-property assertion (count 28 → 30).How the controller works (per tick, only when enabled):
signal = FSNamesystem.getAvgRpcQueueTimeMs(). If< 0(RPC metrics not available yet), fail open — return the current limit without advancing controller state.pendingRepLimiton the first tick (smooth enable, no step change).smoothed = smoothSignal(prevEma, signal)— optional cross-tick EWMA,alpha = tick / (window + tick).nextControllerLimit:smoothed <= healthy→ ramp up byramp.up.step(capped atmax);smoothed >= busy→ ramp down byramp.down.step(floored atmin);busy.rpc.processing.time.ms,max.low.redundancy.blocks) force the floor immediately.Config keys added (all under
dfs.namenode.decommission.backoff.monitor., all reconfigurable at runtime):adaptive.enabledfalsemin.pending.limit100max.pending.limitpending.limit(itself10000)pending.limit, not a constanthealthy.rpc.queue.time.ms1busy.rpc.queue.time.ms50ramp.up.step500ramp.down.step2000signal.ema.window.ms00uses the RPC-metrics windowed mean directlybusy.rpc.processing.time.ms-1-1disablesmax.low.redundancy.blocks-1-1disablesDevelopment notes:
pendingRepqueue, scheduling intoneededReconstruction,blocksPerLocklock cadence, and node tracking/completion are 100% the parentDatanodeAdminBackoffMonitor's code, untouched. The only behavioral change is thatpendingRepLimitis recomputed each tick. Reviewers should not expect changes to the block-scheduling loop.FeedbackAdaptiveRateLimiter(and Janert's Feedback Control for Computer Systems): the limit is a control variable nudged each tick, not recomputed from a curve. The deadband betweenhealthyandbusyis the hysteresis and the small per-tick steps are the output-side smoothing, so adjacent ticks can never snap between floor and ceiling (which the earlier threshold-interpolation design did at both cliff edges). Ramp-down > ramp-up by default (fast to yield, slow to re-expand; AIMD).getLowRedundancyBlocksCount()rises because the monitor schedules its own decommission work, so it would create a self-reinforcing loop. Average RPC queue time (how long calls wait before a handler picks them up) measures genuine foreground contention, is not inflated by our own scheduling, normalizes for service rate, and — critically — is a windowed mean already maintained by the RPC metrics system, so it survives the coarse (30s default) monitor tick where an instantaneousServer.getCallQueueLen()sample would just alias.getQueueMean()is the mean over the RPC metrics collection interval (configurable, commonly ~10s), which is shorter than the tick — so we read a rolling mean every tick rather than integrating the full inter-tick period; residual aliasing is greatly reduced, not zero. The in-monitor cross-tick EWMA (signal.ema.window.ms) exists to close that gap but is off by default (the metric is already a mean).DecayRpcScheduler's decayed averages were considered as an alternative smoothed source but not used, since they only exist when FairCallQueue is enabled; queue-time mean is always available.-1signal leaves the limit untouched;run()wraps the computation in a broadcatch (Exception)(matching the parentrun()) so adaptation can never break the decommission loop.validateAndFixup()clamps bad config at startup (min ≥ 1, max ≥ min, positive steps) and disables adaptation ifbusy <= healthy.pending.limit.max.pending.limitdefaults to the parent's resolvedpending.limit(not a constant), and the reconfig reset path mirrors this viagetConf(), so enabling adaptation never silently caps peak throughput below what the stock monitor already did.busy > healthy,max >= min) are enforced only at startup byvalidateAndFixup();-reconfigsets individual knobs and does not re-run it. Per-knob positivity is validated. A degenerate reconfigured pair is not dangerous (the controller mostly holds/ramps one way) but is worth tightening in a follow-up.blockManager); the only new wiring is the RPC-server reference intoFSNamesystem, which avoids adding anything toDatanodeAdminMonitorInterface. The(FSNamesystem) namesystemcast is safe becauseDatanodeAdminManageris always constructed with the concreteFSNamesystem.