Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/operations/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,12 @@ batch ingestion emit the following metrics. These metrics are deltas for each em
|`ingest/notices/time`|Milliseconds taken to process a notice by the supervisor.|`supervisorId`, `dataSource`, `tags`| < 1s |
|`ingest/pause/time`|Milliseconds spent by a task in a paused state without ingesting.|`dataSource`, `taskId`, `tags`| < 10 seconds|
|`ingest/handoff/time`|Total number of milliseconds taken to handoff a set of segments.|`dataSource`, `taskId`, `taskType`, `groupId`, `tags`|Depends on the coordinator cycle time.|
|`ingest/realtime/segmentUpgrade/count`|Number of pending segments that a concurrent replace (for example, compaction) upgraded to a new version and asked the supervisor to have running tasks announce under the new version. Emitted by the replace task only when streaming ingestion is running concurrently with replace on the same interval.|`dataSource`, `taskId`, `taskType`, `groupId`, `interval`, `version`, `tags`|0 unless [concurrent append and replace](../ingestion/concurrent-append-replace.md) is in use.|

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably rename this metric from count to persisted to signify that this is the count that was actually persisted to the metadata store.
Also, rephrased the text a bit.

Suggested change
|`ingest/realtime/segmentUpgrade/count`|Number of pending segments that a concurrent replace (for example, compaction) upgraded to a new version and asked the supervisor to have running tasks announce under the new version. Emitted by the replace task only when streaming ingestion is running concurrently with replace on the same interval.|`dataSource`, `taskId`, `taskType`, `groupId`, `interval`, `version`, `tags`|0 unless [concurrent append and replace](../ingestion/concurrent-append-replace.md) is in use.|
|`ingest/realtime/segmentUpgrade/persisted`|Number of pending segments that were upgraded in the metadata store while publishing segments to an interval. Emitted by the Overlord only when a REPLACE task (e.g., compaction task with `useConcurrentLocks` set to `true`) commits segments to an interval already containing pending segments allocated to an APPEND task (e.g. streaming task with `useConcurrentLocks` set to `true`). |`dataSource`, `taskId`, `taskType`, `groupId`, `interval`, `version`, `tags`|0 unless [concurrent append and replace](../ingestion/concurrent-append-replace.md) is in use.|

