This is an automated email from the ASF dual-hosted git repository.
FrankChen021 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new c6fcf4325b6 feat: warn when hash/range ingest publishes oversized
segments (#20173)
c6fcf4325b6 is described below
commit c6fcf4325b6cb039620288eb7ea27101f143f9c5
Author: David Alexander <[email protected]>
AuthorDate: Tue Sep 1 08:44:58 2026 -0400
feat: warn when hash/range ingest publishes oversized segments (#20173)
* feat: warn when hash/range ingest publishes oversized segments
* Fix typo in documentation
---------
Co-authored-by: David Alexander <[email protected]>
---
docs/operations/metrics.md | 1 +
.../src/main/resources/defaultMetrics.json | 1 +
.../main/resources/defaultMetricDimensions.json | 1 +
.../common/task/AbstractBatchIndexTask.java | 14 +++-
.../druid/indexing/common/task/IndexTaskUtils.java | 13 ++++
.../parallel/ParallelIndexSupervisorTask.java | 27 +++++++
.../batch/parallel/PartialSegmentMergeTask.java | 10 ++-
.../SeekableStreamIndexTaskRunner.java | 1 +
.../indexing/common/task/IndexTaskUtilsTest.java | 44 ++++++++++++
.../AbstractMultiPhaseParallelIndexingTest.java | 6 +-
.../AbstractParallelIndexSupervisorTaskTest.java | 8 ++-
.../MultiPhaseParallelIndexingRowStatsTest.java | 84 +++++++++++++++++++++-
.../parallel/SinglePhaseParallelIndexingTest.java | 6 +-
.../indexer/report/IngestionStatsAndErrors.java | 20 +++++-
.../resources/loggingEmitterAllowedMetrics.json | 1 +
.../druid/indexer/report/TaskReportSerdeTest.java | 4 +-
.../resources/loggingEmitterAllowedMetrics.json | 1 +
17 files changed, 227 insertions(+), 15 deletions(-)
diff --git a/docs/operations/metrics.md b/docs/operations/metrics.md
index 07a4922945f..f478889f19b 100644
--- a/docs/operations/metrics.md
+++ b/docs/operations/metrics.md
@@ -230,6 +230,7 @@ If SQL is enabled, the Broker will emit the following
metrics for SQL.
|------|-----------|----------|------------|
|`ingest/count`|Count of `1` every time an ingestion job runs (includes
compaction jobs). Aggregate using dimensions. | `dataSource`, `taskId`,
`taskType`, `groupId`, `taskIngestionMode`, `tags` |Always `1`.|
|`ingest/segments/count`|Count of final segments created by job (includes
tombstones). | `dataSource`, `taskId`, `taskType`, `groupId`,
`taskIngestionMode`, `tags` |At least `1`.|
+|`ingest/segments/oversized`|Count of final segments created by job that were
detected as being oversized, meaning the number of rows in the segment exceeded
more than twice the maxRowsPerSegment in the spec. This is relevant only for
hash and range partitioned ingestions. | `dataSource`, `taskId`, `taskType`,
`groupId`, `taskIngestionMode`, `tags` |At least `1`.|
|`ingest/rows/published`|Number of rows successfully published by the job. |
`dataSource`, `taskId`, `taskType`, `groupId`, `taskIngestionMode`, `tags` |At
least `1`.|
|`ingest/tombstones/count`|Count of tombstones created by job. | `dataSource`,
`taskId`, `taskType`, `groupId`, `taskIngestionMode`, `tags` |Zero or more for
replace. Always zero for non-replace tasks (always zero for legacy replace, see
below).|
diff --git
a/extensions-contrib/prometheus-emitter/src/main/resources/defaultMetrics.json
b/extensions-contrib/prometheus-emitter/src/main/resources/defaultMetrics.json
index e1af9b007bd..53492716265 100644
---
a/extensions-contrib/prometheus-emitter/src/main/resources/defaultMetrics.json
+++
b/extensions-contrib/prometheus-emitter/src/main/resources/defaultMetrics.json
@@ -103,6 +103,7 @@
"ingest/count" : { "dimensions" : ["dataSource", "taskType"], "type" :
"count", "help": "Count of 1 every time an ingestion job runs (includes
compaction jobs). Aggregate using dimensions." },
"ingest/segments/count" : { "dimensions" : ["dataSource", "taskType"],
"type" : "count", "help": "Count of final segments created by job (includes
tombstones)." },
+ "ingest/segments/oversized" : { "dimensions" : ["dataSource", "taskType"],
"type" : "count", "help": "Count of final segments created by job that were
detected as being oversized, meaning the number of rows in the segment exceeded
more than 2x the maxRowsPerSegment in the spec. This is relevant only for hash
and range partitioned ingestions." },
"ingest/tombstones/count" : { "dimensions" : ["dataSource", "taskType"],
"type" : "count", "help": "Count of tombstones created by job." },
"ingest/rows/published": { "dimensions" : ["dataSource", "taskType"], "type"
: "count", "help": "Number of rows successfully published by the job." },
diff --git
a/extensions-contrib/statsd-emitter/src/main/resources/defaultMetricDimensions.json
b/extensions-contrib/statsd-emitter/src/main/resources/defaultMetricDimensions.json
index 6eb84425ab7..0b86e8efc5d 100644
---
a/extensions-contrib/statsd-emitter/src/main/resources/defaultMetricDimensions.json
+++
b/extensions-contrib/statsd-emitter/src/main/resources/defaultMetricDimensions.json
@@ -55,6 +55,7 @@
"ingest/merge/time" : { "dimensions" : ["dataSource"], "type" : "timer" },
"ingest/merge/cpu" : { "dimensions" : ["dataSource"], "type" : "timer" },
"ingest/segments/count" : { "dimensions" : ["dataSource"], "type" : "count"
},
+ "ingest/segments/oversized" : { "dimensions" : ["dataSource"], "type" :
"count" },
"ingest/rows/published": { "dimensions" : ["dataSource"], "type" : "count" },
"ingest/realtime/segmentUpgrade/persisted" : { "dimensions" : ["dataSource",
"interval", "version"], "type" : "count" },
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/AbstractBatchIndexTask.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/AbstractBatchIndexTask.java
index bb1f90d1299..806e6121146 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/AbstractBatchIndexTask.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/AbstractBatchIndexTask.java
@@ -929,6 +929,16 @@ public abstract class AbstractBatchIndexTask extends
AbstractTask
return null;
}
+ /**
+ * Number of published segments whose row count exceeds {@code
maxRowsPerSegment} times the oversize ratio.
+ * Null when the check does not apply (for example dynamic partitioning, or
when no target is configured).
+ */
+ @Nullable
+ protected Long getTaskCompletionOversizedSegments()
+ {
+ return null;
+ }
+
protected TaskReport.ReportMap buildLiveIngestionStatsReport(
IngestionState ingestionState,
Map<String, Object> unparseableEvents,
@@ -947,6 +957,7 @@ public abstract class AbstractBatchIndexTask extends
AbstractTask
0L,
null,
null,
+ null,
null
)
)
@@ -1008,7 +1019,8 @@ public abstract class AbstractBatchIndexTask extends
AbstractTask
segmentAvailabilityWaitTimeMs,
Collections.emptyMap(),
segmentsRead,
- segmentsPublished
+ segmentsPublished,
+ getTaskCompletionOversizedSegments()
)
);
}
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/IndexTaskUtils.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/IndexTaskUtils.java
index a03f410982c..154a222c782 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/IndexTaskUtils.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/IndexTaskUtils.java
@@ -141,6 +141,19 @@ public class IndexTaskUtils
.sum();
}
+ /**
+ * Counts published segments whose row count exceeds {@code
maxRowsPerSegment * oversizedRatio}.
+ * Segments without {@link DataSegment#getTotalRows()} populated are ignored.
+ */
+ public static long getOversizedSegments(Collection<DataSegment> segments,
int maxRowsPerSegment, double oversizedRatio)
+ {
+ return segments.stream()
+ .map(DataSegment::getTotalRows)
+ .filter(Objects::nonNull)
+ .filter(rowCount -> rowCount > maxRowsPerSegment * oversizedRatio)
+ .count();
+ }
+
/**
* 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
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/ParallelIndexSupervisorTask.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/ParallelIndexSupervisorTask.java
index 4793b77ac44..1e88c34fda5 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/ParallelIndexSupervisorTask.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/ParallelIndexSupervisorTask.java
@@ -42,6 +42,7 @@ import org.apache.druid.indexer.granularity.GranularitySpec;
import org.apache.druid.indexer.partitions.DimensionRangePartitionsSpec;
import org.apache.druid.indexer.partitions.HashedPartitionsSpec;
import org.apache.druid.indexer.partitions.PartitionsSpec;
+import org.apache.druid.indexer.partitions.SecondaryPartitionType;
import org.apache.druid.indexer.report.IngestionStatsAndErrors;
import org.apache.druid.indexer.report.IngestionStatsAndErrorsTaskReport;
import org.apache.druid.indexer.report.TaskReport;
@@ -154,6 +155,10 @@ public class ParallelIndexSupervisorTask extends
AbstractBatchIndexTask
// and fix
private static final long DEFAULT_NUM_SHARDS_WHEN_ESTIMATE_GOES_NEGATIVE =
7L;
+ // Ratio of the total rows of a segment to the max rows per segment, above
which the segment is considered oversized
+ // For range partitioning, max rows per segment is set to 1.5x target rows,
so the ratio comparison is effectively 3x target rows.
+ private static final double DEFAULT_OVERSIZE_RATIO = 2.0;
+
private final ParallelIndexIngestionSpec ingestionSchema;
/**
* Base name for the {@link SubTaskSpec} ID.
@@ -210,6 +215,7 @@ public class ParallelIndexSupervisorTask extends
AbstractBatchIndexTask
private TaskReport.ReportMap completionReports;
private Long segmentsRead;
private Long segmentsPublished;
+ private Long oversizedSegments; // Number of segments whose row count
exceeds maxRowsPerSegment * DEFAULT_OVERSIZE_RATIO
private final boolean isCompactionTask;
@JsonCreator
@@ -1161,6 +1167,8 @@ public class ParallelIndexSupervisorTask extends
AbstractBatchIndexTask
final Set<DataSegment> oldSegments = new HashSet<>();
final Set<DataSegment> newSegments = new HashSet<>();
final SegmentSchemaMapping segmentSchemaMapping = new
SegmentSchemaMapping(CentralizedDatasourceSchemaConfig.SCHEMA_VERSION);
+ final SecondaryPartitionType type =
ingestionSchema.getTuningConfig().getGivenOrDefaultPartitionsSpec().getType();
+ final Integer maxRowsPerSegment =
ingestionSchema.getTuningConfig().getGivenOrDefaultPartitionsSpec().getMaxRowsPerSegment();
reportsMap
.values()
@@ -1224,6 +1232,19 @@ public class ParallelIndexSupervisorTask extends
AbstractBatchIndexTask
emitMetric(toolbox.getEmitter(), "ingest/tombstones/count",
tombStones.size());
emitMetric(toolbox.getEmitter(), "ingest/segments/count",
newSegments.size());
emitMetric(toolbox.getEmitter(), "ingest/rows/published",
IndexTaskUtils.getTotalRowCount(newSegments));
+ // If partitionsSpec is range or hash, we emit info about the size in
rows of generated partitions, to detect a hot partition.
+ if ((type == SecondaryPartitionType.RANGE || type ==
SecondaryPartitionType.HASH) && maxRowsPerSegment != null) {
+ oversizedSegments = IndexTaskUtils.getOversizedSegments(newSegments,
maxRowsPerSegment, DEFAULT_OVERSIZE_RATIO);
+ if (oversizedSegments > 0) {
+ LOG.warn(
+ "Published [%d] oversized segments with more than
(maxRowsPerSegment [%d] x ratio [%s]) rows.",
+ oversizedSegments,
+ maxRowsPerSegment,
+ DEFAULT_OVERSIZE_RATIO
+ );
+ emitMetric(toolbox.getEmitter(), "ingest/segments/oversized",
oversizedSegments);
+ }
+ }
} else {
throw new ISE("Failed to publish segments");
}
@@ -1276,6 +1297,12 @@ public class ParallelIndexSupervisorTask extends
AbstractBatchIndexTask
);
}
+ @Override
+ protected Long getTaskCompletionOversizedSegments()
+ {
+ return oversizedSegments;
+ }
+
@Override
protected Map<String, Object> getTaskCompletionRowStats()
{
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/PartialSegmentMergeTask.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/PartialSegmentMergeTask.java
index 6bf9c12040f..96a1f841cd4 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/PartialSegmentMergeTask.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/PartialSegmentMergeTask.java
@@ -286,7 +286,8 @@ abstract class PartialSegmentMergeTask<S extends ShardSpec>
extends PerfectRollu
final List<String> metricNames =
Arrays.stream(dataSchema.getAggregators())
.map(AggregatorFactory::getName)
.collect(Collectors.toList());
- SegmentId segmentId = SegmentId.of(
+ final int numRows;
+ final SegmentId segmentId = SegmentId.of(
getDataSource(),
interval,
Preconditions.checkNotNull(AbstractBatchIndexTask.findVersion(
@@ -295,6 +296,9 @@ abstract class PartialSegmentMergeTask<S extends ShardSpec>
extends PerfectRollu
), "version for interval[%s]", interval),
0
);
+ try (QueryableIndex index =
toolbox.getIndexIO().loadIndex(mergedFileAndDimensionNames.lhs)) {
+ numRows = index.getNumRows();
+ }
final DataSegment segment = segmentPusher.push(
mergedFileAndDimensionNames.lhs,
@@ -302,6 +306,7 @@ abstract class PartialSegmentMergeTask<S extends ShardSpec>
extends PerfectRollu
.shardSpec(createShardSpec(toolbox, interval, bucketId))
.dimensions(mergedFileAndDimensionNames.rhs)
.metrics(metricNames)
+ .totalRows(numRows)
.projections(dataSchema.getProjectionNames())
.build(),
false
@@ -324,12 +329,13 @@ abstract class PartialSegmentMergeTask<S extends
ShardSpec> extends PerfectRollu
}
LOG.info("Built segment [%s] for interval [%s] (from [%d] input
segment(s) in [%,d]ms) of "
- + "size [%d] bytes and pushed ([%,d]ms) to deep storage [%s].",
+ + "size [%d] bytes, [%d] rows and pushed ([%,d]ms) to deep storage
[%s].",
segment.getId(),
interval,
segmentFilesToMerge.size(),
(mergeFinishTime - startTime) / 1000000,
segment.getSize(),
+ segment.getTotalRows(),
(pushFinishTime - mergeFinishTime) / 1000000,
segment.getLoadSpec()
);
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskRunner.java
b/indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskRunner.java
index ef60d61e31d..4cf0e70639a 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskRunner.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskRunner.java
@@ -1230,6 +1230,7 @@ public abstract class
SeekableStreamIndexTaskRunner<PartitionIdType, SequenceOff
handoffWaitMs,
getPartitionStats(),
null,
+ null,
null
)
),
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IndexTaskUtilsTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IndexTaskUtilsTest.java
index 77d5015a28e..5538289c3f1 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IndexTaskUtilsTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IndexTaskUtilsTest.java
@@ -19,20 +19,28 @@
package org.apache.druid.indexing.common.task;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;
import org.apache.druid.query.DruidMetrics;
+import org.apache.druid.timeline.DataSegment;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
+import java.util.Arrays;
+import java.util.List;
import java.util.Map;
+import java.util.stream.Stream;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -110,4 +118,40 @@ public class IndexTaskUtilsTest
IndexTaskUtils.setTaskDimensions(metricBuilder, abstractTask);
Assertions.assertNull(metricBuilder.getDimension(DruidMetrics.GROUP_ID));
}
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("oversizedSegmentCases")
+ public void testGetOversizedSegments(
+ String name,
+ List<Integer> rowCounts,
+ int maxRowsPerSegment,
+ double oversizedRatio,
+ long expected
+ )
+ {
+ final List<DataSegment> segments = rowCounts.stream()
+ .map(rowCount -> {
+ DataSegment segment = Mockito.mock(DataSegment.class);
+ Mockito.when(segment.getTotalRows()).thenReturn(rowCount);
+ return segment;
+ })
+ .collect(ImmutableList.toImmutableList());
+ Assertions.assertEquals(
+ expected,
+ IndexTaskUtils.getOversizedSegments(segments, maxRowsPerSegment,
oversizedRatio)
+ );
+ segments.forEach(segment -> {
+ Mockito.verify(segment, Mockito.times(1)).getTotalRows();
+ });
+ }
+ public static Stream<Arguments> oversizedSegmentCases()
+ {
+ return Stream.of(
+ Arguments.of("empty", List.<Integer>of(), 5, 2.0, 0L),
+ Arguments.of("equal to threshold is not oversized", List.of(10), 5,
2.0, 0L),
+ Arguments.of("over threshold", List.of(11), 5, 2.0, 1L),
+ Arguments.of("null totalRows ignored", Arrays.asList(11, null, 3), 5,
2.0, 1L),
+ Arguments.of("mixed", List.of(3, 11, 10, 21), 5, 2.0, 2L)
+ );
+ }
}
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractMultiPhaseParallelIndexingTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractMultiPhaseParallelIndexingTest.java
index 5baca01a71f..baa483f28f4 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractMultiPhaseParallelIndexingTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractMultiPhaseParallelIndexingTest.java
@@ -19,7 +19,6 @@
package org.apache.druid.indexing.common.task.batch.parallel;
-import org.apache.druid.common.guava.FutureUtils;
import org.apache.druid.data.input.InputFormat;
import org.apache.druid.data.input.impl.DimensionsSpec;
import org.apache.druid.data.input.impl.LocalInputSource;
@@ -168,7 +167,10 @@ abstract class AbstractMultiPhaseParallelIndexingTest
extends AbstractParallelIn
TaskReport.ReportMap runTaskAndGetReports(Task task, TaskState
expectedTaskStatus)
{
runTaskAndVerifyStatus(task, expectedTaskStatus);
- return
FutureUtils.getUnchecked(getIndexingServiceClient().taskReportAsMap(task.getId()),
true);
+ // Live reports always omit oversizedSegments; use the completion report
written after publish.
+ final ParallelIndexSupervisorTask executedTask =
+ (ParallelIndexSupervisorTask)
getIndexingServiceClient().getTaskContainer(task.getId()).getTask();
+ return executedTask.getCompletionReports();
}
protected ParallelIndexSupervisorTask createTask(
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java
index 1ca23a349da..345cf2ff70b 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java
@@ -786,6 +786,7 @@ public class AbstractParallelIndexSupervisorTaskTest
extends IngestionTestBase
0L,
null,
null,
+ null,
null
)
)
@@ -795,7 +796,8 @@ public class AbstractParallelIndexSupervisorTaskTest
extends IngestionTestBase
protected TaskReport.ReportMap buildExpectedTaskReportParallel(
String taskId,
List<ParseExceptionReport> expectedUnparseableEvents,
- RowIngestionMetersTotals expectedTotals
+ RowIngestionMetersTotals expectedTotals,
+ Long oversizedSegments
)
{
Map<String, Object> unparseableEvents = ImmutableMap.of("buildSegments",
expectedUnparseableEvents);
@@ -812,7 +814,8 @@ public class AbstractParallelIndexSupervisorTaskTest
extends IngestionTestBase
0L,
null,
null,
- null
+ null,
+ oversizedSegments
)
)
);
@@ -861,6 +864,7 @@ public class AbstractParallelIndexSupervisorTaskTest
extends IngestionTestBase
.stream().map(ParseExceptionReport::getInput).collect(Collectors.toList());
List<String> actualInputs = actualParseExceptionReports
.stream().map(ParseExceptionReport::getInput).collect(Collectors.toList());
+ Assertions.assertEquals(expectedPayload.getOversizedSegments(),
actualPayload.getOversizedSegments());
Assertions.assertEquals(expectedInputs, actualInputs);
}
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/MultiPhaseParallelIndexingRowStatsTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/MultiPhaseParallelIndexingRowStatsTest.java
index 52852ad922b..ec5cb2af87a 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/MultiPhaseParallelIndexingRowStatsTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/MultiPhaseParallelIndexingRowStatsTest.java
@@ -66,6 +66,13 @@ public class MultiPhaseParallelIndexingRowStatsTest extends
AbstractMultiPhasePa
private static final Interval INTERVAL_TO_INDEX =
Intervals.of("2017-12/P1M");
+ // Same day and dim1 so range cannot split the hot key. Unique dim2 avoids
rollup.
+ // Hashing only on dim1 still maps all rows into one bucket.
+ private static final String SKEWED_DAY = "2017-12-1";
+ private static final int SKEWED_DIM1 = 0;
+ private static final int SKEWED_FILE_COUNT = 5;
+ private static final int SKEWED_ROWS_PER_FILE = 20;
+
private File inputDir;
public MultiPhaseParallelIndexingRowStatsTest()
@@ -93,6 +100,15 @@ public class MultiPhaseParallelIndexingRowStatsTest extends
AbstractMultiPhasePa
}
}
+ for (int i = 0; i < SKEWED_FILE_COUNT; i++) {
+ try (final Writer writer =
+ Files.newBufferedWriter(new File(inputDir, "skewed_" +
i).toPath(), StandardCharsets.UTF_8)) {
+ for (int j = 0; j < SKEWED_ROWS_PER_FILE; j++) {
+ writer.write(StringUtils.format("%s,%d,%d th test file\n",
SKEWED_DAY, SKEWED_DIM1, i * SKEWED_ROWS_PER_FILE + j));
+ }
+ }
+ }
+
for (int i = 0; i < 5; i++) {
try (final Writer writer =
Files.newBufferedWriter(new File(inputDir, "filtered_" +
i).toPath(), StandardCharsets.UTF_8)) {
@@ -143,7 +159,8 @@ public class MultiPhaseParallelIndexingRowStatsTest extends
AbstractMultiPhasePa
: buildExpectedTaskReportParallel(
task.getId(),
ImmutableList.of(),
- expectedTotals
+ expectedTotals,
+ null
);
TaskReport.ReportMap actualReports = runTaskAndGetReports(task,
TaskState.SUCCESS);
@@ -169,9 +186,72 @@ public class MultiPhaseParallelIndexingRowStatsTest
extends AbstractMultiPhasePa
TaskReport.ReportMap expectedReports = buildExpectedTaskReportParallel(
task.getId(),
ImmutableList.of(),
- new RowIngestionMetersTotals(200, 5630, 0, 0, 0)
+ new RowIngestionMetersTotals(200, 5630, 0, 0, 0),
+ 0L
);
TaskReport.ReportMap actualReports = runTaskAndGetReports(task,
TaskState.SUCCESS);
compareTaskReports(expectedReports, actualReports);
}
+
+ @Test
+ public void testHashPartitionRowStatsWithOversizedSegments()
+ {
+ final int maxNumConcurrentSubTasks = 10;
+ final int maxRowsPerSegment = 20;
+
+ ParallelIndexSupervisorTask task = createTask(
+ TIMESTAMP_SPEC,
+ DIMENSIONS_SPEC,
+ INPUT_FORMAT,
+ INTERVAL_TO_INDEX,
+ inputDir,
+ "skewed_*",
+ new HashedPartitionsSpec(maxRowsPerSegment, null,
ImmutableList.of(DIM1), null),
+ maxNumConcurrentSubTasks,
+ false,
+ false
+ );
+
+ final RowIngestionMetersTotals expectedTotals =
RowMeters.with().bytes(2790).totalProcessed(100);
+ final TaskReport.ReportMap expectedReports =
buildExpectedTaskReportParallel(
+ task.getId(),
+ ImmutableList.of(),
+ expectedTotals,
+ 1L
+ );
+
+ TaskReport.ReportMap actualReports = runTaskAndGetReports(task,
TaskState.SUCCESS);
+ compareTaskReports(expectedReports, actualReports);
+ }
+
+ @Test
+ public void testRangePartitionRowStatsWithOversizedSegments()
+ {
+ final int maxNumConcurrentSubTasks = 10;
+ final int targetRowsPerSegment = 20;
+
+ ParallelIndexSupervisorTask task = createTask(
+ TIMESTAMP_SPEC,
+ DIMENSIONS_SPEC,
+ INPUT_FORMAT,
+ INTERVAL_TO_INDEX,
+ inputDir,
+ "skewed_*",
+ new SingleDimensionPartitionsSpec(targetRowsPerSegment, null, DIM1,
false),
+ maxNumConcurrentSubTasks,
+ false,
+ false
+ );
+
+ final RowIngestionMetersTotals expectedTotals =
RowMeters.with().bytes(2790).totalProcessed(100);
+ final TaskReport.ReportMap expectedReports =
buildExpectedTaskReportParallel(
+ task.getId(),
+ ImmutableList.of(),
+ expectedTotals,
+ 1L
+ );
+
+ TaskReport.ReportMap actualReports = runTaskAndGetReports(task,
TaskState.SUCCESS);
+ compareTaskReports(expectedReports, actualReports);
+ }
}
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseParallelIndexingTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseParallelIndexingTest.java
index ff16bb094af..d208d0fd1ce 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseParallelIndexingTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseParallelIndexingTest.java
@@ -498,7 +498,8 @@ public class SinglePhaseParallelIndexingTest extends
AbstractParallelIndexSuperv
1L
)
),
- new RowIngestionMetersTotals(10, 335, 1, expectedThrownAwayByReason, 1)
+ new RowIngestionMetersTotals(10, 335, 1, expectedThrownAwayByReason,
1),
+ null
);
compareTaskReports(expectedReports, actualReports);
}
@@ -564,7 +565,8 @@ public class SinglePhaseParallelIndexingTest extends
AbstractParallelIndexSuperv
expectedReports = buildExpectedTaskReportParallel(
task.getId(),
expectedUnparseableEvents,
- expectedTotals
+ expectedTotals,
+ null
);
}
diff --git
a/processing/src/main/java/org/apache/druid/indexer/report/IngestionStatsAndErrors.java
b/processing/src/main/java/org/apache/druid/indexer/report/IngestionStatsAndErrors.java
index f766d0a324c..0e16cd1baf8 100644
---
a/processing/src/main/java/org/apache/druid/indexer/report/IngestionStatsAndErrors.java
+++
b/processing/src/main/java/org/apache/druid/indexer/report/IngestionStatsAndErrors.java
@@ -38,6 +38,7 @@ public class IngestionStatsAndErrors
private final Map<String, Long> recordsProcessed;
private final Long segmentsRead;
private final Long segmentsPublished;
+ private final Long oversizedSegments;
public IngestionStatsAndErrors(
@JsonProperty("ingestionState") IngestionState ingestionState,
@@ -48,7 +49,8 @@ public class IngestionStatsAndErrors
@JsonProperty("segmentAvailabilityWaitTimeMs") long
segmentAvailabilityWaitTimeMs,
@JsonProperty("recordsProcessed") Map<String, Long> recordsProcessed,
@Nullable @JsonProperty("segmentsRead") Long segmentsRead,
- @Nullable @JsonProperty("segmentsPublished") Long segmentsPublished
+ @Nullable @JsonProperty("segmentsPublished") Long segmentsPublished,
+ @Nullable @JsonProperty("oversizedSegments") Long oversizedSegments
)
{
this.ingestionState = ingestionState;
@@ -60,6 +62,7 @@ public class IngestionStatsAndErrors
this.recordsProcessed = recordsProcessed;
this.segmentsRead = segmentsRead;
this.segmentsPublished = segmentsPublished;
+ this.oversizedSegments = oversizedSegments;
}
@JsonProperty
@@ -122,6 +125,14 @@ public class IngestionStatsAndErrors
return segmentsPublished;
}
+ @JsonProperty
+ @Nullable
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public Long getOversizedSegments()
+ {
+ return oversizedSegments;
+ }
+
public static IngestionStatsAndErrors getPayloadFromTaskReports(
Map<String, TaskReport> taskReports
)
@@ -148,7 +159,8 @@ public class IngestionStatsAndErrors
Objects.equals(getSegmentAvailabilityWaitTimeMs(),
that.getSegmentAvailabilityWaitTimeMs()) &&
Objects.equals(getRecordsProcessed(), that.getRecordsProcessed()) &&
Objects.equals(getSegmentsRead(), that.getSegmentsRead()) &&
- Objects.equals(getSegmentsPublished(), that.getSegmentsPublished());
+ Objects.equals(getSegmentsPublished(), that.getSegmentsPublished())
&&
+ Objects.equals(getOversizedSegments(), that.getOversizedSegments());
}
@Override
@@ -163,7 +175,8 @@ public class IngestionStatsAndErrors
getSegmentAvailabilityWaitTimeMs(),
getRecordsProcessed(),
getSegmentsRead(),
- getSegmentsPublished()
+ getSegmentsPublished(),
+ getOversizedSegments()
);
}
@@ -180,6 +193,7 @@ public class IngestionStatsAndErrors
", recordsProcessed=" + recordsProcessed +
", segmentsRead=" + segmentsRead +
", segmentsPublished=" + segmentsPublished +
+ ", oversizedSegments=" + oversizedSegments +
'}';
}
}
diff --git a/processing/src/main/resources/loggingEmitterAllowedMetrics.json
b/processing/src/main/resources/loggingEmitterAllowedMetrics.json
index 9bcdb712952..e0d89fac679 100644
--- a/processing/src/main/resources/loggingEmitterAllowedMetrics.json
+++ b/processing/src/main/resources/loggingEmitterAllowedMetrics.json
@@ -37,6 +37,7 @@
"ingest/persists/time": [],
"ingest/rows/output": [],
"ingest/segments/count": [],
+ "ingest/segments/oversized": [],
"ingest/rows/published": [],
"ingest/sink/count": [],
"ingest/tombstones/count": [],
diff --git
a/processing/src/test/java/org/apache/druid/indexer/report/TaskReportSerdeTest.java
b/processing/src/test/java/org/apache/druid/indexer/report/TaskReportSerdeTest.java
index 67957e9a047..e1c0ac44029 100644
---
a/processing/src/test/java/org/apache/druid/indexer/report/TaskReportSerdeTest.java
+++
b/processing/src/test/java/org/apache/druid/indexer/report/TaskReportSerdeTest.java
@@ -273,6 +273,7 @@ public class TaskReportSerdeTest
1000L,
null,
null,
+ null,
null
)
);
@@ -311,7 +312,8 @@ public class TaskReportSerdeTest
1000L,
Collections.singletonMap("PartitionA", 5000L),
5L,
- 10L
+ 10L,
+ 2L
)
);
}
diff --git a/processing/src/test/resources/loggingEmitterAllowedMetrics.json
b/processing/src/test/resources/loggingEmitterAllowedMetrics.json
index 3d3200362f0..5818ed2d36a 100644
--- a/processing/src/test/resources/loggingEmitterAllowedMetrics.json
+++ b/processing/src/test/resources/loggingEmitterAllowedMetrics.json
@@ -37,6 +37,7 @@
"ingest/persists/time": [],
"ingest/rows/output": [],
"ingest/segments/count": [],
+ "ingest/segments/oversized": [],
"ingest/rows/published": [],
"ingest/sink/count": [],
"ingest/tombstones/count": [],
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]