This is an automated email from the ASF dual-hosted git repository.
dataroaring pushed a commit to branch branch-3.0
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-3.0 by this push:
new cb9c5076ef9 branch-3.0: [improvement](statistics)Improve analyze
partition column and key column corner case. #48757 (#49100)
cb9c5076ef9 is described below
commit cb9c5076ef9ab02762ca04a0649dcf2594c1cd7f
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sun Mar 16 19:31:18 2025 +0800
branch-3.0: [improvement](statistics)Improve analyze partition column and
key column corner case. #48757 (#49100)
Cherry-picked from #48757
Co-authored-by: James <[email protected]>
---
.../apache/doris/statistics/OlapAnalysisTask.java | 89 +++++++-
.../doris/statistics/OlapAnalysisTaskTest.java | 235 ++++++++++++++++++++-
2 files changed, 319 insertions(+), 5 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/statistics/OlapAnalysisTask.java
b/fe/fe-core/src/main/java/org/apache/doris/statistics/OlapAnalysisTask.java
index ba2b2770a5d..5cee3db36a8 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/statistics/OlapAnalysisTask.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/statistics/OlapAnalysisTask.java
@@ -25,6 +25,10 @@ import org.apache.doris.catalog.MaterializedIndex;
import org.apache.doris.catalog.MaterializedIndexMeta;
import org.apache.doris.catalog.OlapTable;
import org.apache.doris.catalog.Partition;
+import org.apache.doris.catalog.PartitionInfo;
+import org.apache.doris.catalog.PartitionKey;
+import org.apache.doris.catalog.PartitionType;
+import org.apache.doris.catalog.RangePartitionItem;
import org.apache.doris.common.Config;
import org.apache.doris.common.DdlException;
import org.apache.doris.common.FeConstants;
@@ -36,6 +40,8 @@ import org.apache.doris.statistics.util.StatisticsUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import org.apache.commons.text.StringSubstitutor;
@@ -63,6 +69,7 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
private boolean partitionColumnSampleTooManyRows = false;
private boolean scanFullTable = false;
private static final long MAXIMUM_SAMPLE_ROWS = 1_000_000_000;
+ public static final long NO_SKIP_TABLET_ID = -1;
@VisibleForTesting
public OlapAnalysisTask() {
@@ -172,6 +179,8 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
// Sort the partitions to get stable result.
List<Partition> sortedPartitions =
olapTable.getPartitions().stream().sorted(
Comparator.comparing(Partition::getName)).collect(Collectors.toList());
+ long largeTabletId = 0;
+ long largeTabletRows = Long.MAX_VALUE;
for (Partition p : sortedPartitions) {
MaterializedIndex materializedIndex = info.indexId == -1 ?
p.getBaseIndex() : p.getIndex(info.indexId);
if (materializedIndex == null) {
@@ -190,8 +199,20 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
for (int i = 0; i < tabletCounts; i++) {
int seekTid = (int) ((i + seek) % ids.size());
long tabletId = ids.get(seekTid);
- sampleTabletIds.add(tabletId);
long tabletRows =
materializedIndex.getTablet(tabletId).getMinReplicaRowCount(p.getVisibleVersion());
+ if (tabletRows > MAXIMUM_SAMPLE_ROWS) {
+ LOG.debug("Found one large tablet id {} in table {}, rows
{}",
+ largeTabletId, tbl.getName(), largeTabletRows);
+ // Skip very large tablet and record the smallest large
tablet id and row count.
+ if (tabletRows < largeTabletRows) {
+ LOG.debug("Current smallest large tablet id {} in
table {}, rows {}",
+ largeTabletId, tbl.getName(), largeTabletRows);
+ largeTabletId = tabletId;
+ largeTabletRows = tabletRows;
+ }
+ continue;
+ }
+ sampleTabletIds.add(tabletId);
if (tabletRows > 0) {
selectedRows += tabletRows;
// For regular column, will stop adding more tablets when
selected tablets'
@@ -208,6 +229,13 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
break;
}
}
+ // If we skipped some large tablets and this cause the sampled rows is
not enough, we add the large tablet back.
+ if (!enough && largeTabletId != 0) {
+ sampleTabletIds.add(largeTabletId);
+ selectedRows += largeTabletRows;
+ LOG.info("Add large tablet {} in table {} back, with rows {}",
+ largeTabletId, tbl.getName(), largeTabletRows);
+ }
if (selectedRows < targetSampleRows) {
scanFullTable = true;
} else if (forPartitionColumn && selectedRows > MAXIMUM_SAMPLE_ROWS) {
@@ -215,7 +243,7 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
partitionColumnSampleTooManyRows = true;
sampleTabletIds.clear();
Collections.shuffle(sortedPartitions);
- selectedRows = pickSamplePartition(sortedPartitions,
sampleTabletIds);
+ selectedRows = pickSamplePartition(sortedPartitions,
sampleTabletIds, getSkipPartitionId(sortedPartitions));
} else if (col.isKey() && selectedRows > MAXIMUM_SAMPLE_ROWS) {
// For key column, if a single tablet contains too many rows, need
to use limit to control rows to read.
// In most cases, a single tablet shouldn't contain more than
MAXIMUM_SAMPLE_ROWS, in this case, we
@@ -372,12 +400,65 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
}
}
- protected long pickSamplePartition(List<Partition> partitions, List<Long>
pickedTabletIds) {
- long averageRowsPerPartition = tbl.getRowCount() / partitions.size();
+ // For partition tables with single time type partition column, we'd
better to skip sampling the partition
+ // that contains all the history data. Because this partition may contain
many old data which is not
+ // visited by most queries. To sample this partition may cause the
statistics not accurate.
+ // For example, one table has 366 partitions, partition 1 ~ 365 store date
for each day of the year from now.
+ // Partition 0 stores all the history data earlier than 1 year. We want to
skip sampling partition 0.
+ protected long getSkipPartitionId(List<Partition> partitions) {
+ if (partitions == null || partitions.size() <
StatisticsUtil.getPartitionSampleCount()) {
+ return NO_SKIP_TABLET_ID;
+ }
+ PartitionInfo partitionInfo = ((OlapTable) tbl).getPartitionInfo();
+ if (!PartitionType.RANGE.equals(partitionInfo.getType())) {
+ return NO_SKIP_TABLET_ID;
+ }
+ if (partitionInfo.getPartitionColumns().size() != 1) {
+ return NO_SKIP_TABLET_ID;
+ }
+ Column column = partitionInfo.getPartitionColumns().get(0);
+ if (!column.getType().isDateType()) {
+ return NO_SKIP_TABLET_ID;
+ }
+ PartitionKey lowestKey = PartitionKey.createMaxPartitionKey();
+ long lowestPartitionId = -1;
+ for (Partition p : partitions) {
+ RangePartitionItem item = (RangePartitionItem)
partitionInfo.getItem(p.getId());
+ Range<PartitionKey> items = item.getItems();
+ if (!items.hasLowerBound()) {
+ lowestPartitionId = p.getId();
+ break;
+ }
+ if (items.lowerEndpoint().compareTo(lowestKey) < 0) {
+ lowestKey = items.lowerEndpoint();
+ lowestPartitionId = p.getId();
+ }
+ }
+ return lowestPartitionId;
+ }
+
+ protected long pickSamplePartition(List<Partition> partitions, List<Long>
pickedTabletIds, long skipPartitionId) {
+ Partition partition = ((OlapTable) tbl).getPartition(skipPartitionId);
+ long averageRowsPerPartition;
+ if (partition != null) {
+ LOG.debug("Going to skip partition {} in table {}",
skipPartitionId, tbl.getName());
+ // If we want to skip the oldest partition, calculate the average
rows per partition value without
+ // the oldest partition, otherwise if the oldest partition is very
large, we may skip all partitions.
+ // Because we only pick partitions which meet partitionRowCount >=
averageRowsPerPartition.
+ Preconditions.checkNotNull(partitions, "Partition list of table "
+ tbl.getName() + " is null");
+ Preconditions.checkState(partitions.size() > 1, "Too few
partitions in " + tbl.getName());
+ averageRowsPerPartition = (tbl.getRowCount() -
partition.getRowCount()) / (partitions.size() - 1);
+ } else {
+ averageRowsPerPartition = tbl.getRowCount() / partitions.size();
+ }
long indexId = info.indexId == -1 ? ((OlapTable) tbl).getBaseIndexId()
: info.indexId;
long pickedRows = 0;
int pickedPartitionCount = 0;
for (Partition p : partitions) {
+ if (skipPartitionId == p.getId()) {
+ LOG.info("Partition {} in table {} skipped", skipPartitionId,
tbl.getName());
+ continue;
+ }
long partitionRowCount = p.getRowCount();
if (partitionRowCount >= averageRowsPerPartition) {
pickedRows += partitionRowCount;
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/statistics/OlapAnalysisTaskTest.java
b/fe/fe-core/src/test/java/org/apache/doris/statistics/OlapAnalysisTaskTest.java
index 9bda917b34e..1404855f092 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/statistics/OlapAnalysisTaskTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/statistics/OlapAnalysisTaskTest.java
@@ -17,26 +17,34 @@
package org.apache.doris.statistics;
+import org.apache.doris.analysis.PartitionValue;
import org.apache.doris.analysis.TableSample;
import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.DataProperty;
import org.apache.doris.catalog.DatabaseIf;
import org.apache.doris.catalog.MaterializedIndex;
import org.apache.doris.catalog.OlapTable;
import org.apache.doris.catalog.Partition;
import org.apache.doris.catalog.PartitionInfo;
+import org.apache.doris.catalog.PartitionKey;
import org.apache.doris.catalog.PartitionType;
import org.apache.doris.catalog.PrimitiveType;
import org.apache.doris.catalog.RandomDistributionInfo;
+import org.apache.doris.catalog.RangePartitionItem;
import org.apache.doris.catalog.TableIf;
+import org.apache.doris.catalog.Tablet;
import org.apache.doris.catalog.Type;
+import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.Pair;
import org.apache.doris.datasource.CatalogIf;
import org.apache.doris.statistics.AnalysisInfo.AnalysisMethod;
import org.apache.doris.statistics.AnalysisInfo.JobType;
import org.apache.doris.statistics.util.StatisticsUtil;
+import org.apache.doris.thrift.TStorageMedium;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Range;
import mockit.Expectations;
import mockit.Mock;
import mockit.MockUp;
@@ -45,6 +53,7 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -279,7 +288,7 @@ public class OlapAnalysisTaskTest {
return ret;
}
};
- long rows = task.pickSamplePartition(partitions, ids);
+ long rows = task.pickSamplePartition(partitions, ids, 0);
Assertions.assertEquals(900000000, rows);
Assertions.assertEquals(4, ids.size());
Assertions.assertEquals(0, ids.get(0));
@@ -419,4 +428,228 @@ public class OlapAnalysisTaskTest {
Assertions.assertEquals("2000000000", params.get("ndvFunction"));
Assertions.assertEquals("limit 1000000000", params.get("limit"));
}
+
+ @Test
+ public void testGetSkipPartitionId(@Mocked OlapTable tableIf) throws
AnalysisException {
+ // test null partition list
+ OlapAnalysisTask task = new OlapAnalysisTask();
+ long skipPartitionId = task.getSkipPartitionId(null);
+ Assertions.assertEquals(OlapAnalysisTask.NO_SKIP_TABLET_ID,
skipPartitionId);
+
+ // test empty partition list
+ List<Partition> partitions = Lists.newArrayList();
+ skipPartitionId = task.getSkipPartitionId(partitions);
+ Assertions.assertEquals(OlapAnalysisTask.NO_SKIP_TABLET_ID,
skipPartitionId);
+
+ // test partition list item less than session variable
partition_sample_count
+ Partition p1 = new Partition(1, "p1", new MaterializedIndex(), new
RandomDistributionInfo());
+ partitions.add(p1);
+ skipPartitionId = task.getSkipPartitionId(partitions);
+ Assertions.assertEquals(OlapAnalysisTask.NO_SKIP_TABLET_ID,
skipPartitionId);
+
+ partitions.clear();
+ int partitionSampleCount = StatisticsUtil.getPartitionSampleCount();
+ for (int i = 1; i <= partitionSampleCount; i++) {
+ Partition p = new Partition(i, "p" + i, new MaterializedIndex(),
new RandomDistributionInfo());
+ partitions.add(p);
+ }
+
+ // Test List partition return NO_SKIP_TABLET_ID
+ new MockUp<OlapTable>() {
+ @Mock
+ public PartitionInfo getPartitionInfo() {
+ return new PartitionInfo(PartitionType.LIST);
+ }
+ };
+ task.tbl = tableIf;
+ skipPartitionId = task.getSkipPartitionId(partitions);
+ Assertions.assertEquals(OlapAnalysisTask.NO_SKIP_TABLET_ID,
skipPartitionId);
+
+ // Test Unpartition return NO_SKIP_TABLET_ID
+ new MockUp<OlapTable>() {
+ @Mock
+ public PartitionInfo getPartitionInfo() {
+ return new PartitionInfo(PartitionType.UNPARTITIONED);
+ }
+ };
+ skipPartitionId = task.getSkipPartitionId(partitions);
+ Assertions.assertEquals(OlapAnalysisTask.NO_SKIP_TABLET_ID,
skipPartitionId);
+
+ // Test more than 1 partition column return NO_SKIP_TABLET_ID
+ new MockUp<OlapTable>() {
+ @Mock
+ public PartitionInfo getPartitionInfo() {
+ ArrayList<Column> columns = Lists.newArrayList();
+ columns.add(new Column("col1", PrimitiveType.DATEV2));
+ columns.add(new Column("col2", PrimitiveType.DATEV2));
+ return new PartitionInfo(PartitionType.RANGE, columns);
+ }
+ };
+ skipPartitionId = task.getSkipPartitionId(partitions);
+ Assertions.assertEquals(OlapAnalysisTask.NO_SKIP_TABLET_ID,
skipPartitionId);
+
+ // Test not Date type return NO_SKIP_TABLET_ID
+ new MockUp<OlapTable>() {
+ @Mock
+ public PartitionInfo getPartitionInfo() {
+ ArrayList<Column> columns = Lists.newArrayList();
+ columns.add(new Column("col1", PrimitiveType.STRING));
+ return new PartitionInfo(PartitionType.RANGE, columns);
+ }
+ };
+ skipPartitionId = task.getSkipPartitionId(partitions);
+ Assertions.assertEquals(OlapAnalysisTask.NO_SKIP_TABLET_ID,
skipPartitionId);
+
+ // Test return the partition id with the oldest date range.
+ ArrayList<Column> columns = Lists.newArrayList();
+ Column col1 = new Column("col1", PrimitiveType.DATEV2);
+ columns.add(col1);
+ PartitionInfo partitionInfo = new PartitionInfo(PartitionType.RANGE,
columns);
+
+ List<PartitionValue> lowKey = Lists.newArrayList();
+ lowKey.add(new PartitionValue("2025-01-01"));
+ List<PartitionValue> highKey = Lists.newArrayList();
+ highKey.add(new PartitionValue("2025-01-02"));
+ Range<PartitionKey> range1 =
Range.closedOpen(PartitionKey.createPartitionKey(lowKey, columns),
+ PartitionKey.createPartitionKey(highKey, columns));
+ RangePartitionItem item1 = new RangePartitionItem(range1);
+
+ lowKey.clear();
+ lowKey.add(new PartitionValue("2024-11-01"));
+ highKey.clear();
+ highKey.add(new PartitionValue("2024-11-02"));
+ Range<PartitionKey> range2 =
Range.closedOpen(PartitionKey.createPartitionKey(lowKey, columns),
+ PartitionKey.createPartitionKey(highKey, columns));
+ RangePartitionItem item2 = new RangePartitionItem(range2);
+
+ lowKey.clear();
+ lowKey.add(new PartitionValue("2025-02-13"));
+ highKey.clear();
+ highKey.add(new PartitionValue("2025-02-14"));
+ Range<PartitionKey> range3 =
Range.closedOpen(PartitionKey.createPartitionKey(lowKey, columns),
+ PartitionKey.createPartitionKey(highKey, columns));
+ RangePartitionItem item3 = new RangePartitionItem(range3);
+
+ partitionInfo.addPartition(1, false, item1, new
DataProperty(TStorageMedium.HDD), null, false, false);
+ partitionInfo.addPartition(2, false, item2, new
DataProperty(TStorageMedium.HDD), null, false, false);
+ partitionInfo.addPartition(3, false, item3, new
DataProperty(TStorageMedium.HDD), null, false, false);
+
+ new MockUp<OlapTable>() {
+ @Mock
+ public PartitionInfo getPartitionInfo() {
+ return partitionInfo;
+ }
+ };
+ new MockUp<StatisticsUtil>() {
+ @Mock
+ public int getPartitionSampleCount() {
+ return 3;
+ }
+ };
+ partitions.clear();
+ for (int i = 1; i <= 3; i++) {
+ Partition p = new Partition(i, "p" + i, new MaterializedIndex(),
new RandomDistributionInfo());
+ partitions.add(p);
+ }
+ skipPartitionId = task.getSkipPartitionId(partitions);
+ Assertions.assertEquals(2, skipPartitionId);
+
+ // Test less than partition
+ partitions.add(new Partition(4, "p4", new MaterializedIndex(), new
RandomDistributionInfo()));
+ partitions.add(new Partition(5, "p5", new MaterializedIndex(), new
RandomDistributionInfo()));
+ new MockUp<StatisticsUtil>() {
+ @Mock
+ public int getPartitionSampleCount() {
+ return 5;
+ }
+ };
+ highKey.clear();
+ highKey.add(new PartitionValue("2024-01-01"));
+ Range<PartitionKey> range4 =
Range.lessThan(PartitionKey.createPartitionKey(highKey, columns));
+ RangePartitionItem item4 = new RangePartitionItem(range4);
+ partitionInfo.addPartition(4, false, item4, new
DataProperty(TStorageMedium.HDD), null, false, false);
+ lowKey.clear();
+ lowKey.add(new PartitionValue("2024-03-13"));
+ highKey.clear();
+ highKey.add(new PartitionValue("2024-03-14"));
+ Range<PartitionKey> range5 =
Range.closedOpen(PartitionKey.createPartitionKey(lowKey, columns),
+ PartitionKey.createPartitionKey(highKey, columns));
+ RangePartitionItem item5 = new RangePartitionItem(range5);
+ partitionInfo.addPartition(5, false, item5, new
DataProperty(TStorageMedium.HDD), null, false, false);
+ skipPartitionId = task.getSkipPartitionId(partitions);
+ Assertions.assertEquals(4, skipPartitionId);
+ }
+
+ @Test
+ public void testGetSampleTablets(@Mocked MaterializedIndex index, @Mocked
Tablet t) {
+ OlapAnalysisTask task = new OlapAnalysisTask();
+ task.tbl = new OlapTable();
+ task.col = new Column("col1", PrimitiveType.STRING);
+ task.info = new AnalysisInfoBuilder().setIndexId(-1L).build();
+ task.tableSample = new TableSample(false, 4000000L, 0L);
+ List<Partition> partitions = Lists.newArrayList();
+ partitions.add(new Partition(1, "p1", new MaterializedIndex(), new
RandomDistributionInfo()));
+ final int[] i = {0};
+ long[] tabletsRowCount = {1100000000, 100000000};
+ List<Long> ret = Lists.newArrayList();
+ ret.add(10001L);
+ ret.add(10002L);
+ new MockUp<OlapAnalysisTask>() {
+ @Mock
+ protected long getSampleRows() {
+ return 4000000;
+ }
+ };
+ new MockUp<OlapTable>() {
+ @Mock
+ boolean isPartitionColumn(String columnName) {
+ return false;
+ }
+
+ @Mock
+ public Collection<Partition> getPartitions() {
+ return partitions;
+ }
+ };
+ new MockUp<Partition>() {
+ @Mock
+ public MaterializedIndex getBaseIndex() {
+ return index;
+ }
+ };
+ new MockUp<MaterializedIndex>() {
+ @Mock
+ public List<Long> getTabletIdsInOrder() {
+ return ret;
+ }
+
+ @Mock
+ public long getRowCount() {
+ return 1_200_000_000L;
+ }
+
+ @Mock
+ public Tablet getTablet(long tabletId) {
+ return t;
+ }
+ };
+ new MockUp<Tablet>() {
+ @Mock
+ public long getMinReplicaRowCount(long version) {
+ return tabletsRowCount[i[0]++];
+ }
+ };
+ // Test set large tablet id back if it doesn't pick enough sample rows.
+ Pair<List<Long>, Long> sampleTablets = task.getSampleTablets();
+ Assertions.assertEquals(1, sampleTablets.first.size());
+ Assertions.assertEquals(10001, sampleTablets.first.get(0));
+ Assertions.assertEquals(1100000000L, sampleTablets.second);
+
+ // Test normal pick
+ task.tableSample = new TableSample(false, 4000000L, 1L);
+ sampleTablets = task.getSampleTablets();
+ Assertions.assertEquals(1, sampleTablets.first.size());
+ Assertions.assertEquals(10002, sampleTablets.first.get(0));
+ Assertions.assertEquals(100000000L, sampleTablets.second);
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]