|`ingest/realtime/segmentUpgrade/notified`|Number of notifications the supervisor sent to running tasks, emitted once per task so it scales with the replica count. Reconcile against the per-task outcomes: `notified` should equal `announced` + `skipped` + `sendFailed` over the same period and `dataSource`, since every notified task either announces, skips, or fails to receive the request. A shortfall indicates a notification was silently dropped.|`supervisorId`, `dataSource`, `stream`, `taskId`, `interval`, `version`, `tags`|Equal to `announced` + `skipped` + `sendFailed`.|
|`ingest/realtime/segmentUpgrade/unmatched`|Number of upgraded pending segments the supervisor could not route to any running task. These are not announced under the new version until handoff, so the corresponding data may be briefly missing from queries.|`supervisorId`, `dataSource`, `stream`, `interval`, `version`, `tags`|0. A non-zero value indicates a lost upgrade announcement.|
|`ingest/realtime/segmentUpgrade/sendFailed`|Number of upgrade requests that matched a running task but failed to reach it over the wire after retries.|`supervisorId`, `dataSource`, `stream`, `taskId`, `interval`, `version`, `tags`|0|
|`ingest/realtime/segmentUpgrade/announced`|Number of upgraded segments a task announced under the new version. Emitted once per task, so it scales with the replica count. Reconcile against `notified` (also per task), not `ingest/realtime/segmentUpgrade/count`, which is per segment rather than per replica.|`dataSource`, `taskId`, `taskType`, `groupId`, `interval`, `version`, `tags`|Greater than 0 while concurrent replace occurs.|
|`ingest/realtime/segmentUpgrade/skipped`|Number of upgrade requests a task received but did not announce. The `reason` dimension is one of `unknown base` (the request reached the wrong task), `base sink already dropped` (the base sink is no longer present), or `dropping base sink` (the base sink is handing off, which is benign and covered by the durable publish path).|`dataSource`, `taskId`, `taskType`, `groupId`, `interval`, `version`, `tags`, `reason`|0, excluding `reason=dropping base sink`.|
|`task/autoScaler/requiredCount`|Count of required tasks based on the calculations of the auto scaler.|`supervisorId`, `dataSource`, `stream`, `scalingSkipReason`|Depends on auto scaler config.|
|`task/autoScaler/scaleActionTime`|Time taken in milliseconds to complete the scale action.|`supervisorId`, `dataSource`, `stream`, `tags`|Depends on auto scaler config.|
|`task/autoScaler/costBased/optimalTaskCount`|Optimal task count computed by the cost-based auto scaler.|`supervisorId`, `dataSource`, `stream`|Depends on auto scaler config.|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,60 @@
"type": "timer",
"timeUnit": "NANOSECONDS"
},
"ingest/realtime/segmentUpgrade/count": {
"dimensions": [
"dataSource",
"interval",
"version"
],
"type": "counter"
},
"ingest/realtime/segmentUpgrade/notified": {
"dimensions": [
"dataSource",
"stream",
"taskId",
"interval",
"version"
],
"type": "counter"
},
"ingest/realtime/segmentUpgrade/unmatched": {
"dimensions": [
"dataSource",
"stream",
"interval",
"version"
],
"type": "counter"
},
"ingest/realtime/segmentUpgrade/sendFailed": {
"dimensions": [
"dataSource",
"stream",
"taskId",
"interval",
"version"
],
"type": "counter"
},
"ingest/realtime/segmentUpgrade/announced": {
"dimensions": [
"dataSource",
"interval",
"version"
],
"type": "counter"
},
"ingest/realtime/segmentUpgrade/skipped": {
"dimensions": [
"dataSource",
"reason",
"interval",
"version"
],
"type": "counter"
},
"task/run/time": {
"dimensions": [
"dataSource",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,42 @@
"ingest/events/messageGap": [
"dataSource"
],
"ingest/realtime/segmentUpgrade/count": [
"dataSource",
"interval",
"version"
],
"ingest/realtime/segmentUpgrade/notified": [
"dataSource",
"stream",
"taskId",
"interval",
"version"
],
"ingest/realtime/segmentUpgrade/unmatched": [
"dataSource",
"stream",
"interval",
"version"
],
"ingest/realtime/segmentUpgrade/sendFailed": [
"dataSource",
"stream",
"taskId",
"interval",
"version"
],
"ingest/realtime/segmentUpgrade/announced": [
"dataSource",
"interval",
"version"
],
"ingest/realtime/segmentUpgrade/skipped": [
"dataSource",
"reason",
"interval",
"version"
],
"ingest/kafka/lag": [
"dataSource",
"stream"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@
"ingest/notices/time" : { "dimensions" : ["dataSource"], "type" : "timer", "conversionFactor": 1000.0, "help": "Seconds taken to process a notice by the supervisor." },
"ingest/pause/time" : { "dimensions" : ["dataSource"], "type" : "timer", "conversionFactor": 1000.0, "help": "Seconds spent by a task in a paused state without ingesting." },
"ingest/handoff/time" : { "dimensions" : ["dataSource"], "type" : "timer", "conversionFactor": 1000.0, "help": "Total number of seconds taken to handoff a set of segments." },
"ingest/realtime/segmentUpgrade/count" : { "dimensions" : ["dataSource", "interval", "version"], "type" : "count", "help": "Number of pending segments upgraded by a concurrent replace." },
"ingest/realtime/segmentUpgrade/notified" : { "dimensions" : ["dataSource", "stream", "taskId", "interval", "version"], "type" : "count", "help": "Number of notifications the supervisor sent to running tasks, one per task." },
"ingest/realtime/segmentUpgrade/unmatched" : { "dimensions" : ["dataSource", "stream", "interval", "version"], "type" : "count", "help": "Number of upgraded pending segments that matched no running task." },
"ingest/realtime/segmentUpgrade/sendFailed" : { "dimensions" : ["dataSource", "stream", "taskId", "interval", "version"], "type" : "count", "help": "Number of upgrade requests that failed to reach a task." },
"ingest/realtime/segmentUpgrade/announced" : { "dimensions" : ["dataSource", "interval", "version"], "type" : "count", "help": "Number of upgraded segments a task announced under the new version." },
"ingest/realtime/segmentUpgrade/skipped" : { "dimensions" : ["dataSource", "reason", "interval", "version"], "type" : "count", "help": "Number of upgrade requests a task received but did not announce." },
"task/autoScaler/requiredCount" : { "dimensions" : ["dataSource"], "type" : "count", "help": "Count of required tasks based on the calculations of lagBased auto scaler." },

"task/run/time" : { "dimensions" : ["dataSource", "taskType"], "type" : "timer", "conversionFactor": 1000.0, "help": "Seconds taken to run a task."},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@
"ingest/segments/count" : { "dimensions" : ["dataSource"], "type" : "count" },
"ingest/rows/published": { "dimensions" : ["dataSource"], "type" : "count" },

"ingest/realtime/segmentUpgrade/count" : { "dimensions" : ["dataSource", "interval", "version"], "type" : "count" },
"ingest/realtime/segmentUpgrade/notified" : { "dimensions" : ["dataSource", "stream", "taskId", "interval", "version"], "type" : "count" },
"ingest/realtime/segmentUpgrade/unmatched" : { "dimensions" : ["dataSource", "stream", "interval", "version"], "type" : "count" },
"ingest/realtime/segmentUpgrade/sendFailed" : { "dimensions" : ["dataSource", "stream", "taskId", "interval", "version"], "type" : "count" },
"ingest/realtime/segmentUpgrade/announced" : { "dimensions" : ["dataSource", "interval", "version"], "type" : "count" },
"ingest/realtime/segmentUpgrade/skipped" : { "dimensions" : ["dataSource", "reason", "interval", "version"], "type" : "count" },

"ingest/kafka/lag" : { "dimensions" : ["dataSource", "stream"], "type" : "gauge" },
"ingest/kafka/maxLag" : { "dimensions" : ["dataSource", "stream"], "type" : "gauge" },
"ingest/kafka/avgLag" : { "dimensions" : ["dataSource", "stream"], "type" : "gauge" },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.druid.indexing.common;

import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;
import org.apache.druid.metadata.PendingSegmentRecord;
import org.apache.druid.query.DruidMetrics;
import org.apache.druid.segment.realtime.appenderator.SegmentIdWithShardSpec;

/**
* Metric names and dimension values for the re-announcement of pending segments upgraded by a concurrent REPLACE.
* <ul>
* <li>the task action emits {@link #COUNT} (how many upgrades a commit produced),</li>
* <li>the supervisor emits {@link #NOTIFIED}, {@link #UNMATCHED} and {@link #SEND_FAILED} as it fans requests out,</li>
* <li>the streaming task emits {@link #ANNOUNCED} and {@link #SKIPPED} (with a {@code reason}) as it applies them.</li>
* </ul>
* Two reconciliations bound the visibility gap:
* <ul>
* <li>per segment, {@link #UNMATCHED} counts upgrades that reached no running task (delayed until handoff);</li>
* <li>per task, {@link #NOTIFIED} should equal {@link #ANNOUNCED} + {@link #SKIPPED} + {@link #SEND_FAILED},
* since every notified task either announces, skips, or fails to receive the request. A shortfall means a
* notification was silently dropped.</li>
* </ul>
*/
public class SegmentUpgradeMetrics
{
/** Number of upgraded pending segments a REPLACE commit created and handed to the supervisor. Task-action dims. */
public static final String COUNT = "ingest/realtime/segmentUpgrade/count";

/** A notification was sent to a running task (once per task). Supervisor dims plus {@code taskId}. */
public static final String NOTIFIED = "ingest/realtime/segmentUpgrade/notified";

/** A record matched no running task and will not be re-announced until handoff. Supervisor dims. */
public static final String UNMATCHED = "ingest/realtime/segmentUpgrade/unmatched";

/** An upgrade request failed to reach a task over the wire. Supervisor dims plus {@code taskId}. */
public static final String SEND_FAILED = "ingest/realtime/segmentUpgrade/sendFailed";

/** A task announced an upgraded segment under the new version. Task dims. */
public static final String ANNOUNCED = "ingest/realtime/segmentUpgrade/announced";

/**
* A task received an upgrade request but did not announce it. The {@code reason} dimension carries
* {@link org.apache.druid.segment.realtime.appenderator.StreamAppenderator.PendingSegmentUpgradeResult#getReason()}.
* Task dims.
*/
public static final String SKIPPED = "ingest/realtime/segmentUpgrade/skipped";

/**
* Adds the upgraded pending segment's {@code interval} and {@code version} to a metric builder so that every
* segment-upgrade metric can be sliced by the specific segment being re-announced. Mirrors
* {@code IndexTaskUtils.setSegmentDimensions}, which serves the same purpose for {@code DataSegment}s.
*/
public static ServiceMetricEvent.Builder setSegmentDimensions(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Maybe move this method to IndexTaskUtils itself, as it feels like a more generic place (rather than upgrade specific) allowing for reuse for this method.

ServiceMetricEvent.Builder metricBuilder,
PendingSegmentRecord pendingSegmentRecord
)
{
final SegmentIdWithShardSpec id = pendingSegmentRecord.getId();
return metricBuilder
.setDimension(DruidMetrics.INTERVAL, id.getInterval().toString())
.setDimension(DruidMetrics.VERSION, id.getVersion());
}

private SegmentUpgradeMetrics()
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,24 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import org.apache.druid.indexing.common.SegmentUpgradeMetrics;
import org.apache.druid.indexing.common.task.IndexTaskUtils;
import org.apache.druid.indexing.common.task.Task;
import org.apache.druid.indexing.overlord.CriticalAction;
import org.apache.druid.indexing.overlord.SegmentPublishResult;
import org.apache.druid.indexing.overlord.supervisor.SupervisorManager;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;
import org.apache.druid.metadata.PendingSegmentRecord;
import org.apache.druid.metadata.ReplaceTaskLock;
import org.apache.druid.segment.SegmentSchemaMapping;
import org.apache.druid.segment.SegmentUtils;
import org.apache.druid.timeline.DataSegment;

import javax.annotation.Nullable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -169,19 +173,45 @@ private void registerUpgradedPendingSegmentsOnSupervisor(
List<PendingSegmentRecord> upgradedPendingSegments
)
{
// Emit one count per upgraded segment (rather than a single aggregate) regardless of whether a supervisor exists
// to receive them, so the total can be compared against the count actually announced by tasks and so each event
// carries the segment's interval and version.
for (PendingSegmentRecord upgradedPendingSegment : upgradedPendingSegments) {
final ServiceMetricEvent.Builder metricBuilder = new ServiceMetricEvent.Builder();
IndexTaskUtils.setTaskDimensions(metricBuilder, task);
SegmentUpgradeMetrics.setSegmentDimensions(metricBuilder, upgradedPendingSegment);
toolbox.getEmitter().emit(metricBuilder.setMetric(SegmentUpgradeMetrics.COUNT, 1));
}

final SupervisorManager supervisorManager = toolbox.getSupervisorManager();
final Optional<String> activeSupervisorIdWithAppendLock =
supervisorManager.getActiveSupervisorIdForDatasourceWithAppendLock(task.getDataSource());

if (!activeSupervisorIdWithAppendLock.isPresent()) {
log.info("No active streaming supervisor for datasource[%s]; the [%d] upgraded pending segment(s) from task[%s]"
+ " will become queryable when their tasks hand off.",
task.getDataSource(),
upgradedPendingSegments.size(),
task.getId()
);
Comment on lines +191 to +196

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor rephrase

Suggested change
log.info("No active streaming supervisor for datasource[%s]; the [%d] upgraded pending segment(s) from task[%s]"
+ " will become queryable when their tasks hand off.",
task.getDataSource(),
upgradedPendingSegments.size(),
task.getId()
);
log.info(
"Could not find any active concurrent streaming supervisor for datasource[%s]."
+ " Ignoring registry of [%d] pending segment(s) upgraded by task[%s]. These will become queryable only after handoff."
task.getDataSource(),
upgradedPendingSegments.size(),
task.getId()
);

return;
}

upgradedPendingSegments.forEach(
upgradedPendingSegment -> supervisorManager.registerUpgradedPendingSegmentOnSupervisor(
activeSupervisorIdWithAppendLock.get(),
upgradedPendingSegment
)
// Register each upgraded pending segment on the supervisor and summarize the whole batch in one log line,
// mapping each segment to the number of running tasks the supervisor notified.
final Map<String, Integer> notifiedTasksBySegment = new LinkedHashMap<>();
for (PendingSegmentRecord upgradedPendingSegment : upgradedPendingSegments) {
supervisorManager
.registerUpgradedPendingSegmentOnSupervisor(activeSupervisorIdWithAppendLock.get(), upgradedPendingSegment)
.ifPresent(notified -> notifiedTasksBySegment.put(upgradedPendingSegment.getId().toString(), notified));
}
log.info(
"Registered [%d] upgraded pending segment(s) created by task[%s] on supervisor[%s]; tasks notified per"
+ " segment[%s].",
notifiedTasksBySegment.size(),
task.getId(),
activeSupervisorIdWithAppendLock.get(),
notifiedTasksBySegment
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
Expand Down Expand Up @@ -511,8 +512,11 @@ public boolean checkPointDataSourceMetadata(
* Registers a new version of the given pending segment on a supervisor. This
* allows the supervisor to include the pending segment in queries fired against
* that segment version.
*
* @return the number of tasks notified if the segment was registered on a seekable stream supervisor, or
* {@link OptionalInt#empty()} if no such supervisor was found or the registration failed
*/
public boolean registerUpgradedPendingSegmentOnSupervisor(
public OptionalInt registerUpgradedPendingSegmentOnSupervisor(
String supervisorId,
PendingSegmentRecord upgradedPendingSegment
)
Expand All @@ -529,12 +533,11 @@ public boolean registerUpgradedPendingSegmentOnSupervisor(
Pair<Supervisor, SupervisorSpec> supervisor = supervisors.get(supervisorId);
Preconditions.checkNotNull(supervisor, "supervisor could not be found");
if (!(supervisor.lhs instanceof SeekableStreamSupervisor)) {
return false;
return OptionalInt.empty();
}

SeekableStreamSupervisor<?, ?, ?> seekableStreamSupervisor = (SeekableStreamSupervisor<?, ?, ?>) supervisor.lhs;
seekableStreamSupervisor.registerNewVersionOfPendingSegment(upgradedPendingSegment);
return true;
return OptionalInt.of(seekableStreamSupervisor.registerNewVersionOfPendingSegment(upgradedPendingSegment));
}
catch (Exception e) {
log.error(
Expand All @@ -545,7 +548,7 @@ public boolean registerUpgradedPendingSegmentOnSupervisor(
supervisorId
);
}
return false;
return OptionalInt.empty();
}

/**
Expand Down
Loading
Loading