This is an automated email from the ASF dual-hosted git repository.
gianm 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 04d643f5fb6 perf: Limit details returned by
RetrieveUsedSegmentsAction. (#20006)
04d643f5fb6 is described below
commit 04d643f5fb6afb81032d17e0b7b85393883fbde3
Author: Gian Merlino <[email protected]>
AuthorDate: Fri Aug 14 15:53:38 2026 -0700
perf: Limit details returned by RetrieveUsedSegmentsAction. (#20006)
DataSegment objects can have a variety of different details. Most of them
are not necessary for the tasks that use this action. The new "details"
parameter allows tasks to limit what is returned, saving bandwidth and
serde time.
---
.../ActionBasedPublishedSegmentRetriever.java | 12 +-
.../common/actions/RetrieveUsedSegmentsAction.java | 96 ++++++++--
.../common/task/AbstractBatchIndexTask.java | 9 +-
.../druid/indexing/common/task/CompactionTask.java | 10 +-
.../druid/indexing/common/task/IndexTask.java | 4 +-
.../common/task/KillUnusedSegmentsTask.java | 5 +-
.../parallel/ParallelIndexSupervisorTask.java | 4 +-
.../task/batch/parallel/SinglePhaseSubTask.java | 4 +-
.../task/batch/parallel/TombstoneHelper.java | 4 +-
.../druid/indexing/input/DruidInputSource.java | 5 +-
.../ActionBasedPublishedSegmentRetrieverTest.java | 5 +-
.../actions/RetrieveSegmentsActionsTest.java | 3 +-
.../RetrieveUsedSegmentsActionSerdeTest.java | 36 +++-
.../concurrent/ConcurrentReplaceAndAppendTest.java | 7 +-
.../ConcurrentReplaceAndStreamingAppendTest.java | 7 +-
.../msq/indexing/IndexerTableInputSpecSlicer.java | 10 +-
.../org/apache/druid/msq/exec/MSQReplaceTest.java | 67 +++++--
.../org/apache/druid/timeline/DataSegment.java | 27 +++
.../org/apache/druid/timeline/SegmentDetail.java | 132 ++++++++++++++
.../apache/druid/timeline/SegmentDetailTest.java | 193 +++++++++++++++++++++
.../appenderator/BaseAppenderatorDriver.java | 25 ++-
21 files changed, 610 insertions(+), 55 deletions(-)
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/appenderator/ActionBasedPublishedSegmentRetriever.java
b/indexing-service/src/main/java/org/apache/druid/indexing/appenderator/ActionBasedPublishedSegmentRetriever.java
index bb349cc9790..74ea1cbebde 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/appenderator/ActionBasedPublishedSegmentRetriever.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/appenderator/ActionBasedPublishedSegmentRetriever.java
@@ -29,12 +29,14 @@ import org.apache.druid.java.util.common.JodaUtils;
import org.apache.druid.java.util.common.logger.Logger;
import
org.apache.druid.segment.realtime.appenderator.PublishedSegmentRetriever;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.SegmentId;
import org.joda.time.Interval;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -92,7 +94,15 @@ public class ActionBasedPublishedSegmentRetriever implements
PublishedSegmentRet
Iterables.transform(segmentIds, SegmentId::getInterval)
);
final Collection<DataSegment> foundUsedSegments = taskActionClient.submit(
- new RetrieveUsedSegmentsAction(dataSource, usedSearchIntervals,
Segments.INCLUDING_OVERSHADOWED)
+ new RetrieveUsedSegmentsAction(
+ dataSource,
+ usedSearchIntervals,
+ Segments.INCLUDING_OVERSHADOWED,
+ // LOAD_SPEC is required: callers compare the load specs of the
returned segments against the ones they
+ // pushed themselves, to decide whether the pushed copies are safe
to delete from deep storage. See
+ // BaseAppenderatorDriver#publishInBackground.
+ EnumSet.of(SegmentDetail.LOAD_SPEC)
+ )
);
for (DataSegment segment : foundUsedSegments) {
if (segmentIds.contains(segment.getId())) {
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/actions/RetrieveUsedSegmentsAction.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/actions/RetrieveUsedSegmentsAction.java
index 51bec50a600..4ba2a137f85 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/common/actions/RetrieveUsedSegmentsAction.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/actions/RetrieveUsedSegmentsAction.java
@@ -20,6 +20,7 @@
package org.apache.druid.indexing.common.actions;
import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.druid.common.config.Configs;
@@ -34,12 +35,16 @@ import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.metadata.ReplaceTaskLock;
import org.apache.druid.timeline.DataSegment;
import org.apache.druid.timeline.Partitions;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.SegmentTimeline;
import org.apache.druid.utils.CollectionUtils;
import org.joda.time.Interval;
import javax.annotation.Nullable;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -56,6 +61,9 @@ import java.util.stream.Collectors;
* only segments that were created before the REPLACE lock was acquired are
* returned for an interval. This ensures that the input set of segments for
this
* replace task remains consistent even when new data is appended by other
concurrent tasks.
+ * <p>
+ * Callers declare which optional segment details they need through {@code
details}. Details that
+ * are not requested are nulled out of the returned segments.
*/
public class RetrieveUsedSegmentsAction implements
TaskAction<Collection<DataSegment>>
{
@@ -65,11 +73,18 @@ public class RetrieveUsedSegmentsAction implements
TaskAction<Collection<DataSeg
private final List<Interval> intervals;
private final Segments visibility;
- @JsonCreator
+ /**
+ * Optional segment details to include in the returned segments; see {@link
SegmentDetail}. Null means "include
+ * everything", which is what a client older than this parameter expects.
+ */
+ @Nullable
+ private final Set<SegmentDetail> details;
+
public RetrieveUsedSegmentsAction(
- @JsonProperty("dataSource") String dataSource,
- @JsonProperty("intervals") Collection<Interval> intervals,
- @JsonProperty("visibility") @Nullable Segments visibility
+ String dataSource,
+ Collection<Interval> intervals,
+ @Nullable Segments visibility,
+ @Nullable EnumSet<SegmentDetail> details
)
{
if (CollectionUtils.isNullOrEmpty(intervals)) {
@@ -79,11 +94,31 @@ public class RetrieveUsedSegmentsAction implements
TaskAction<Collection<DataSeg
this.dataSource = dataSource;
this.intervals = JodaUtils.condenseIntervals(intervals);
this.visibility = Configs.valueOrDefault(visibility,
Segments.ONLY_VISIBLE);
+ this.details = details == null ? null :
Collections.unmodifiableSet(EnumSet.copyOf(details));
+ }
+
+ public RetrieveUsedSegmentsAction(
+ String dataSource,
+ Collection<Interval> intervals,
+ EnumSet<SegmentDetail> details
+ )
+ {
+ this(dataSource, intervals, Segments.ONLY_VISIBLE, details);
}
- public RetrieveUsedSegmentsAction(String dataSource, Collection<Interval>
intervals)
+ /**
+ * Factory for deserialization. Takes the details as raw names rather than
as {@link SegmentDetail} so that a name
+ * this version of Druid does not know about is skipped instead of failing
the whole request.
+ */
+ @JsonCreator
+ static RetrieveUsedSegmentsAction fromJson(
+ @JsonProperty("dataSource") String dataSource,
+ @JsonProperty("intervals") Collection<Interval> intervals,
+ @JsonProperty("visibility") @Nullable Segments visibility,
+ @JsonProperty("details") @Nullable Collection<String> details
+ )
{
- this(dataSource, intervals, Segments.ONLY_VISIBLE);
+ return new RetrieveUsedSegmentsAction(dataSource, intervals, visibility,
SegmentDetail.fromNamesLenient(details));
}
@JsonProperty
@@ -104,6 +139,19 @@ public class RetrieveUsedSegmentsAction implements
TaskAction<Collection<DataSeg
return visibility;
}
+ /**
+ * Segment details to include. Null means include all details, empty means
include no details.
+ *
+ * @see DataSegment#retainOnlyDetails(Set)
+ */
+ @Nullable
+ @JsonProperty
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public Set<SegmentDetail> getDetails()
+ {
+ return details;
+ }
+
@Override
public TypeReference<Collection<DataSegment>> getReturnTypeReference()
{
@@ -112,6 +160,11 @@ public class RetrieveUsedSegmentsAction implements
TaskAction<Collection<DataSeg
@Override
public Collection<DataSegment> perform(Task task, TaskActionToolbox toolbox)
+ {
+ return retainRequestedDetails(retrieveSegments(task, toolbox));
+ }
+
+ private Collection<DataSegment> retrieveSegments(Task task,
TaskActionToolbox toolbox)
{
// When fetching segments for a datasource other than the one this task is
writing to,
// just return all segments with the needed visibility.
@@ -186,6 +239,23 @@ public class RetrieveUsedSegmentsAction implements
TaskAction<Collection<DataSeg
.retrieveUsedSegmentsForIntervals(dataSource, intervals,
visibility);
}
+ /**
+ * Strips the optional details that the caller did not ask for out of {@code
segments}. A null {@link #details} means
+ * the caller is older than this parameter and expects every detail, so the
segments pass through untouched.
+ */
+ private Collection<DataSegment> retainRequestedDetails(final
Collection<DataSegment> segments)
+ {
+ if (details == null) {
+ return segments;
+ }
+
+ final List<DataSegment> retVal = new ArrayList<>(segments.size());
+ for (final DataSegment segment : segments) {
+ retVal.add(segment.retainOnlyDetails(details));
+ }
+ return retVal;
+ }
+
@Override
public boolean equals(Object o)
{
@@ -198,19 +268,16 @@ public class RetrieveUsedSegmentsAction implements
TaskAction<Collection<DataSeg
RetrieveUsedSegmentsAction that = (RetrieveUsedSegmentsAction) o;
- if (!dataSource.equals(that.dataSource)) {
- return false;
- }
- if (!intervals.equals(that.intervals)) {
- return false;
- }
- return visibility.equals(that.visibility);
+ return dataSource.equals(that.dataSource)
+ && intervals.equals(that.intervals)
+ && visibility.equals(that.visibility)
+ && Objects.equals(details, that.details);
}
@Override
public int hashCode()
{
- return Objects.hash(dataSource, intervals, visibility);
+ return Objects.hash(dataSource, intervals, visibility, details);
}
@Override
@@ -220,6 +287,7 @@ public class RetrieveUsedSegmentsAction implements
TaskAction<Collection<DataSeg
"dataSource='" + dataSource + '\'' +
", intervals=" + intervals +
", visibility=" + visibility +
+ ", details=" + details +
'}';
}
}
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 a555056c995..bb1f90d1299 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
@@ -81,6 +81,7 @@ import
org.apache.druid.segment.transform.CompactionTransformSpec;
import org.apache.druid.timeline.CompactionState;
import org.apache.druid.timeline.DataSegment;
import org.apache.druid.timeline.Partitions;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.SegmentTimeline;
import org.apache.druid.timeline.partition.HashBasedNumberedShardSpec;
import org.apache.druid.timeline.partition.TombstoneShardSpec;
@@ -94,6 +95,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -697,13 +699,12 @@ public abstract class AbstractBatchIndexTask extends
AbstractTask
protected static List<DataSegment> findInputSegments(
String dataSource,
TaskActionClient actionClient,
- List<Interval> intervalsToRead
+ List<Interval> intervalsToRead,
+ EnumSet<SegmentDetail> details
) throws IOException
{
return ImmutableList.copyOf(
- actionClient.submit(
- new RetrieveUsedSegmentsAction(dataSource, intervalsToRead)
- )
+ actionClient.submit(new RetrieveUsedSegmentsAction(dataSource,
intervalsToRead, details))
);
}
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/CompactionTask.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/CompactionTask.java
index d4a8b68d26c..0e857e52786 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/CompactionTask.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/CompactionTask.java
@@ -110,6 +110,7 @@ import
org.apache.druid.server.coordinator.CompactionConfigValidationResult;
import org.apache.druid.server.lookup.cache.LookupLoadingSpec;
import org.apache.druid.server.security.ResourceAction;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.SegmentId;
import org.apache.druid.timeline.SegmentTimeline;
import org.apache.druid.timeline.TimelineObjectHolder;
@@ -125,6 +126,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -493,7 +495,7 @@ public class CompactionTask extends AbstractBatchIndexTask
implements PendingSeg
throws IOException
{
return ImmutableList.copyOf(
- taskActionClient.submit(new
RetrieveUsedSegmentsAction(getDataSource(), intervals))
+ taskActionClient.submit(new
RetrieveUsedSegmentsAction(getDataSource(), intervals, SegmentDetail.none()))
);
}
@@ -1385,7 +1387,11 @@ public class CompactionTask extends
AbstractBatchIndexTask implements PendingSeg
{
return new ArrayList<>(
actionClient.submit(
- new RetrieveUsedSegmentsAction(dataSource,
ImmutableList.of(interval))
+ new RetrieveUsedSegmentsAction(
+ dataSource,
+ ImmutableList.of(interval),
+ EnumSet.of(SegmentDetail.LOAD_SPEC)
+ )
)
);
}
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/IndexTask.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/IndexTask.java
index b29899a77cf..75b3c25e1e9 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/IndexTask.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/IndexTask.java
@@ -94,6 +94,7 @@ import org.apache.druid.server.security.AuthorizationUtils;
import org.apache.druid.server.security.AuthorizerMapper;
import org.apache.druid.server.security.ResourceAction;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.partition.HashBasedNumberedShardSpec;
import org.apache.druid.timeline.partition.NumberedShardSpec;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@@ -269,7 +270,8 @@ public class IndexTask extends AbstractBatchIndexTask
implements ChatHandler, Pe
return findInputSegments(
getDataSource(),
taskActionClient,
- intervals
+ intervals,
+ SegmentDetail.none()
);
}
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTask.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTask.java
index 5c74770c230..3e8ca76b39a 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTask.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTask.java
@@ -53,6 +53,7 @@ import org.apache.druid.server.http.DataSegmentPlus;
import org.apache.druid.server.lookup.cache.LookupLoadingSpec;
import org.apache.druid.server.security.ResourceAction;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.utils.CollectionUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -61,6 +62,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -235,7 +237,8 @@ public class KillUnusedSegmentsTask extends
AbstractFixedIntervalTask
RetrieveUsedSegmentsAction retrieveUsedSegmentsAction = new
RetrieveUsedSegmentsAction(
getDataSource(),
ImmutableList.of(getInterval()),
- Segments.INCLUDING_OVERSHADOWED
+ Segments.INCLUDING_OVERSHADOWED,
+ EnumSet.of(SegmentDetail.LOAD_SPEC)
);
// Fetch the load specs of all segments overlapping with the unused
segment intervals
final Set<Map<String, Object>> usedSegmentLoadSpecs =
taskActionClient.submit(retrieveUsedSegmentsAction)
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 2764c26c6af..4793b77ac44 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
@@ -85,6 +85,7 @@ import org.apache.druid.server.security.AuthorizationUtils;
import org.apache.druid.server.security.AuthorizerMapper;
import org.apache.druid.server.security.ResourceAction;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.partition.BuildingShardSpec;
import org.apache.druid.timeline.partition.NumberedShardSpec;
import org.apache.druid.timeline.partition.PartitionBoundaries;
@@ -460,7 +461,8 @@ public class ParallelIndexSupervisorTask extends
AbstractBatchIndexTask
return findInputSegments(
getDataSource(),
taskActionClient,
- intervals
+ intervals,
+ SegmentDetail.none()
);
}
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseSubTask.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseSubTask.java
index 53a480b66cc..2b7d3043127 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseSubTask.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseSubTask.java
@@ -68,6 +68,7 @@ import org.apache.druid.server.security.AuthorizationUtils;
import org.apache.druid.server.security.AuthorizerMapper;
import org.apache.druid.server.security.ResourceAction;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.SegmentTimeline;
import org.apache.druid.timeline.TimelineObjectHolder;
import org.apache.druid.timeline.partition.PartitionChunk;
@@ -310,7 +311,8 @@ public class SinglePhaseSubTask extends
AbstractBatchSubtask implements ChatHand
return findInputSegments(
getDataSource(),
taskActionClient,
- intervals
+ intervals,
+ SegmentDetail.none()
);
}
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/TombstoneHelper.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/TombstoneHelper.java
index 7d739dd02e9..9f0f017c864 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/TombstoneHelper.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/TombstoneHelper.java
@@ -35,6 +35,7 @@ import
org.apache.druid.java.util.common.granularity.IntervalsByGranularity;
import org.apache.druid.segment.indexing.DataSchema;
import org.apache.druid.segment.realtime.appenderator.SegmentIdWithShardSpec;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.partition.ShardSpec;
import org.apache.druid.timeline.partition.TombstoneShardSpec;
import org.joda.time.Interval;
@@ -310,7 +311,8 @@ public class TombstoneHelper
Collection<DataSegment> usedSegmentsInInputInterval =
taskActionClient.submit(new RetrieveUsedSegmentsAction(
dataSource,
- condensedInputIntervals
+ condensedInputIntervals,
+ SegmentDetail.none()
));
for (DataSegment usedSegment : usedSegmentsInInputInterval) {
for (Interval condensedInputInterval : condensedInputIntervals) {
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/input/DruidInputSource.java
b/indexing-service/src/main/java/org/apache/druid/indexing/input/DruidInputSource.java
index 917936c03c5..c1178a421fa 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/input/DruidInputSource.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/input/DruidInputSource.java
@@ -61,6 +61,7 @@ import org.apache.druid.segment.IndexIO;
import org.apache.druid.segment.column.ColumnHolder;
import org.apache.druid.segment.loading.SegmentCacheManager;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.SegmentId;
import org.apache.druid.timeline.SegmentTimeline;
import org.apache.druid.timeline.TimelineObjectHolder;
@@ -77,6 +78,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -589,7 +591,8 @@ public class DruidInputSource extends AbstractInputSource
implements SplittableI
usedSegments = toolbox.getTaskActionClient()
.submit(new RetrieveUsedSegmentsAction(
dataSource,
- Collections.singletonList(interval)
+ Collections.singletonList(interval),
+ EnumSet.of(SegmentDetail.LOAD_SPEC)
));
}
catch (IOException e) {
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/appenderator/ActionBasedPublishedSegmentRetrieverTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/appenderator/ActionBasedPublishedSegmentRetrieverTest.java
index 14b51b55782..c300e66cc19 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/appenderator/ActionBasedPublishedSegmentRetrieverTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/appenderator/ActionBasedPublishedSegmentRetrieverTest.java
@@ -31,6 +31,7 @@ import
org.apache.druid.java.util.common.granularity.Granularities;
import org.apache.druid.segment.TestDataSource;
import org.apache.druid.server.coordinator.CreateDataSegments;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.SegmentId;
import org.easymock.EasyMock;
import org.junit.jupiter.api.Assertions;
@@ -39,6 +40,7 @@ import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -105,7 +107,8 @@ public class ActionBasedPublishedSegmentRetrieverTest
new RetrieveUsedSegmentsAction(
TestDataSource.WIKI,
Collections.singletonList(Intervals.of("2013-01-01/P3D")),
- Segments.INCLUDING_OVERSHADOWED
+ Segments.INCLUDING_OVERSHADOWED,
+ EnumSet.of(SegmentDetail.LOAD_SPEC)
)
)
).andReturn(segments).once();
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/actions/RetrieveSegmentsActionsTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/actions/RetrieveSegmentsActionsTest.java
index d2edd148733..7b4858d20ea 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/actions/RetrieveSegmentsActionsTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/actions/RetrieveSegmentsActionsTest.java
@@ -26,6 +26,7 @@ import org.apache.druid.indexing.common.task.Task;
import org.apache.druid.java.util.common.DateTimes;
import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.partition.NoneShardSpec;
import org.joda.time.Interval;
import org.junit.jupiter.api.Assertions;
@@ -100,7 +101,7 @@ public class RetrieveSegmentsActionsTest
public void testRetrieveUsedSegmentsAction()
{
final RetrieveUsedSegmentsAction action =
- new RetrieveUsedSegmentsAction(task.getDataSource(),
ImmutableList.of(INTERVAL));
+ new RetrieveUsedSegmentsAction(task.getDataSource(),
ImmutableList.of(INTERVAL), SegmentDetail.all());
final Set<DataSegment> observedUsedSegments = new
HashSet<>(action.perform(task, actionTestKit.getTaskActionToolbox()));
Assertions.assertEquals(expectedUsedSegments, observedUsedSegments);
}
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/actions/RetrieveUsedSegmentsActionSerdeTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/actions/RetrieveUsedSegmentsActionSerdeTest.java
index 6a1a20ed6c9..f58377d922d 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/actions/RetrieveUsedSegmentsActionSerdeTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/actions/RetrieveUsedSegmentsActionSerdeTest.java
@@ -24,11 +24,13 @@ import com.google.common.collect.ImmutableList;
import org.apache.druid.indexing.overlord.Segments;
import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.segment.TestHelper;
+import org.apache.druid.timeline.SegmentDetail;
import org.joda.time.Interval;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.List;
/**
@@ -42,8 +44,12 @@ public class RetrieveUsedSegmentsActionSerdeTest
{
Interval interval = Intervals.of("2014/2015");
- RetrieveUsedSegmentsAction expected =
- new RetrieveUsedSegmentsAction("dataSource",
Collections.singletonList(interval), Segments.ONLY_VISIBLE);
+ RetrieveUsedSegmentsAction expected = new RetrieveUsedSegmentsAction(
+ "dataSource",
+ Collections.singletonList(interval),
+ Segments.ONLY_VISIBLE,
+ SegmentDetail.all()
+ );
RetrieveUsedSegmentsAction actual =
MAPPER.readValue(MAPPER.writeValueAsString(expected),
RetrieveUsedSegmentsAction.class);
@@ -57,7 +63,8 @@ public class RetrieveUsedSegmentsActionSerdeTest
List<Interval> intervals = ImmutableList.of(Intervals.of("2014/2015"),
Intervals.of("2016/2017"));
RetrieveUsedSegmentsAction expected = new RetrieveUsedSegmentsAction(
"dataSource",
- intervals
+ intervals,
+ SegmentDetail.none()
);
RetrieveUsedSegmentsAction actual =
@@ -66,6 +73,25 @@ public class RetrieveUsedSegmentsActionSerdeTest
Assertions.assertEquals(expected, actual);
}
+ @Test
+ public void testPartialDetailsSerde() throws Exception
+ {
+ RetrieveUsedSegmentsAction expected = new RetrieveUsedSegmentsAction(
+ "dataSource",
+ Collections.singletonList(Intervals.of("2014/2015")),
+ Segments.INCLUDING_OVERSHADOWED,
+ EnumSet.of(SegmentDetail.LOAD_SPEC, SegmentDetail.DIMENSIONS)
+ );
+
+ final String json = MAPPER.writeValueAsString(expected);
+ RetrieveUsedSegmentsAction actual = MAPPER.readValue(json,
RetrieveUsedSegmentsAction.class);
+ Assertions.assertEquals(
+ EnumSet.of(SegmentDetail.DIMENSIONS, SegmentDetail.LOAD_SPEC),
+ actual.getDetails()
+ );
+ Assertions.assertEquals(expected, actual);
+ }
+
@Test
public void testOldJsonDeserialization() throws Exception
{
@@ -76,9 +102,11 @@ public class RetrieveUsedSegmentsActionSerdeTest
new RetrieveUsedSegmentsAction(
"test",
Collections.singletonList(Intervals.of("2014/2015")),
- Segments.ONLY_VISIBLE
+ Segments.ONLY_VISIBLE,
+ null
),
actual
);
+ Assertions.assertNull(actual.getDetails());
}
}
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/concurrent/ConcurrentReplaceAndAppendTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/concurrent/ConcurrentReplaceAndAppendTest.java
index 6a11cc30ce4..a96c650dd31 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/concurrent/ConcurrentReplaceAndAppendTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/concurrent/ConcurrentReplaceAndAppendTest.java
@@ -63,6 +63,7 @@ import org.apache.druid.server.DruidNode;
import org.apache.druid.server.metrics.NoopServiceEmitter;
import org.apache.druid.tasklogs.NoopTaskLogs;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.SegmentId;
import org.apache.druid.timeline.partition.NumberedShardSpec;
import org.joda.time.Interval;
@@ -1298,7 +1299,8 @@ public class ConcurrentReplaceAndAppendTest extends
IngestionTestBase
new RetrieveUsedSegmentsAction(
TestDataSource.WIKI,
ImmutableList.of(interval),
- visibility
+ visibility,
+ SegmentDetail.all()
)
);
Assertions.assertEquals(Sets.newHashSet(expectedSegments),
Sets.newHashSet(allUsedSegments));
@@ -1315,7 +1317,8 @@ public class ConcurrentReplaceAndAppendTest extends
IngestionTestBase
Collection<DataSegment> allUsedSegments = taskActionClient.submit(
new RetrieveUsedSegmentsAction(
TestDataSource.WIKI,
- Collections.singletonList(interval)
+ Collections.singletonList(interval),
+ SegmentDetail.all()
)
);
Assertions.assertEquals(Sets.newHashSet(expectedSegments),
Sets.newHashSet(allUsedSegments));
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/concurrent/ConcurrentReplaceAndStreamingAppendTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/concurrent/ConcurrentReplaceAndStreamingAppendTest.java
index 57c25a65060..6f31c07002d 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/concurrent/ConcurrentReplaceAndStreamingAppendTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/concurrent/ConcurrentReplaceAndStreamingAppendTest.java
@@ -64,6 +64,7 @@ import org.apache.druid.server.DruidNode;
import org.apache.druid.server.metrics.NoopServiceEmitter;
import org.apache.druid.tasklogs.NoopTaskLogs;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.SegmentId;
import org.apache.druid.timeline.partition.NumberedShardSpec;
import org.easymock.Capture;
@@ -704,7 +705,8 @@ public class ConcurrentReplaceAndStreamingAppendTest
extends IngestionTestBase
new RetrieveUsedSegmentsAction(
TestDataSource.WIKI,
ImmutableList.of(interval),
- visibility
+ visibility,
+ SegmentDetail.all()
)
);
Assertions.assertEquals(Sets.newHashSet(expectedSegments),
Sets.newHashSet(allUsedSegments));
@@ -823,7 +825,8 @@ public class ConcurrentReplaceAndStreamingAppendTest
extends IngestionTestBase
new RetrieveUsedSegmentsAction(
TestDataSource.WIKI,
ImmutableList.of(Intervals.ETERNITY),
- Segments.INCLUDING_OVERSHADOWED
+ Segments.INCLUDING_OVERSHADOWED,
+ SegmentDetail.all()
)
);
}
diff --git
a/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/IndexerTableInputSpecSlicer.java
b/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/IndexerTableInputSpecSlicer.java
index ddbfdde9854..825becde940 100644
---
a/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/IndexerTableInputSpecSlicer.java
+++
b/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/IndexerTableInputSpecSlicer.java
@@ -48,6 +48,7 @@ import org.apache.druid.query.SegmentDescriptor;
import org.apache.druid.query.filter.SegmentPruner;
import org.apache.druid.server.coordination.DruidServerMetadata;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.SegmentTimeline;
import org.apache.druid.timeline.TimelineLookup;
import org.apache.druid.timeline.VersionedIntervalTimeline;
@@ -221,8 +222,13 @@ public class IndexerTableInputSpecSlicer implements
InputSpecSlicer
if (intervals.isEmpty()) {
publishedUsedSegments = Collections.emptySet();
} else {
- publishedUsedSegments =
- taskActionClient.submit(new RetrieveUsedSegmentsAction(dataSource,
intervals));
+ publishedUsedSegments = taskActionClient.submit(
+ new RetrieveUsedSegmentsAction(
+ dataSource,
+ intervals,
+ SegmentDetail.none() // Even LoadSpec is not needed, because
workers fetch them from the Coordinator.
+ )
+ );
}
}
catch (IOException e) {
diff --git
a/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQReplaceTest.java
b/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQReplaceTest.java
index c51a7312c10..9acabcf1c8b 100644
---
a/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQReplaceTest.java
+++
b/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQReplaceTest.java
@@ -69,6 +69,7 @@ import
org.apache.druid.segment.virtual.ExpressionVirtualColumn;
import org.apache.druid.sql.calcite.util.CalciteTests;
import org.apache.druid.timeline.CompactionState;
import org.apache.druid.timeline.DataSegment;
+import org.apache.druid.timeline.SegmentDetail;
import org.apache.druid.timeline.SegmentId;
import org.apache.druid.timeline.partition.DimensionRangeShardSpec;
import org.apache.druid.timeline.partition.NumberedShardSpec;
@@ -144,7 +145,11 @@ public class MSQReplaceTest extends MSQTestBase
Mockito.doCallRealMethod()
.doReturn(ImmutableSet.of(existingDataSegment0,
existingDataSegment1))
.when(testTaskActionClient)
- .submit(new RetrieveUsedSegmentsAction("foo",
ImmutableList.of(Intervals.ETERNITY)));
+ .submit(new RetrieveUsedSegmentsAction(
+ "foo",
+ ImmutableList.of(Intervals.ETERNITY),
+ SegmentDetail.none()
+ ));
testIngestQuery().setSql(" REPLACE INTO foo OVERWRITE ALL "
+ "SELECT __time, m1 "
@@ -235,7 +240,11 @@ public class MSQReplaceTest extends MSQTestBase
Mockito.doCallRealMethod()
.doReturn(ImmutableSet.of(existingDataSegment0,
existingDataSegment1))
.when(testTaskActionClient)
- .submit(new RetrieveUsedSegmentsAction("foo",
ImmutableList.of(Intervals.ETERNITY)));
+ .submit(new RetrieveUsedSegmentsAction(
+ "foo",
+ ImmutableList.of(Intervals.ETERNITY),
+ SegmentDetail.none()
+ ));
testIngestQuery().setSql(" REPLACE INTO foo OVERWRITE ALL "
+ "SELECT __time, dim1, m1 "
@@ -315,7 +324,11 @@ public class MSQReplaceTest extends MSQTestBase
Mockito.doCallRealMethod()
.doReturn(ImmutableSet.of(existingDataSegment0,
existingDataSegment1))
.when(testTaskActionClient)
- .submit(new RetrieveUsedSegmentsAction("foo",
ImmutableList.of(Intervals.ETERNITY)));
+ .submit(new RetrieveUsedSegmentsAction(
+ "foo",
+ ImmutableList.of(Intervals.ETERNITY),
+ SegmentDetail.none()
+ ));
testIngestQuery().setSql(" REPLACE INTO foo OVERWRITE ALL "
+ "SELECT __time, dim1, m1 "
@@ -408,7 +421,11 @@ public class MSQReplaceTest extends MSQTestBase
Mockito.doCallRealMethod()
.doReturn(ImmutableSet.of(existingDataSegment0,
existingDataSegment1))
.when(testTaskActionClient)
- .submit(new RetrieveUsedSegmentsAction("foo",
ImmutableList.of(Intervals.ETERNITY)));
+ .submit(new RetrieveUsedSegmentsAction(
+ "foo",
+ ImmutableList.of(Intervals.ETERNITY),
+ SegmentDetail.none()
+ ));
testIngestQuery().setSql(" REPLACE INTO foo OVERWRITE ALL "
+ "SELECT __time, dim1, m1 "
@@ -494,7 +511,11 @@ public class MSQReplaceTest extends MSQTestBase
Mockito.doCallRealMethod()
.doReturn(ImmutableSet.of(existingDataSegment0,
existingDataSegment1))
.when(testTaskActionClient)
- .submit(new RetrieveUsedSegmentsAction("foo",
ImmutableList.of(Intervals.ETERNITY)));
+ .submit(new RetrieveUsedSegmentsAction(
+ "foo",
+ ImmutableList.of(Intervals.ETERNITY),
+ SegmentDetail.none()
+ ));
testIngestQuery().setSql(" REPLACE INTO foo OVERWRITE ALL "
+ "SELECT __time, dim1, m1 "
@@ -620,7 +641,11 @@ public class MSQReplaceTest extends MSQTestBase
Mockito.doCallRealMethod()
.doReturn(ImmutableSet.of(existingDataSegment0,
existingDataSegment1))
.when(testTaskActionClient)
- .submit(new RetrieveUsedSegmentsAction("foo",
ImmutableList.of(Intervals.ETERNITY)));
+ .submit(new RetrieveUsedSegmentsAction(
+ "foo",
+ ImmutableList.of(Intervals.ETERNITY),
+ SegmentDetail.none()
+ ));
testIngestQuery().setSql(" REPLACE INTO foo OVERWRITE ALL "
+ "SELECT __time, dim1, m1 "
@@ -1147,7 +1172,11 @@ public class MSQReplaceTest extends MSQTestBase
Mockito.doCallRealMethod()
.doReturn(ImmutableSet.of(existingDataSegment0,
existingDataSegment1))
.when(testTaskActionClient)
- .submit(new RetrieveUsedSegmentsAction("foo",
ImmutableList.of(Intervals.ETERNITY)));
+ .submit(new RetrieveUsedSegmentsAction(
+ "foo",
+ ImmutableList.of(Intervals.ETERNITY),
+ SegmentDetail.none()
+ ));
testIngestQuery().setSql(" REPLACE INTO foo "
@@ -1230,7 +1259,11 @@ public class MSQReplaceTest extends MSQTestBase
Mockito.doReturn(ImmutableSet.of(existingDataSegment0))
.when(testTaskActionClient)
- .submit(new RetrieveUsedSegmentsAction("foo",
ImmutableList.of(Intervals.of("2000-01-01/2000-03-01"))));
+ .submit(new RetrieveUsedSegmentsAction(
+ "foo",
+ ImmutableList.of(Intervals.of("2000-01-01/2000-03-01")),
+ SegmentDetail.none()
+ ));
testIngestQuery().setSql(" REPLACE INTO foo "
+ "OVERWRITE WHERE __time >= TIMESTAMP
'2000-01-01' AND __time < TIMESTAMP '2000-03-01' "
@@ -1319,7 +1352,8 @@ public class MSQReplaceTest extends MSQTestBase
.when(testTaskActionClient)
.submit(new RetrieveUsedSegmentsAction(
EasyMock.eq("foo"),
-
EasyMock.eq(ImmutableList.of(Intervals.of("2000-01-01/2002-01-01")))
+
EasyMock.eq(ImmutableList.of(Intervals.of("2000-01-01/2002-01-01"))),
+ SegmentDetail.none()
));
@@ -1499,7 +1533,8 @@ public class MSQReplaceTest extends MSQTestBase
.when(testTaskActionClient)
.submit(new RetrieveUsedSegmentsAction(
EasyMock.eq("foo"),
-
EasyMock.eq(ImmutableList.of(Intervals.of("2000-01-01/2000-03-01")))
+
EasyMock.eq(ImmutableList.of(Intervals.of("2000-01-01/2000-03-01"))),
+ SegmentDetail.none()
));
testIngestQuery().setSql(" REPLACE INTO foo "
@@ -1613,7 +1648,8 @@ public class MSQReplaceTest extends MSQTestBase
.when(testTaskActionClient)
.submit(new RetrieveUsedSegmentsAction(
EasyMock.eq("foo"),
- EasyMock.eq(ImmutableList.of(Intervals.of("2000/2002")))
+ EasyMock.eq(ImmutableList.of(Intervals.of("2000/2002"))),
+ SegmentDetail.none()
));
testIngestQuery().setSql(" REPLACE INTO foo "
@@ -1672,7 +1708,8 @@ public class MSQReplaceTest extends MSQTestBase
.when(testTaskActionClient)
.submit(new RetrieveUsedSegmentsAction(
EasyMock.eq("foo"),
- EasyMock.eq(ImmutableList.of(Intervals.ETERNITY))
+ EasyMock.eq(ImmutableList.of(Intervals.ETERNITY)),
+ SegmentDetail.none()
));
testIngestQuery().setSql(" REPLACE INTO foo "
@@ -2100,7 +2137,8 @@ public class MSQReplaceTest extends MSQTestBase
.when(testTaskActionClient)
.submit(new RetrieveUsedSegmentsAction(
EasyMock.eq("foo"),
- EasyMock.eq(ImmutableList.of(Intervals.of("1999/2002")))
+ EasyMock.eq(ImmutableList.of(Intervals.of("1999/2002"))),
+ SegmentDetail.none()
));
testIngestQuery().setSql(" REPLACE INTO foo "
@@ -2179,7 +2217,8 @@ public class MSQReplaceTest extends MSQTestBase
.when(testTaskActionClient)
.submit(new RetrieveUsedSegmentsAction(
EasyMock.eq("foo1"),
- EasyMock.eq(ImmutableList.of(Intervals.of("2000/2002")))
+ EasyMock.eq(ImmutableList.of(Intervals.of("2000/2002"))),
+ SegmentDetail.none()
));
List<Object[]> expectedResults = ImmutableList.of(
diff --git
a/processing/src/main/java/org/apache/druid/timeline/DataSegment.java
b/processing/src/main/java/org/apache/druid/timeline/DataSegment.java
index 0a82f8575a8..9e9208bfcd2 100644
--- a/processing/src/main/java/org/apache/druid/timeline/DataSegment.java
+++ b/processing/src/main/java/org/apache/druid/timeline/DataSegment.java
@@ -47,6 +47,7 @@ import org.joda.time.Interval;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
@@ -532,6 +533,32 @@ public class DataSegment implements
Comparable<DataSegment>, Overshadowable<Data
return
builder(this).indexingStateFingerprint(indexingStateFingerprint).build();
}
+ /**
+ * Returns a copy of this segment with every optional top-level field that
is not listed in {@code details} removed.
+ * The fields that are not optional are always retained; see {@link
SegmentDetail} for the full split.
+ *
+ * @param details the optional details to retain; null retains all details
and an empty set retains none
+ */
+ public DataSegment retainOnlyDetails(@Nullable final Set<SegmentDetail>
details)
+ {
+ if (details == null) {
+ return this;
+ } else {
+ return builder(this)
+ .dimensions(details.contains(SegmentDetail.DIMENSIONS) ? dimensions
: null)
+ .metrics(details.contains(SegmentDetail.METRICS) ? metrics : null)
+ .projections(details.contains(SegmentDetail.PROJECTIONS) ?
projections : null)
+ .clusterGroups(details.contains(SegmentDetail.CLUSTER_GROUPS) ?
clusterGroups : null)
+
.lastCompactionState(details.contains(SegmentDetail.COMPACTION_STATE) ?
lastCompactionState : null)
+ .loadSpec(details.contains(SegmentDetail.LOAD_SPEC) ? loadSpec :
null)
+ .totalRows(details.contains(SegmentDetail.ROW_COUNT) ? totalRows :
null)
+ .indexingStateFingerprint(
+ details.contains(SegmentDetail.INDEXING_STATE_FINGERPRINT) ?
indexingStateFingerprint : null
+ )
+ .build();
+ }
+ }
+
public DataSegment.Builder toBuilder()
{
return builder(this);
diff --git
a/processing/src/main/java/org/apache/druid/timeline/SegmentDetail.java
b/processing/src/main/java/org/apache/druid/timeline/SegmentDetail.java
new file mode 100644
index 00000000000..713340e08cb
--- /dev/null
+++ b/processing/src/main/java/org/apache/druid/timeline/SegmentDetail.java
@@ -0,0 +1,132 @@
+/*
+ * 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.timeline;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import org.apache.druid.java.util.common.IAE;
+
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.EnumSet;
+
+/**
+ * The optional top-level fields of a {@link DataSegment}: the ones that a
segment can be returned without, so that a
+ * caller that does not need them can avoid paying for them in serialization
size and heap.
+ * <p>
+ * The rest of {@link DataSegment} is never optional: {@link
DataSegment#getId()} (and therefore the dataSource,
+ * interval, and version), {@link DataSegment#getShardSpec()}, {@link
DataSegment#getBinaryVersion()}, and
+ * {@link DataSegment#getSize()} are always populated.
+ * <p>
+ * Use {@link DataSegment#retainOnlyDetails} to drop the details that are not
wanted.
+ *
+ * @see org.apache.druid.indexing.common.actions.RetrieveUsedSegmentsAction
+ */
+public enum SegmentDetail
+{
+ DIMENSIONS("dimensions"),
+ METRICS("metrics"),
+ PROJECTIONS("projections"),
+ CLUSTER_GROUPS("clusterGroups"),
+ COMPACTION_STATE("lastCompactionState"),
+ LOAD_SPEC("loadSpec"),
+ ROW_COUNT("totalRows"),
+ INDEXING_STATE_FINGERPRINT("indexingStateFingerprint");
+
+ private final String jsonName;
+
+ SegmentDetail(final String jsonName)
+ {
+ this.jsonName = jsonName;
+ }
+
+ /**
+ * All details.
+ */
+ public static EnumSet<SegmentDetail> all()
+ {
+ return EnumSet.allOf(SegmentDetail.class);
+ }
+
+ /**
+ * No details.
+ */
+ public static EnumSet<SegmentDetail> none()
+ {
+ return EnumSet.noneOf(SegmentDetail.class);
+ }
+
+ /**
+ * Parses a collection of names, skipping any name that this version of
Druid does not recognize.
+ */
+ @Nullable
+ public static EnumSet<SegmentDetail> fromNamesLenient(@Nullable final
Collection<String> names)
+ {
+ if (names == null) {
+ return null;
+ }
+
+ final EnumSet<SegmentDetail> retVal = none();
+ for (final String name : names) {
+ final SegmentDetail detail = fromNameLenient(name);
+ if (detail != null) {
+ retVal.add(detail);
+ }
+ }
+ return retVal;
+ }
+
+ /**
+ * Returns the {@link SegmentDetail} for a name, or throws {@link
IllegalArgumentException} if none exists.
+ */
+ @JsonCreator
+ public static SegmentDetail fromName(final String name)
+ {
+ final SegmentDetail detail = fromNameLenient(name);
+ if (detail == null) {
+ throw new IAE("No such SegmentDetail[%s]", name);
+ }
+ return detail;
+ }
+
+ /**
+ * Returns the {@link SegmentDetail} for a name, or null if none exists.
+ */
+ @Nullable
+ public static SegmentDetail fromNameLenient(@Nullable final String name)
+ {
+ if (name == null) {
+ return null;
+ }
+ for (final SegmentDetail detail : values()) {
+ if (detail.jsonName.equalsIgnoreCase(name)) {
+ return detail;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ @JsonValue
+ public String toString()
+ {
+ return jsonName;
+ }
+}
diff --git
a/processing/src/test/java/org/apache/druid/timeline/SegmentDetailTest.java
b/processing/src/test/java/org/apache/druid/timeline/SegmentDetailTest.java
new file mode 100644
index 00000000000..2bea51c8778
--- /dev/null
+++ b/processing/src/test/java/org/apache/druid/timeline/SegmentDetailTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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.timeline;
+
+import com.fasterxml.jackson.databind.InjectableValues;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.apache.druid.indexer.partitions.HashedPartitionsSpec;
+import org.apache.druid.jackson.DefaultObjectMapper;
+import org.apache.druid.java.util.common.Intervals;
+import org.apache.druid.segment.column.ColumnType;
+import org.apache.druid.segment.column.RowSignature;
+import org.apache.druid.timeline.partition.NumberedShardSpec;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+
+public class SegmentDetailTest
+{
+ private static final ObjectMapper MAPPER = new DefaultObjectMapper();
+ private static final DataSegment FULL_SEGMENT =
+ DataSegment
+ .builder(SegmentId.of("wiki", Intervals.of("2011/2012"), "v1", new
NumberedShardSpec(3, 5)))
+ .loadSpec(ImmutableMap.of("type", "local", "path", "/tmp/wiki"))
+ .dimensions(Arrays.asList("dim1", "dim2"))
+ .metrics(Arrays.asList("met1", "met2"))
+ .projections(Arrays.asList("proj1", "proj2"))
+ .clusterGroups(
+ new ClusterGroupTuples(
+ RowSignature.builder().add("dim1",
ColumnType.STRING).build(),
+ ImmutableList.of(ImmutableList.of("a"))
+ )
+ )
+ .lastCompactionState(
+ CompactionState.builder()
+ .partitionsSpec(new HashedPartitionsSpec(100,
null, ImmutableList.of("dim1")))
+ .build()
+ )
+ .binaryVersion(9)
+ .size(1234L)
+ .totalRows(42)
+ .indexingStateFingerprint("abcdef")
+ .build();
+
+ @BeforeAll
+ public static void setUpClass()
+ {
+ final InjectableValues.Std injectableValues = new InjectableValues.Std();
+ injectableValues.addValue(DataSegment.PruneSpecsHolder.class,
DataSegment.PruneSpecsHolder.DEFAULT);
+ MAPPER.setInjectableValues(injectableValues);
+ }
+
+ @Test
+ public void test_retainOnlyDetails_none()
+ {
+ final DataSegment retained =
FULL_SEGMENT.retainOnlyDetails(SegmentDetail.none());
+
+ // Mandatory fields survive.
+ Assertions.assertEquals(FULL_SEGMENT.getId(), retained.getId());
+ Assertions.assertEquals(FULL_SEGMENT.getShardSpec(),
retained.getShardSpec());
+ Assertions.assertEquals(FULL_SEGMENT.getBinaryVersion(),
retained.getBinaryVersion());
+ Assertions.assertEquals(FULL_SEGMENT.getSize(), retained.getSize());
+
+ // Optional details do not.
+ Assertions.assertNull(retained.getLoadSpec());
+ Assertions.assertEquals(Collections.emptyList(), retained.getDimensions());
+ Assertions.assertEquals(Collections.emptyList(), retained.getMetrics());
+ Assertions.assertNull(retained.getProjections());
+ Assertions.assertNull(retained.getClusterGroups());
+ Assertions.assertNull(retained.getLastCompactionState());
+ Assertions.assertNull(retained.getTotalRows());
+ Assertions.assertNull(retained.getIndexingStateFingerprint());
+ }
+
+ @Test
+ public void test_retainOnlyDetails_all()
+ {
+ final DataSegment retained =
FULL_SEGMENT.retainOnlyDetails(SegmentDetail.all());
+ assertAllFieldsEqual(FULL_SEGMENT, retained);
+ }
+
+ @Test
+ public void test_retainOnlyDetails_null()
+ {
+ // Equivalent to "retain all".
+ final DataSegment retained = FULL_SEGMENT.retainOnlyDetails(null);
+ assertAllFieldsEqual(FULL_SEGMENT, retained);
+ }
+
+ @Test
+ public void test_retainOnlyDetails_some()
+ {
+ final DataSegment retained =
+ FULL_SEGMENT.retainOnlyDetails(EnumSet.of(SegmentDetail.LOAD_SPEC,
SegmentDetail.ROW_COUNT));
+
+ Assertions.assertEquals(FULL_SEGMENT.getLoadSpec(),
retained.getLoadSpec());
+ Assertions.assertEquals(FULL_SEGMENT.getTotalRows(),
retained.getTotalRows());
+
+ Assertions.assertEquals(Collections.emptyList(), retained.getDimensions());
+ Assertions.assertEquals(Collections.emptyList(), retained.getMetrics());
+ Assertions.assertNull(retained.getProjections());
+ Assertions.assertNull(retained.getClusterGroups());
+ Assertions.assertNull(retained.getLastCompactionState());
+ Assertions.assertNull(retained.getIndexingStateFingerprint());
+ }
+
+ @Test
+ public void test_all()
+ {
+ Assertions.assertEquals(SegmentDetail.values().length,
SegmentDetail.all().size());
+ }
+
+ @Test
+ public void test_none()
+ {
+ Assertions.assertEquals(0, SegmentDetail.none().size());
+ }
+
+ @Test
+ public void test_fromNamesLenient()
+ {
+ Assertions.assertNull(SegmentDetail.fromNamesLenient(null));
+ Assertions.assertEquals(SegmentDetail.none(),
SegmentDetail.fromNamesLenient(Collections.emptyList()));
+ Assertions.assertEquals(
+ EnumSet.of(SegmentDetail.LOAD_SPEC, SegmentDetail.ROW_COUNT),
+ SegmentDetail.fromNamesLenient(ImmutableList.of("loadSpec",
"a_detail_from_the_future", "totalRows"))
+ );
+ }
+
+ @Test
+ public void test_serde() throws Exception
+ {
+ for (final SegmentDetail detail : SegmentDetail.values()) {
+ final String json = MAPPER.writeValueAsString(detail);
+ Assertions.assertEquals("\"" + detail + "\"", json);
+ Assertions.assertEquals(detail, MAPPER.readValue(json,
SegmentDetail.class));
+ }
+
+ Assertions.assertEquals(SegmentDetail.LOAD_SPEC,
MAPPER.readValue("\"loadSpec\"", SegmentDetail.class));
+ Assertions.assertThrows(Exception.class, () ->
MAPPER.readValue("\"nonexistent\"", SegmentDetail.class));
+ }
+
+ @Test
+ public void test_serde_ofDataSegment() throws Exception
+ {
+ final DataSegment retained =
FULL_SEGMENT.retainOnlyDetails(EnumSet.of(SegmentDetail.LOAD_SPEC));
+ final DataSegment deserialized =
MAPPER.readValue(MAPPER.writeValueAsString(retained), DataSegment.class);
+ assertAllFieldsEqual(retained, deserialized);
+ Assertions.assertEquals(FULL_SEGMENT.getLoadSpec(),
deserialized.getLoadSpec());
+ }
+
+ private static void assertAllFieldsEqual(final DataSegment expected, final
DataSegment actual)
+ {
+ Assertions.assertEquals(expected.getId(), actual.getId(), "id");
+ Assertions.assertEquals(expected.getShardSpec(), actual.getShardSpec(),
"shardSpec");
+ Assertions.assertEquals(expected.getBinaryVersion(),
actual.getBinaryVersion(), "binaryVersion");
+ Assertions.assertEquals(expected.getSize(), actual.getSize(), "size");
+ Assertions.assertEquals(expected.getLoadSpec(), actual.getLoadSpec(),
"loadSpec");
+ Assertions.assertEquals(expected.getDimensions(), actual.getDimensions(),
"dimensions");
+ Assertions.assertEquals(expected.getMetrics(), actual.getMetrics(),
"metrics");
+ Assertions.assertEquals(expected.getProjections(),
actual.getProjections(), "projections");
+ Assertions.assertEquals(expected.getClusterGroups(),
actual.getClusterGroups(), "clusterGroups");
+ Assertions.assertEquals(expected.getLastCompactionState(),
actual.getLastCompactionState(), "lastCompactionState");
+ Assertions.assertEquals(expected.getTotalRows(), actual.getTotalRows(),
"totalRows");
+ Assertions.assertEquals(
+ expected.getIndexingStateFingerprint(),
+ actual.getIndexingStateFingerprint(),
+ "indexingStateFingerprint"
+ );
+ }
+}
diff --git
a/server/src/main/java/org/apache/druid/segment/realtime/appenderator/BaseAppenderatorDriver.java
b/server/src/main/java/org/apache/druid/segment/realtime/appenderator/BaseAppenderatorDriver.java
index b45c11bee7e..b0ec16c7f70 100644
---
a/server/src/main/java/org/apache/druid/segment/realtime/appenderator/BaseAppenderatorDriver.java
+++
b/server/src/main/java/org/apache/druid/segment/realtime/appenderator/BaseAppenderatorDriver.java
@@ -36,6 +36,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import org.apache.druid.data.input.Committer;
import org.apache.druid.data.input.InputRow;
+import org.apache.druid.error.DruidException;
import org.apache.druid.indexing.overlord.SegmentPublishResult;
import org.apache.druid.java.util.common.ISE;
import org.apache.druid.java.util.common.Intervals;
@@ -687,8 +688,8 @@ public abstract class BaseAppenderatorDriver implements
Closeable
// Clean up pushed segments if they are physically
disjoint from the published ones (this means
// they were probably pushed by a replica, and with the
unique paths option).
final boolean physicallyDisjoint = Sets.intersection(
-
publishedSegments.stream().map(DataSegment::getLoadSpec).collect(Collectors.toSet()),
-
ourSegments.stream().map(DataSegment::getLoadSpec).collect(Collectors.toSet())
+ getLoadSpecs("published", publishedSegments),
+ getLoadSpecs("ours", ourSegments)
).isEmpty();
if (physicallyDisjoint) {
@@ -763,6 +764,26 @@ public abstract class BaseAppenderatorDriver implements
Closeable
executor.shutdownNow();
}
+ /**
+ * Returns a Set of {@link DataSegment#getLoadSpec()} from the provided
segments. Throws if any loadspecs
+ * are null, which may indicate that they were incorrectly pruned by {@link
DataSegment#retainOnlyDetails(Set)}.
+ *
+ * @param label label for the error message, if it fires
+ * @param dataSegments the segments
+ */
+ private static Set<Map<String, Object>> getLoadSpecs(String label,
Iterable<DataSegment> dataSegments)
+ {
+ final Set<Map<String, Object>> loadSpecs = new HashSet<>();
+ for (final DataSegment segment : dataSegments) {
+ final Map<String, Object> loadSpec = segment.getLoadSpec();
+ if (loadSpec == null) {
+ throw DruidException.defensive("Segment[%s] (%s) missing loadSpec",
segment.getId(), label);
+ }
+ loadSpecs.add(loadSpec);
+ }
+ return loadSpecs;
+ }
+
/**
* Wrapped committer for BaseAppenderatorDriver. Used in only {@link
StreamAppenderatorDriver} because batch ingestion
* doesn't need committing intermediate states.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]