FrankChen021 commented on code in PR #19687:
URL: https://github.com/apache/druid/pull/19687#discussion_r3720545431
##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/supervisor/SupervisorManager.java:
##########
@@ -644,6 +648,90 @@ public boolean isAnotherTaskGroupPublishingToPartitions(
}
}
+ /**
+ * Simulates the effects of the {@code costBased} auto-scaler by computing
the optimal
+ * task count under various values of aggregate lag.
+ */
+ public Map<String, Object> simulateAutoscaling(
+ String supervisorId,
+ CostBasedAutoScalerConfig config,
+ int maxProcessingRatePerTask,
+ @Nullable Integer requestedTaskCount
+ )
+ {
+ // Validate that this is a SeekableStreamSupervisor
+ final Pair<Supervisor, SupervisorSpec> supervisorPair =
supervisors.get(supervisorId);
+ if (supervisorPair == null || supervisorPair.rhs == null ||
supervisorPair.lhs == null) {
+ throw NotFound.exception("Invalid supervisor[%s]", supervisorId);
+ } else if (!(supervisorPair.rhs instanceof SeekableStreamSupervisorSpec)) {
+ throw InvalidInput.exception(
+ "Cannot simulate autoscaling for supervisor[%s] of type[%s]",
+ supervisorId, supervisorPair.rhs.getType()
+ );
+ }
+
+ final SeekableStreamSupervisorSpec supervisorSpec =
(SeekableStreamSupervisorSpec) supervisorPair.rhs;
+
+ // Validate the inputs
+ final long criticalLag =
Configs.valueOrDefault(config.getCriticalLagThreshold(), 1_000_000);
+ InvalidInput.conditionalException(
+ criticalLag >= 1000,
+ "Value of critical lag[%d] must be 1000 or more",
+ criticalLag
+ );
+ InvalidInput.conditionalException(
+ maxProcessingRatePerTask >= 100,
+ "Value of maxProcessingRatePerTask[%d] must be 100 events per second
or more",
+ maxProcessingRatePerTask
+ );
+ InvalidInput.conditionalException(
+ requestedTaskCount == null
+ || (requestedTaskCount >= config.getTaskCountMin() &&
requestedTaskCount <= config.getTaskCountMax()),
+ "Value of currentTaskCount[%d] must be within taskCountMin[%d] and
taskCountMax[%d]",
+ requestedTaskCount, config.getTaskCountMin(), config.getTaskCountMax()
+ );
+
+ // Simulate from the supervisor's live task count unless the caller pins
one.
+ final int currentTaskCount = supervisorSpec.getIoConfig().getTaskCount();
+ final int simulationTaskCount = Math.max(
+ config.getTaskCountMin(),
+ Math.min(
+ Configs.valueOrDefault(requestedTaskCount, currentTaskCount),
+ config.getTaskCountMax()
+ )
+ );
+
+ // Use the partition count and task duration from the supervisor spec
+ final int partitionCount = ((SeekableStreamSupervisor<?, ?, ?>)
supervisorPair.lhs).getKnownPartitionCount();
+ final long taskDurationSeconds =
supervisorSpec.getIoConfig().getTaskDuration().getStandardSeconds();
+
+ // Assume that the tasks are fully used since there is some lag
+ final double idleRatio = config.getOptimalTaskIdleRatio();
+
+ // Invoke the cost function for lag in the range [0, 2 *
criticalLagThreshold)
+ final Object[] rows = new Object[200];
+ final int lagStepSize = (int) (criticalLag / 100);
+ final CostBasedAutoScaler autoscaleSimulator =
CostBasedAutoScaler.createSimulator(config, supervisorId);
+ for (int i = 0; i < 200; ++i) {
+ final double observedAggregateLag = (double) lagStepSize * i;
+ final CostMetrics costMetrics = new CostMetrics(
+ observedAggregateLag / partitionCount,
+ observedAggregateLag,
+ simulationTaskCount,
+ partitionCount,
+ idleRatio,
+ taskDurationSeconds,
+ maxProcessingRatePerTask,
+ maxProcessingRatePerTask * 1.0
+ );
+ final int optimalTaskCount =
autoscaleSimulator.computeOptimalTaskCountInternal(costMetrics, true);
Review Comment:
[P2] Suppress every simulator log path
Passing `true` suppresses the normal computation logs, but the high- and
critical-lag branches in `computeOptimalTaskCountInternal` still log
unconditionally. Each request evaluates 200 points; with the standard 0-to-2x
range, 125 of them cross the 0.75x high-lag threshold and emit INFO records.
Guard those branches with `!isSimulation` as well.
##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/supervisor/SupervisorManager.java:
##########
@@ -644,6 +648,90 @@ public boolean isAnotherTaskGroupPublishingToPartitions(
}
}
+ /**
+ * Simulates the effects of the {@code costBased} auto-scaler by computing
the optimal
+ * task count under various values of aggregate lag.
+ */
+ public Map<String, Object> simulateAutoscaling(
+ String supervisorId,
+ CostBasedAutoScalerConfig config,
+ int maxProcessingRatePerTask,
+ @Nullable Integer requestedTaskCount
+ )
+ {
+ // Validate that this is a SeekableStreamSupervisor
+ final Pair<Supervisor, SupervisorSpec> supervisorPair =
supervisors.get(supervisorId);
+ if (supervisorPair == null || supervisorPair.rhs == null ||
supervisorPair.lhs == null) {
+ throw NotFound.exception("Invalid supervisor[%s]", supervisorId);
+ } else if (!(supervisorPair.rhs instanceof SeekableStreamSupervisorSpec)) {
+ throw InvalidInput.exception(
+ "Cannot simulate autoscaling for supervisor[%s] of type[%s]",
+ supervisorId, supervisorPair.rhs.getType()
+ );
+ }
+
+ final SeekableStreamSupervisorSpec supervisorSpec =
(SeekableStreamSupervisorSpec) supervisorPair.rhs;
+
+ // Validate the inputs
+ final long criticalLag =
Configs.valueOrDefault(config.getCriticalLagThreshold(), 1_000_000);
+ InvalidInput.conditionalException(
+ criticalLag >= 1000,
+ "Value of critical lag[%d] must be 1000 or more",
+ criticalLag
+ );
+ InvalidInput.conditionalException(
+ maxProcessingRatePerTask >= 100,
+ "Value of maxProcessingRatePerTask[%d] must be 100 events per second
or more",
+ maxProcessingRatePerTask
+ );
+ InvalidInput.conditionalException(
Review Comment:
[P2] Reject non-positive task-count bounds
The simulation config accepts `taskCountMin=0`, and a positive live task
count then reaches `computeValidTaskCounts`, which divides by `taskCountMin`
and returns HTTP 500. Restore validation that both task-count bounds are at
least one before running the simulation.
##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/supervisor/SupervisorManager.java:
##########
@@ -644,6 +648,90 @@ public boolean isAnotherTaskGroupPublishingToPartitions(
}
}
+ /**
+ * Simulates the effects of the {@code costBased} auto-scaler by computing
the optimal
+ * task count under various values of aggregate lag.
+ */
+ public Map<String, Object> simulateAutoscaling(
+ String supervisorId,
+ CostBasedAutoScalerConfig config,
+ int maxProcessingRatePerTask,
+ @Nullable Integer requestedTaskCount
+ )
+ {
+ // Validate that this is a SeekableStreamSupervisor
+ final Pair<Supervisor, SupervisorSpec> supervisorPair =
supervisors.get(supervisorId);
+ if (supervisorPair == null || supervisorPair.rhs == null ||
supervisorPair.lhs == null) {
+ throw NotFound.exception("Invalid supervisor[%s]", supervisorId);
+ } else if (!(supervisorPair.rhs instanceof SeekableStreamSupervisorSpec)) {
+ throw InvalidInput.exception(
+ "Cannot simulate autoscaling for supervisor[%s] of type[%s]",
+ supervisorId, supervisorPair.rhs.getType()
+ );
+ }
+
+ final SeekableStreamSupervisorSpec supervisorSpec =
(SeekableStreamSupervisorSpec) supervisorPair.rhs;
+
+ // Validate the inputs
+ final long criticalLag =
Configs.valueOrDefault(config.getCriticalLagThreshold(), 1_000_000);
+ InvalidInput.conditionalException(
+ criticalLag >= 1000,
+ "Value of critical lag[%d] must be 1000 or more",
+ criticalLag
+ );
+ InvalidInput.conditionalException(
+ maxProcessingRatePerTask >= 100,
+ "Value of maxProcessingRatePerTask[%d] must be 100 events per second
or more",
+ maxProcessingRatePerTask
+ );
+ InvalidInput.conditionalException(
+ requestedTaskCount == null
+ || (requestedTaskCount >= config.getTaskCountMin() &&
requestedTaskCount <= config.getTaskCountMax()),
+ "Value of currentTaskCount[%d] must be within taskCountMin[%d] and
taskCountMax[%d]",
+ requestedTaskCount, config.getTaskCountMin(), config.getTaskCountMax()
+ );
+
+ // Simulate from the supervisor's live task count unless the caller pins
one.
+ final int currentTaskCount = supervisorSpec.getIoConfig().getTaskCount();
+ final int simulationTaskCount = Math.max(
+ config.getTaskCountMin(),
+ Math.min(
+ Configs.valueOrDefault(requestedTaskCount, currentTaskCount),
+ config.getTaskCountMax()
+ )
+ );
+
+ // Use the partition count and task duration from the supervisor spec
+ final int partitionCount = ((SeekableStreamSupervisor<?, ?, ?>)
supervisorPair.lhs).getKnownPartitionCount();
Review Comment:
[P2] Reject unavailable live partition topology
`getKnownPartitionCount()` is zero during startup and temporarily after
repartitioning clears assignments. In that state every simulated point returns
`CANNOT_COMPUTE` (`taskCount=-1`) in an otherwise successful response,
producing a bogus chart. Return a retryable or invalid-input error until a
positive live partition count is available.
##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/supervisor/SupervisorResource.java:
##########
@@ -534,6 +537,31 @@ public Response terminateAll(@Context final
HttpServletRequest req)
);
}
+ @POST
+ @Path("/{id}/autoscaler/simulate")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ResourceFilters(SupervisorResourceFilter.class)
Review Comment:
[P2] Authorize simulation as read-only
`SupervisorResourceFilter` maps every POST to `Action.WRITE`, so read-only
users who can inspect this supervisor receive 403 for a computation that
changes no state. The prior authorization finding remains unresolved; use an
explicit datasource READ authorization path for this endpoint.
##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/supervisor/SupervisorManager.java:
##########
@@ -644,6 +648,90 @@ public boolean isAnotherTaskGroupPublishingToPartitions(
}
}
+ /**
+ * Simulates the effects of the {@code costBased} auto-scaler by computing
the optimal
+ * task count under various values of aggregate lag.
+ */
+ public Map<String, Object> simulateAutoscaling(
+ String supervisorId,
+ CostBasedAutoScalerConfig config,
+ int maxProcessingRatePerTask,
+ @Nullable Integer requestedTaskCount
+ )
+ {
+ // Validate that this is a SeekableStreamSupervisor
+ final Pair<Supervisor, SupervisorSpec> supervisorPair =
supervisors.get(supervisorId);
+ if (supervisorPair == null || supervisorPair.rhs == null ||
supervisorPair.lhs == null) {
+ throw NotFound.exception("Invalid supervisor[%s]", supervisorId);
+ } else if (!(supervisorPair.rhs instanceof SeekableStreamSupervisorSpec)) {
+ throw InvalidInput.exception(
+ "Cannot simulate autoscaling for supervisor[%s] of type[%s]",
+ supervisorId, supervisorPair.rhs.getType()
+ );
+ }
+
+ final SeekableStreamSupervisorSpec supervisorSpec =
(SeekableStreamSupervisorSpec) supervisorPair.rhs;
+
+ // Validate the inputs
+ final long criticalLag =
Configs.valueOrDefault(config.getCriticalLagThreshold(), 1_000_000);
+ InvalidInput.conditionalException(
+ criticalLag >= 1000,
+ "Value of critical lag[%d] must be 1000 or more",
+ criticalLag
+ );
+ InvalidInput.conditionalException(
+ maxProcessingRatePerTask >= 100,
+ "Value of maxProcessingRatePerTask[%d] must be 100 events per second
or more",
+ maxProcessingRatePerTask
+ );
+ InvalidInput.conditionalException(
+ requestedTaskCount == null
+ || (requestedTaskCount >= config.getTaskCountMin() &&
requestedTaskCount <= config.getTaskCountMax()),
+ "Value of currentTaskCount[%d] must be within taskCountMin[%d] and
taskCountMax[%d]",
+ requestedTaskCount, config.getTaskCountMin(), config.getTaskCountMax()
+ );
+
+ // Simulate from the supervisor's live task count unless the caller pins
one.
+ final int currentTaskCount = supervisorSpec.getIoConfig().getTaskCount();
+ final int simulationTaskCount = Math.max(
+ config.getTaskCountMin(),
+ Math.min(
+ Configs.valueOrDefault(requestedTaskCount, currentTaskCount),
+ config.getTaskCountMax()
+ )
+ );
+
+ // Use the partition count and task duration from the supervisor spec
+ final int partitionCount = ((SeekableStreamSupervisor<?, ?, ?>)
supervisorPair.lhs).getKnownPartitionCount();
+ final long taskDurationSeconds =
supervisorSpec.getIoConfig().getTaskDuration().getStandardSeconds();
+
+ // Assume that the tasks are fully used since there is some lag
+ final double idleRatio = config.getOptimalTaskIdleRatio();
+
+ // Invoke the cost function for lag in the range [0, 2 *
criticalLagThreshold)
+ final Object[] rows = new Object[200];
+ final int lagStepSize = (int) (criticalLag / 100);
Review Comment:
[P3] Preserve the long critical-lag range
`criticalLagThreshold` is a `long`, but casting its step to `int` overflows
once the threshold exceeds 214,748,364,700. Valid larger thresholds therefore
produce wrapped negative or truncated lag points. Keep `lagStepSize` as `long`
or `double`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]