This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new aa135178aaf branch-4.1: [opt](compaction) support tablet-level
compaction through SQL #66611 (#67203)
aa135178aaf is described below
commit aa135178aaf1ac0d012071d32838e748144a176f
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Aug 28 01:34:05 2026 +0800
branch-4.1: [opt](compaction) support tablet-level compaction through SQL
#66611 (#67203)
Cherry-picked from #66611
Co-authored-by: dzr171712 <[email protected]>
---
.../antlr4/org/apache/doris/nereids/DorisParser.g4 | 1 +
.../main/java/org/apache/doris/catalog/Env.java | 49 +++++++++
.../org/apache/doris/cloud/catalog/CloudEnv.java | 66 +++++++++++
.../doris/nereids/parser/LogicalPlanBuilder.java | 8 ++
.../apache/doris/nereids/trees/plans/PlanType.java | 1 +
.../plans/commands/AdminCompactTabletCommand.java | 121 +++++++++++++++++++++
.../trees/plans/visitor/CommandVisitor.java | 5 +
.../parser/AdminCompactTabletParserTest.java | 45 ++++++++
.../test_cloud_admin_compact_tablet.groovy | 101 +++++++++++++++++
.../compaction/test_admin_compact_tablet.groovy | 101 +++++++++++++++++
10 files changed, 498 insertions(+)
diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
index e7305beeb02..b7689c07a62 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
@@ -654,6 +654,7 @@ supportedAdminStatement
| ADMIN DIAGNOSE TABLET tabletId=INTEGER_VALUE
#adminDiagnoseTablet
| ADMIN SHOW REPLICA STATUS FROM baseTableRef (WHERE STATUS EQ|NEQ
STRING_LITERAL)? #adminShowReplicaStatus
| ADMIN COMPACT TABLE baseTableRef (WHERE TYPE EQ STRING_LITERAL)?
#adminCompactTable
+ | ADMIN COMPACT TABLET tabletId=INTEGER_VALUE WHERE TYPE EQ STRING_LITERAL
#adminCompactTablet
| ADMIN CHECK tabletList properties=propertyClause?
#adminCheckTablets
| ADMIN SHOW TABLET STORAGE FORMAT VERBOSE?
#adminShowTabletStorageFormat
| ADMIN SET (FRONTEND | (ALL FRONTENDS)) CONFIG
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index 3dad10564d1..2c79c25f417 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -7369,6 +7369,55 @@ public class Env {
return result;
}
+ public void compactTablet(long tabletId, String type) throws DdlException {
+ TabletMeta tabletMeta =
getCurrentInvertedIndex().getTabletMeta(tabletId);
+ if (tabletMeta == null) {
+ throw new DdlException("Unknown tablet: " + tabletId);
+ }
+
+ Database db = getInternalCatalog().getDbNullable(tabletMeta.getDbId());
+ if (db == null) {
+ throw new DdlException("Unknown database for tablet: " + tabletId);
+ }
+ Table table = db.getTableNullable(tabletMeta.getTableId());
+ if (!(table instanceof OlapTable)) {
+ throw new DdlException("Unknown OLAP table for tablet: " +
tabletId);
+ }
+ OlapTable olapTable = (OlapTable) table;
+
+ AgentBatchTask batchTask = new AgentBatchTask();
+ olapTable.readLock();
+ try {
+ Partition partition =
olapTable.getPartition(tabletMeta.getPartitionId());
+ if (partition == null) {
+ throw new DdlException("Unknown partition for tablet: " +
tabletId);
+ }
+ MaterializedIndex index =
partition.getIndex(tabletMeta.getIndexId());
+ if (index == null || !index.getState().isVisible()) {
+ throw new DdlException("Tablet " + tabletId + " is not in a
visible index");
+ }
+ Tablet tablet = index.getTablet(tabletId);
+ if (tablet == null) {
+ throw new DdlException("Tablet " + tabletId + " does not
belong to its metadata index");
+ }
+
+ int schemaHash = olapTable.getSchemaHashByIndexId(index.getId());
+ LOG.info("Tablet compaction. database: {}, table: {}, tablet: {},
type: {}",
+ db.getFullName(), olapTable.getName(), tabletId, type);
+ for (Replica replica : tablet.getReplicas()) {
+ batchTask.addTask(new
CompactionTask(replica.getBackendIdWithoutException(), db.getId(),
+ olapTable.getId(), partition.getId(), index.getId(),
tabletId, schemaHash, type));
+ }
+ } finally {
+ olapTable.readUnlock();
+ }
+
+ if (batchTask.getTaskNum() == 0) {
+ throw new DdlException("No replica found for tablet: " + tabletId);
+ }
+ AgentTaskExecutor.submit(batchTask);
+ }
+
public void compactTable(String dbName, String tableName, String type,
List<String> partitionNames)
throws DdlException {
Database db = getInternalCatalog().getDbOrDdlException(dbName);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java
b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java
index f4393b36e72..b482ddf260e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java
@@ -26,7 +26,9 @@ import
org.apache.doris.catalog.MaterializedIndex.IndexExtState;
import org.apache.doris.catalog.OlapTable;
import org.apache.doris.catalog.Partition;
import org.apache.doris.catalog.Replica;
+import org.apache.doris.catalog.Table;
import org.apache.doris.catalog.Tablet;
+import org.apache.doris.catalog.TabletMeta;
import org.apache.doris.cloud.CacheHotspotManager;
import org.apache.doris.cloud.CloudWarmUpJob;
import org.apache.doris.cloud.CloudWarmUpJob.JobState;
@@ -501,6 +503,70 @@ public class CloudEnv extends Env {
}
}
+ @Override
+ public void compactTablet(long tabletId, String type) throws DdlException {
+ TabletMeta tabletMeta =
Env.getCurrentInvertedIndex().getTabletMeta(tabletId);
+ if (tabletMeta == null) {
+ throw new DdlException("Unknown tablet: " + tabletId);
+ }
+
+ Database db = getInternalCatalog().getDbNullable(tabletMeta.getDbId());
+ if (db == null) {
+ throw new DdlException("Unknown database for tablet: " + tabletId);
+ }
+ Table table = db.getTableNullable(tabletMeta.getTableId());
+ if (!(table instanceof OlapTable)) {
+ throw new DdlException("Unknown OLAP table for tablet: " +
tabletId);
+ }
+ OlapTable olapTable = (OlapTable) table;
+
+ List<PendingCloudCompactionTablet> pending = new ArrayList<>();
+ olapTable.readLock();
+ try {
+ Partition partition =
olapTable.getPartition(tabletMeta.getPartitionId());
+ if (partition == null) {
+ throw new DdlException("Unknown partition for tablet: " +
tabletId);
+ }
+ MaterializedIndex index =
partition.getIndex(tabletMeta.getIndexId());
+ if (index == null || !index.getState().isVisible()) {
+ throw new DdlException("Tablet " + tabletId + " is not in a
visible index");
+ }
+ Tablet tablet = index.getTablet(tabletId);
+ if (tablet == null) {
+ throw new DdlException("Tablet " + tabletId + " does not
belong to its metadata index");
+ }
+
+ int schemaHash = olapTable.getSchemaHashByIndexId(index.getId());
+ LOG.info("Cloud tablet compaction. database={}, table={},
tablet={}, type={}",
+ db.getFullName(), olapTable.getName(), tabletId, type);
+ for (Replica replica : tablet.getReplicas()) {
+ pending.add(new
PendingCloudCompactionTablet(partition.getId(), index.getId(), tabletId,
+ schemaHash, replica));
+ }
+ } finally {
+ olapTable.readUnlock();
+ }
+
+ AgentBatchTask batchTask = new AgentBatchTask();
+ for (PendingCloudCompactionTablet pendingTablet : pending) {
+ long backendId;
+ try {
+ backendId = pendingTablet.replica.getBackendId();
+ } catch (UserException e) {
+ throw new DdlException("failed to resolve backend for tablet "
+ tabletId
+ + ": " + e.getMessage());
+ }
+ batchTask.addTask(new CompactionTask(backendId, db.getId(),
olapTable.getId(),
+ pendingTablet.partitionId, pendingTablet.indexId,
pendingTablet.tabletId,
+ pendingTablet.schemaHash, type));
+ }
+
+ if (batchTask.getTaskNum() == 0) {
+ throw new DdlException("No replica found for tablet: " + tabletId);
+ }
+ AgentTaskExecutor.submit(batchTask);
+ }
+
@Override
public void compactTable(String dbName, String tableName, String type,
List<String> partitionNames)
throws DdlException {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index 7d9d2f05e38..9f96cd0a0c7 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -92,6 +92,7 @@ import
org.apache.doris.nereids.DorisParser.AddRollupClauseContext;
import org.apache.doris.nereids.DorisParser.AdminCancelRebalanceDiskContext;
import org.apache.doris.nereids.DorisParser.AdminCheckTabletsContext;
import org.apache.doris.nereids.DorisParser.AdminCompactTableContext;
+import org.apache.doris.nereids.DorisParser.AdminCompactTabletContext;
import org.apache.doris.nereids.DorisParser.AdminDiagnoseTabletContext;
import org.apache.doris.nereids.DorisParser.AdminRebalanceDiskContext;
import org.apache.doris.nereids.DorisParser.AdminRotateTdeRootKeyContext;
@@ -638,6 +639,7 @@ import
org.apache.doris.nereids.trees.plans.commands.AdminCancelRepairTableComma
import org.apache.doris.nereids.trees.plans.commands.AdminCheckTabletsCommand;
import org.apache.doris.nereids.trees.plans.commands.AdminCleanTrashCommand;
import org.apache.doris.nereids.trees.plans.commands.AdminCompactTableCommand;
+import org.apache.doris.nereids.trees.plans.commands.AdminCompactTabletCommand;
import org.apache.doris.nereids.trees.plans.commands.AdminCopyTabletCommand;
import
org.apache.doris.nereids.trees.plans.commands.AdminCreateClusterSnapshotCommand;
import
org.apache.doris.nereids.trees.plans.commands.AdminDropClusterSnapshotCommand;
@@ -1944,6 +1946,12 @@ public class LogicalPlanBuilder extends
DorisParserBaseVisitor<Object> {
return new AdminCompactTableCommand(tableRefInfo, equalTo);
}
+ @Override
+ public AdminCompactTabletCommand
visitAdminCompactTablet(AdminCompactTabletContext ctx) {
+ String compactionType = stripQuotes(ctx.STRING_LITERAL().getText());
+ return new
AdminCompactTabletCommand(Long.parseLong(ctx.tabletId.getText()),
compactionType);
+ }
+
@Override
public AlterMTMVCommand visitAlterMTMV(AlterMTMVContext ctx) {
List<String> nameParts = visitMultipartIdentifier(ctx.mvName);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
index b6aea691fc9..683939e70c8 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
@@ -204,6 +204,7 @@ public enum PlanType {
ALTER_AUTHENTICATION_INTEGRATION_COMMAND,
ADD_CONSTRAINT_COMMAND,
ADMIN_COMPACT_TABLE_COMMAND,
+ ADMIN_COMPACT_TABLET_COMMAND,
DROP_CONSTRAINT_COMMAND,
SHOW_CONSTRAINTS_COMMAND,
REFRESH_MTMV_COMMAND,
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminCompactTabletCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminCompactTabletCommand.java
new file mode 100644
index 00000000000..fa4ed1fada3
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminCompactTabletCommand.java
@@ -0,0 +1,121 @@
+// 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.doris.nereids.trees.plans.commands;
+
+import org.apache.doris.analysis.StmtType;
+import org.apache.doris.catalog.Database;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.Table;
+import org.apache.doris.catalog.TabletMeta;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.common.UserException;
+import org.apache.doris.datasource.InternalCatalog;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.StmtExecutor;
+
+import java.util.Locale;
+
+/**
+ * Command for triggering compaction on one tablet.
+ */
+public class AdminCompactTabletCommand extends Command implements
ForwardWithSync {
+ private enum CompactionType {
+ CUMULATIVE("cumulative"),
+ BASE("base"),
+ FULL("full");
+
+ private final String value;
+
+ CompactionType(String value) {
+ this.value = value;
+ }
+
+ private static CompactionType fromString(String compactionType) throws
AnalysisException {
+ try {
+ return valueOf(compactionType.toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException e) {
+ throw new AnalysisException("Where clause should looks like:
type = 'BASE/CUMULATIVE/FULL'");
+ }
+ }
+ }
+
+ private final long tabletId;
+ private final String compactionType;
+ private CompactionType typeFilter;
+
+ public AdminCompactTabletCommand(long tabletId, String compactionType) {
+ super(PlanType.ADMIN_COMPACT_TABLET_COMMAND);
+ this.tabletId = tabletId;
+ this.compactionType = compactionType;
+ }
+
+ public long getTabletId() {
+ return tabletId;
+ }
+
+ @Override
+ public void run(ConnectContext ctx, StmtExecutor executor) throws
Exception {
+ validate(ctx);
+ ctx.getEnv().compactTablet(tabletId, typeFilter.value);
+ }
+
+ private void validate(ConnectContext ctx) throws UserException {
+ validateTablet(ctx);
+ typeFilter = CompactionType.fromString(compactionType);
+ }
+
+ private void validateTablet(ConnectContext ctx) throws UserException {
+ TabletMeta tabletMeta =
Env.getCurrentInvertedIndex().getTabletMeta(tabletId);
+ if (tabletMeta == null) {
+ throw new AnalysisException("Unknown tablet: " + tabletId);
+ }
+
+ Database db =
Env.getCurrentEnv().getInternalCatalog().getDbNullable(tabletMeta.getDbId());
+ if (db == null) {
+ throw new AnalysisException("Unknown database for tablet: " +
tabletId);
+ }
+ Table table = db.getTableNullable(tabletMeta.getTableId());
+ if (!(table instanceof OlapTable)) {
+ throw new AnalysisException("Unknown OLAP table for tablet: " +
tabletId);
+ }
+
+ boolean hasGlobalAdmin = Env.getCurrentEnv().getAccessManager()
+ .checkGlobalPriv(ctx, PrivPredicate.ADMIN);
+ boolean hasTableAlter =
Env.getCurrentEnv().getAccessManager().checkTblPriv(ctx,
+ InternalCatalog.INTERNAL_CATALOG_NAME, db.getFullName(),
table.getName(), PrivPredicate.ALTER);
+ if (!hasGlobalAdmin && !hasTableAlter) {
+
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
"ALTER");
+ }
+ }
+
+ @Override
+ public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+ return visitor.visitAdminCompactTabletCommand(this, context);
+ }
+
+ @Override
+ public StmtType stmtType() {
+ return StmtType.ADMIN;
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
index ae20f80f36f..61f5ae23df0 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
@@ -23,6 +23,7 @@ import
org.apache.doris.nereids.trees.plans.commands.AdminCancelRepairTableComma
import org.apache.doris.nereids.trees.plans.commands.AdminCheckTabletsCommand;
import org.apache.doris.nereids.trees.plans.commands.AdminCleanTrashCommand;
import org.apache.doris.nereids.trees.plans.commands.AdminCompactTableCommand;
+import org.apache.doris.nereids.trees.plans.commands.AdminCompactTabletCommand;
import org.apache.doris.nereids.trees.plans.commands.AdminCopyTabletCommand;
import
org.apache.doris.nereids.trees.plans.commands.AdminCreateClusterSnapshotCommand;
import
org.apache.doris.nereids.trees.plans.commands.AdminDropClusterSnapshotCommand;
@@ -434,6 +435,10 @@ public interface CommandVisitor<R, C> {
return visitCommand(adminCompactTableCommand, context);
}
+ default R visitAdminCompactTabletCommand(AdminCompactTabletCommand
adminCompactTabletCommand, C context) {
+ return visitCommand(adminCompactTabletCommand, context);
+ }
+
default R visitAdminCleanTrashCommand(AdminCleanTrashCommand
adminCleanTrashCommand, C context) {
return visitCommand(adminCleanTrashCommand, context);
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AdminCompactTabletParserTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AdminCompactTabletParserTest.java
new file mode 100644
index 00000000000..6baac7b9509
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AdminCompactTabletParserTest.java
@@ -0,0 +1,45 @@
+// 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.doris.nereids.parser;
+
+import org.apache.doris.analysis.StmtType;
+import org.apache.doris.nereids.exceptions.ParseException;
+import org.apache.doris.nereids.trees.plans.commands.AdminCompactTabletCommand;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class AdminCompactTabletParserTest {
+ private final NereidsParser parser = new NereidsParser();
+
+ @Test
+ public void testParseTabletCompaction() {
+ LogicalPlan plan = parser.parseSingle(
+ "ADMIN COMPACT TABLET 12345 WHERE TYPE = 'CUMULATIVE'");
+ Assertions.assertInstanceOf(AdminCompactTabletCommand.class, plan);
+ Assertions.assertEquals(StmtType.ADMIN, plan.stmtType());
+ Assertions.assertEquals(12345L, ((AdminCompactTabletCommand)
plan).getTabletId());
+ }
+
+ @Test
+ public void testRejectTabletCompactionWithoutType() {
+ Assertions.assertThrows(ParseException.class,
+ () -> parser.parseSingle("ADMIN COMPACT TABLET 12345"));
+ }
+}
diff --git
a/regression-test/suites/cloud_p0/compaction/test_cloud_admin_compact_tablet.groovy
b/regression-test/suites/cloud_p0/compaction/test_cloud_admin_compact_tablet.groovy
new file mode 100644
index 00000000000..03cd33b7ca2
--- /dev/null
+++
b/regression-test/suites/cloud_p0/compaction/test_cloud_admin_compact_tablet.groovy
@@ -0,0 +1,101 @@
+// 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.
+
+suite("test_cloud_admin_compact_tablet", "p0") {
+ if (!isCloudMode()) {
+ return
+ }
+
+ def tableName = "test_cloud_admin_compact_tablet"
+ sql "DROP TABLE IF EXISTS ${tableName}"
+ sql """
+ CREATE TABLE ${tableName} (
+ k INT,
+ v INT
+ ) DUPLICATE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "disable_auto_compaction" = "true"
+ )
+ """
+
+ def tablets = sql_return_maparray "SHOW TABLETS FROM ${tableName}"
+ assertEquals(1, tablets.size())
+ def tabletId = tablets[0].TabletId
+ def backendId = tablets[0].BackendId
+
+ def backendIdToBackendIp = [:]
+ def backendIdToBackendHttpPort = [:]
+ getBackendIpHttpPort(backendIdToBackendIp, backendIdToBackendHttpPort)
+ def beHost = backendIdToBackendIp["${backendId}"]
+ def bePort = backendIdToBackendHttpPort["${backendId}"]
+
+ def showTabletCompaction = {
+ sql "SELECT COUNT(*) FROM ${tableName}"
+ def (code, stdout, stderr) = be_show_tablet_status(beHost, bePort,
tabletId)
+ assertEquals(0, code)
+ return parseJson(stdout.trim())
+ }
+
+ def countDataRowsets = { json ->
+ return json.rowsets.findAll { it.contains(" DATA ") }.size()
+ }
+
+ def epochTime = "1970-01-01 08:00:00.000"
+ for (int i = 1; i <= 8; i++) {
+ sql "INSERT INTO ${tableName} VALUES (${i}, ${i})"
+ }
+
+ def before = showTabletCompaction()
+ def rowsetsBefore = countDataRowsets(before)
+ assertTrue(rowsetsBefore >= 8,
+ "expected >= 8 data rowsets before tablet compaction, got
${rowsetsBefore}")
+ assertEquals(epochTime, before["last cumulative success time"])
+
+ sql "ADMIN COMPACT TABLET ${tabletId} WHERE TYPE = 'CUMULATIVE'"
+ def after = null
+ def deadline = System.currentTimeMillis() + 90 * 1000L
+ while (System.currentTimeMillis() < deadline) {
+ after = showTabletCompaction()
+ if (after["last cumulative success time"] != epochTime
+ && countDataRowsets(after) < rowsetsBefore) {
+ break
+ }
+ sleep(1000)
+ }
+
+ assertNotNull(after)
+ assertNotEquals(epochTime, after["last cumulative success time"])
+ assertTrue(countDataRowsets(after) < rowsetsBefore,
+ "tablet cumulative did not reduce rowset count: ${after.rowsets}")
+
+ test {
+ sql "ADMIN COMPACT TABLET ${tabletId} WHERE TYPE = 'UNKNOWN'"
+ exception "BASE/CUMULATIVE/FULL"
+ }
+
+ test {
+ sql "ADMIN COMPACT TABLET ${tabletId}"
+ exception "WHERE"
+ }
+
+ def rowCount = sql "SELECT COUNT(*) FROM ${tableName}"
+ assertEquals(8, rowCount[0][0])
+ def rows = sql "SELECT * FROM ${tableName} ORDER BY k"
+ assertEquals((1..8).collect { [it, it] }, rows)
+}
diff --git a/regression-test/suites/compaction/test_admin_compact_tablet.groovy
b/regression-test/suites/compaction/test_admin_compact_tablet.groovy
new file mode 100644
index 00000000000..aca867d816f
--- /dev/null
+++ b/regression-test/suites/compaction/test_admin_compact_tablet.groovy
@@ -0,0 +1,101 @@
+// 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.
+
+suite("test_admin_compact_tablet", "p0") {
+ if (isCloudMode()) {
+ return
+ }
+
+ def tableName = "test_admin_compact_tablet"
+ sql "DROP TABLE IF EXISTS ${tableName}"
+ sql """
+ CREATE TABLE ${tableName} (
+ k INT,
+ v INT
+ ) DUPLICATE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "disable_auto_compaction" = "true"
+ )
+ """
+
+ def tablets = sql_return_maparray "SHOW TABLETS FROM ${tableName}"
+ assertEquals(1, tablets.size())
+ def tabletId = tablets[0].TabletId
+ def backendId = tablets[0].BackendId
+
+ def backendIdToBackendIp = [:]
+ def backendIdToBackendHttpPort = [:]
+ getBackendIpHttpPort(backendIdToBackendIp, backendIdToBackendHttpPort)
+ def beHost = backendIdToBackendIp["${backendId}"]
+ def bePort = backendIdToBackendHttpPort["${backendId}"]
+
+ def showTabletCompaction = {
+ def (code, stdout, stderr) = be_show_tablet_status(beHost, bePort,
tabletId)
+ assertEquals(0, code)
+ return parseJson(stdout.trim())
+ }
+
+ def countDataRowsets = { json ->
+ return json.rowsets.findAll { it.contains(" DATA ") }.size()
+ }
+
+ def epochTime = "1970-01-01 08:00:00.000"
+ for (int i = 1; i <= 8; i++) {
+ sql "INSERT INTO ${tableName} VALUES (${i}, ${i})"
+ }
+
+ def before = showTabletCompaction()
+ def rowsetsBefore = countDataRowsets(before)
+ assertTrue(rowsetsBefore >= 8,
+ "expected >= 8 data rowsets before tablet compaction, got
${rowsetsBefore}")
+ assertEquals(epochTime, before["last cumulative success time"])
+
+ sql "ADMIN COMPACT TABLET ${tabletId} WHERE TYPE = 'CUMULATIVE'"
+ def after = null
+ def deadline = System.currentTimeMillis() + 60 * 1000L
+ while (System.currentTimeMillis() < deadline) {
+ after = showTabletCompaction()
+ if (after["last cumulative success time"] != epochTime
+ && countDataRowsets(after) < rowsetsBefore) {
+ break
+ }
+ sleep(500)
+ }
+
+ assertNotNull(after)
+ assertNotEquals(epochTime, after["last cumulative success time"])
+ assertEquals("[OK]", after["last cumulative status"])
+ assertTrue(countDataRowsets(after) < rowsetsBefore,
+ "tablet cumulative did not reduce rowset count: ${after.rowsets}")
+
+ test {
+ sql "ADMIN COMPACT TABLET ${tabletId} WHERE TYPE = 'UNKNOWN'"
+ exception "BASE/CUMULATIVE/FULL"
+ }
+
+ test {
+ sql "ADMIN COMPACT TABLET ${tabletId}"
+ exception "WHERE"
+ }
+
+ def rowCount = sql "SELECT COUNT(*) FROM ${tableName}"
+ assertEquals(8, rowCount[0][0])
+ def rows = sql "SELECT * FROM ${tableName} ORDER BY k"
+ assertEquals((1..8).collect { [it, it] }, rows)
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]