This is an automated email from the ASF dual-hosted git repository.
liaoxin01 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 058dfd3794a [opt](schema) Optimize tables schema scan status (#64933)
058dfd3794a is described below
commit 058dfd3794aca46617766173ced5ac5574efff94
Author: Refrain <[email protected]>
AuthorDate: Tue Jul 7 09:49:17 2026 +0800
[opt](schema) Optimize tables schema scan status (#64933)
Optimize information_schema.tables status scan for large OlapTables.
Before this PR, FE always filled the full table status payload for
information_schema.tables, so even queries that only projected
lightweight columns such as UPDATE_TIME still paid the cost of
tablet-derived status fields.
This PR includes the following optimizations:
| Optimization | Description |
| --- | --- |
| Column pruning for table status RPC | BE passes the required
information_schema.tables columns to FE, and FE only fills requested
optional status fields. Lightweight queries can skip TABLE_ROWS,
DATA_LENGTH, AVG_ROW_LENGTH, and INDEX_LENGTH. |
| Single tablet-status traversal | TABLE_ROWS, DATA_LENGTH,
AVG_ROW_LENGTH, and INDEX_LENGTH are computed together when any of them
is requested, avoiding repeated OlapTable tablet/replica traversal. |
This PR does not add a table-level cache for tablet-derived status
fields. The benchmark still shows the possible room for that future
optimization: the full status query remains slower than the
UPDATE_TIME-only query because it still needs to read tablet-derived
status, while the lightweight query can skip it completely.
Benchmark on a table with 3900 partitions and 21 buckets per partition,
about 81,900 tablets, workers=20, targetQps=100:
| Query | Before avgQps | After avgQps | Improvement | Notes |
| --- | ---: | ---: | ---: | --- |
| SHOW TABLE STATUS FROM regression_test_stress_load_usercase LIKE ... |
26.3 | 49.1 | +86.7% | Full status query. It still needs tablet-derived
status fields, but benefits from the single traversal optimization. |
| SELECT TABLE_NAME, UPDATE_TIME FROM information_schema.tables WHERE
TABLE_SCHEMA = ... | 29.3 | 92.2 | +214.7% | Lightweight projection. It
benefits from column pruning and skips tablet-derived status fields. |
---
be/src/exec/operator/schema_scan_operator.cpp | 3 +
be/src/information_schema/schema_scanner.h | 2 +
.../information_schema/schema_tables_scanner.cpp | 1 +
.../java/org/apache/doris/catalog/LocalTablet.java | 32 +++-
.../java/org/apache/doris/catalog/OlapTable.java | 48 +++---
.../java/org/apache/doris/catalog/TableIf.java | 34 ++++
.../apache/doris/service/FrontendServiceImpl.java | 60 +++++--
.../org/apache/doris/catalog/OlapTableTest.java | 36 +++++
.../java/org/apache/doris/catalog/TabletTest.java | 17 ++
.../doris/service/FrontendServiceImplTest.java | 176 +++++++++++++++++++++
gensrc/thrift/FrontendService.thrift | 3 +
.../test_tables_status_column_pruning.groovy | 97 ++++++++++++
12 files changed, 470 insertions(+), 39 deletions(-)
diff --git a/be/src/exec/operator/schema_scan_operator.cpp
b/be/src/exec/operator/schema_scan_operator.cpp
index 95ee374824c..3ede2483750 100644
--- a/be/src/exec/operator/schema_scan_operator.cpp
+++ b/be/src/exec/operator/schema_scan_operator.cpp
@@ -25,6 +25,7 @@
#include "core/data_type/data_type_factory.hpp"
#include "exec/operator/operator.h"
#include "runtime/runtime_profile.h"
+#include "util/string_util.h"
namespace doris {
class RuntimeState;
@@ -186,6 +187,8 @@ Status SchemaScanOperatorX::prepare(RuntimeState* state) {
for (; j < columns_desc.size(); ++j) {
if (boost::iequals(_dest_tuple_desc->slots()[i]->col_name(),
columns_desc[j].name)) {
_slot_offsets[i] = j;
+ _common_scanner_param->required_columns.insert(
+ to_upper(_dest_tuple_desc->slots()[i]->col_name()));
break;
}
}
diff --git a/be/src/information_schema/schema_scanner.h
b/be/src/information_schema/schema_scanner.h
index aa32f1639b3..31662be9391 100644
--- a/be/src/information_schema/schema_scanner.h
+++ b/be/src/information_schema/schema_scanner.h
@@ -24,6 +24,7 @@
#include <cstddef>
#include <cstdint>
#include <memory>
+#include <set>
#include <string>
#include <vector>
@@ -69,6 +70,7 @@ struct SchemaScannerCommonParam {
int64_t thread_id;
const std::string* catalog = nullptr;
std::set<TNetworkAddress> fe_addr_list;
+ std::set<std::string> required_columns;
};
// scanner parameter from frontend
diff --git a/be/src/information_schema/schema_tables_scanner.cpp
b/be/src/information_schema/schema_tables_scanner.cpp
index 947097906e1..ef94909fd73 100644
--- a/be/src/information_schema/schema_tables_scanner.cpp
+++ b/be/src/information_schema/schema_tables_scanner.cpp
@@ -114,6 +114,7 @@ Status SchemaTablesScanner::_get_new_table() {
if (nullptr != _param->common_param->table) {
table_params.__set_table(*(_param->common_param->table));
}
+
table_params.__set_required_columns(_param->common_param->required_columns);
if (nullptr != _param->common_param->current_user_ident) {
table_params.__set_current_user_ident(*(_param->common_param->current_user_ident));
} else {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/LocalTablet.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/LocalTablet.java
index 5db0a1286b5..8c1c8016b33 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/LocalTablet.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/LocalTablet.java
@@ -34,7 +34,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.stream.LongStream;
public class LocalTablet extends Tablet {
private static final Logger LOG = LogManager.getLogger(LocalTablet.class);
@@ -133,17 +132,34 @@ public class LocalTablet extends Tablet {
// due to dataSize not write to image
@Override
public long getDataSize(boolean singleReplica, boolean filterSizeZero) {
- LongStream s = getReplicas().stream().filter(r -> r.getState() ==
ReplicaState.NORMAL)
- .filter(r -> !filterSizeZero || r.getDataSize() > 0)
- .mapToLong(Replica::getDataSize);
- return singleReplica ?
Double.valueOf(s.average().orElse(0)).longValue() : s.sum();
+ long dataSize = 0;
+ int replicaNum = 0;
+ for (Replica replica : getReplicas()) {
+ if (replica.getState() != ReplicaState.NORMAL) {
+ continue;
+ }
+ long replicaDataSize = replica.getDataSize();
+ if (filterSizeZero && replicaDataSize <= 0) {
+ continue;
+ }
+ dataSize += replicaDataSize;
+ replicaNum++;
+ }
+ return singleReplica && replicaNum > 0 ? dataSize / replicaNum :
dataSize;
}
@Override
public long getRowCount(boolean singleReplica) {
- LongStream s = getReplicas().stream().filter(r -> r.getState() ==
ReplicaState.NORMAL)
- .mapToLong(Replica::getRowCount);
- return singleReplica ?
Double.valueOf(s.average().orElse(0)).longValue() : s.sum();
+ long rowCount = 0;
+ int replicaNum = 0;
+ for (Replica replica : getReplicas()) {
+ if (replica.getState() != ReplicaState.NORMAL) {
+ continue;
+ }
+ rowCount += replica.getRowCount();
+ replicaNum++;
+ }
+ return singleReplica && replicaNum > 0 ? rowCount / replicaNum :
rowCount;
}
// Get the least row count among all valid replicas.
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
index 970999ef80f..c8d6c515b29 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
@@ -1911,37 +1911,43 @@ public class OlapTable extends Table implements
MTMVRelatedTableIf, GsonPostProc
@Override
public long getAvgRowLength() {
- long rowCount = 0;
- long dataSize = 0;
- for (Map.Entry<Long, Partition> entry : idToPartition.entrySet()) {
- rowCount += entry.getValue().getBaseIndex().getRowCount();
- dataSize += entry.getValue().getBaseIndex().getDataSize(false,
false);
- }
- if (rowCount > 0) {
- return dataSize / rowCount;
- } else {
- return 0;
- }
+ return getTableStatusStats().getAvgRowLength();
}
@Override
public long getDataLength() {
- long dataSize = 0;
- for (Map.Entry<Long, Partition> entry : idToPartition.entrySet()) {
- dataSize += entry.getValue().getBaseIndex().getLocalSegmentSize();
- dataSize += entry.getValue().getBaseIndex().getRemoteSegmentSize();
- }
- return dataSize;
+ return getTableStatusStats().getDataLength();
}
@Override
public long getIndexLength() {
- long indexSize = 0;
+ return getTableStatusStats().getIndexLength();
+ }
+
+ @Override
+ public TableStatusStats getTableStatusStats() {
+ long rowCount = 0;
+ long rowCountForAvg = 0;
+ long dataSizeForAvg = 0;
+ long dataLength = 0;
+ long indexLength = 0;
for (Map.Entry<Long, Partition> entry : idToPartition.entrySet()) {
- indexSize += entry.getValue().getBaseIndex().getLocalIndexSize();
- indexSize += entry.getValue().getBaseIndex().getRemoteIndexSize();
+ MaterializedIndex baseIndex = entry.getValue().getBaseIndex();
+ long baseIndexRowCount = baseIndex.getRowCount();
+ rowCount += baseIndexRowCount == UNKNOWN_ROW_COUNT ? 0 :
baseIndexRowCount;
+ rowCountForAvg += baseIndexRowCount;
+ for (Tablet tablet : baseIndex.getTablets()) {
+ for (Replica replica : tablet.getReplicas()) {
+ if (replica.getState() == Replica.ReplicaState.NORMAL) {
+ dataSizeForAvg += replica.getDataSize();
+ }
+ dataLength += replica.getLocalSegmentSize() +
replica.getRemoteSegmentSize();
+ indexLength += replica.getLocalInvertedIndexSize() +
replica.getRemoteInvertedIndexSize();
+ }
+ }
}
- return indexSize;
+ long avgRowLength = rowCountForAvg > 0 ? dataSizeForAvg /
rowCountForAvg : 0;
+ return new TableStatusStats(rowCount, dataLength, avgRowLength,
indexLength);
}
// Get the signature string of this table with specified partitions.
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java
index 59d5c10045e..a201617b4f3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java
@@ -46,6 +46,36 @@ import java.util.concurrent.TimeUnit;
public interface TableIf {
Logger LOG = LogManager.getLogger(TableIf.class);
+ class TableStatusStats {
+ private final long rows;
+ private final long dataLength;
+ private final long avgRowLength;
+ private final long indexLength;
+
+ public TableStatusStats(long rows, long dataLength, long avgRowLength,
long indexLength) {
+ this.rows = rows;
+ this.dataLength = dataLength;
+ this.avgRowLength = avgRowLength;
+ this.indexLength = indexLength;
+ }
+
+ public long getRows() {
+ return rows;
+ }
+
+ public long getDataLength() {
+ return dataLength;
+ }
+
+ public long getAvgRowLength() {
+ return avgRowLength;
+ }
+
+ public long getIndexLength() {
+ return indexLength;
+ }
+ }
+
long UNKNOWN_ROW_COUNT = -1;
default void readLock() {
@@ -176,6 +206,10 @@ public interface TableIf {
long getIndexLength();
+ default TableStatusStats getTableStatusStats() {
+ return new TableStatusStats(getCachedRowCount(), getDataLength(),
getAvgRowLength(), getIndexLength());
+ }
+
long getLastCheckTime();
String getComment(boolean escapeQuota);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
b/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
index 434910398c3..34c0093c6b9 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
@@ -350,6 +350,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
@@ -660,6 +661,11 @@ public class FrontendServiceImpl implements
FrontendService.Iface {
TListTableStatusResult result = new TListTableStatusResult();
List<TTableStatus> tablesResult = Lists.newArrayList();
result.setTables(tablesResult);
+ Set<String> requiredColumns = params.isSetRequiredColumns()
+ ? params.getRequiredColumns().stream()
+ .map(column -> column.toUpperCase(Locale.ROOT))
+ .collect(Collectors.toSet())
+ : null;
PatternMatcher matcher = null;
String specifiedTable = null;
if (params.isSetPattern()) {
@@ -739,17 +745,40 @@ public class FrontendServiceImpl implements
FrontendService.Iface {
TTableStatus status = new TTableStatus();
status.setName(table.getName());
status.setType(table.getMysqlType());
- status.setEngine(table.getEngine());
status.setComment(table.getComment());
- status.setCreateTime(table.getCreateTime());
- status.setLastCheckTime(lastCheckTime / 1000);
- status.setUpdateTime(table.getUpdateTime() / 1000);
- status.setCheckTime(lastCheckTime / 1000);
- status.setCollation("utf-8");
- status.setRows(table.getCachedRowCount());
- status.setDataLength(table.getDataLength());
- status.setAvgRowLength(table.getAvgRowLength());
- status.setIndexLength(table.getIndexLength());
+ if (needTableStatusColumn(requiredColumns,
"ENGINE")) {
+ status.setEngine(table.getEngine());
+ }
+ if (needTableStatusColumn(requiredColumns,
"CREATE_TIME")) {
+ status.setCreateTime(table.getCreateTime());
+ }
+ if (needTableStatusColumn(requiredColumns,
"LAST_CHECK_TIME")
+ || needTableStatusColumn(requiredColumns,
"CHECK_TIME")) {
+ status.setLastCheckTime(lastCheckTime / 1000);
+ }
+ if (needTableStatusColumn(requiredColumns,
"UPDATE_TIME")) {
+ status.setUpdateTime(table.getUpdateTime() /
1000);
+ }
+ if (needTableStatusColumn(requiredColumns,
"CHECK_TIME")) {
+ status.setCheckTime(lastCheckTime / 1000);
+ }
+ if (needTableStatusColumn(requiredColumns,
"TABLE_COLLATION")) {
+ status.setCollation("utf-8");
+ }
+ TableIf.TableStatusStats tableStatusStats =
+ needTableStatusStats(requiredColumns) ?
table.getTableStatusStats() : null;
+ if (needTableStatusColumn(requiredColumns,
"TABLE_ROWS")) {
+ status.setRows(tableStatusStats.getRows());
+ }
+ if (needTableStatusColumn(requiredColumns,
"DATA_LENGTH")) {
+
status.setDataLength(tableStatusStats.getDataLength());
+ }
+ if (needTableStatusColumn(requiredColumns,
"AVG_ROW_LENGTH")) {
+
status.setAvgRowLength(tableStatusStats.getAvgRowLength());
+ }
+ if (needTableStatusColumn(requiredColumns,
"INDEX_LENGTH")) {
+
status.setIndexLength(tableStatusStats.getIndexLength());
+ }
if (table instanceof View) {
status.setDdlSql(((View)
table).getInlineViewDef());
}
@@ -766,6 +795,17 @@ public class FrontendServiceImpl implements
FrontendService.Iface {
return result;
}
+ private static boolean needTableStatusColumn(Set<String> requiredColumns,
String columnName) {
+ return requiredColumns == null || requiredColumns.contains(columnName);
+ }
+
+ private static boolean needTableStatusStats(Set<String> requiredColumns) {
+ return needTableStatusColumn(requiredColumns, "TABLE_ROWS")
+ || needTableStatusColumn(requiredColumns, "DATA_LENGTH")
+ || needTableStatusColumn(requiredColumns, "AVG_ROW_LENGTH")
+ || needTableStatusColumn(requiredColumns, "INDEX_LENGTH");
+ }
+
public TListTableMetadataNameIdsResult
listTableMetadataNameIds(TGetTablesParams params) throws TException {
if (LOG.isDebugEnabled()) {
LOG.debug("get list simple table request: {}", params);
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java
b/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java
index 87b4c7c78b7..f7f7bb88beb 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java
@@ -52,6 +52,42 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
public class OlapTableTest {
+ @Test
+ public void testGetTableStatusStatsUsesSinglePassSemantics() {
+ OlapTable olapTable = new OlapTable();
+ MaterializedIndex index = new MaterializedIndex(10,
MaterializedIndex.IndexState.NORMAL);
+ index.setRowCount(20);
+
+ List<Replica> replicas = Lists.newArrayList(
+ mockReplica(Replica.ReplicaState.NORMAL, 10, 100, 1, 20, 2),
+ mockReplica(Replica.ReplicaState.DECOMMISSION, 30, 300, 3, 40,
4),
+ mockReplica(Replica.ReplicaState.NORMAL, 50, 500, 5, 60, 6));
+ Tablet tablet = Mockito.mock(Tablet.class);
+ Mockito.when(tablet.getReplicas()).thenReturn(replicas);
+ index.appendTablets(Lists.newArrayList(tablet));
+
+ Partition partition = new Partition(11, "p1", index, null);
+ olapTable.addPartition(partition);
+
+ TableIf.TableStatusStats stats = olapTable.getTableStatusStats();
+ Assert.assertEquals(20L, stats.getRows());
+ Assert.assertEquals(909L, stats.getDataLength());
+ Assert.assertEquals(3L, stats.getAvgRowLength());
+ Assert.assertEquals(132L, stats.getIndexLength());
+ }
+
+ private Replica mockReplica(Replica.ReplicaState state, long dataSize,
long localSegmentSize,
+ long remoteSegmentSize, long localIndexSize, long remoteIndexSize)
{
+ Replica replica = Mockito.mock(Replica.class);
+ Mockito.when(replica.getState()).thenReturn(state);
+ Mockito.when(replica.getDataSize()).thenReturn(dataSize);
+
Mockito.when(replica.getLocalSegmentSize()).thenReturn(localSegmentSize);
+
Mockito.when(replica.getRemoteSegmentSize()).thenReturn(remoteSegmentSize);
+
Mockito.when(replica.getLocalInvertedIndexSize()).thenReturn(localIndexSize);
+
Mockito.when(replica.getRemoteInvertedIndexSize()).thenReturn(remoteIndexSize);
+ return replica;
+ }
+
@Test
public void test() throws IOException {
diff --git a/fe/fe-core/src/test/java/org/apache/doris/catalog/TabletTest.java
b/fe/fe-core/src/test/java/org/apache/doris/catalog/TabletTest.java
index 2f89dcdeea8..69427f58a74 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/TabletTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/TabletTest.java
@@ -105,6 +105,23 @@ public class TabletTest {
Assert.assertEquals("tabletId=" + newTabletId, tablet.toString());
}
+ @Test
+ public void testGetReplicaStatsOnlyUsesNormalReplicas() {
+ Tablet statsTablet = new LocalTablet(2);
+ invertedIndex.addTablet(2, new TabletMeta(10, 20, 30, 40, 1,
TStorageMedium.HDD));
+ statsTablet.addReplica(new LocalReplica(11L, 1L, 100L, 0, 10L, 0L,
100L, ReplicaState.NORMAL, 0L, 100L));
+ statsTablet.addReplica(new LocalReplica(12L, 2L, 100L, 0, 20L, 0L,
200L, ReplicaState.NORMAL, 0L, 100L));
+ statsTablet.addReplica(new LocalReplica(13L, 3L, 100L, 0, 0L, 0L,
300L, ReplicaState.NORMAL, 0L, 100L));
+ statsTablet.addReplica(new LocalReplica(14L, 4L, 100L, 0, 40L, 0L,
400L, ReplicaState.DECOMMISSION,
+ 0L, 100L));
+
+ Assert.assertEquals(30L, statsTablet.getDataSize(false, false));
+ Assert.assertEquals(10L, statsTablet.getDataSize(true, false));
+ Assert.assertEquals(30L, statsTablet.getDataSize(false, true));
+ Assert.assertEquals(600L, statsTablet.getRowCount(false));
+ Assert.assertEquals(200L, statsTablet.getRowCount(true));
+ }
+
@Test
public void deleteReplicaTest() {
// delete replica1
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
index 5c442974eed..008c10d91df 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
@@ -22,6 +22,7 @@ import org.apache.doris.catalog.Database;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.OlapTable;
import org.apache.doris.catalog.Partition;
+import org.apache.doris.catalog.TableIf;
import org.apache.doris.cloud.CacheHotspotManager;
import org.apache.doris.cloud.catalog.CloudEnv;
import org.apache.doris.common.AuthenticationException;
@@ -52,6 +53,7 @@ import org.apache.doris.thrift.TGetTablesParams;
import org.apache.doris.thrift.TGetTablesResult;
import org.apache.doris.thrift.TGetTabletReplicaInfosRequest;
import org.apache.doris.thrift.TGetTabletReplicaInfosResult;
+import org.apache.doris.thrift.TListTableStatusResult;
import org.apache.doris.thrift.TLoadTxnCommitRequest;
import org.apache.doris.thrift.TLoadTxnRollbackRequest;
import org.apache.doris.thrift.TMaxComputeBlockIdRequest;
@@ -65,10 +67,12 @@ import org.apache.doris.thrift.TSchemaTableRequestParams;
import org.apache.doris.thrift.TShowUserRequest;
import org.apache.doris.thrift.TShowUserResult;
import org.apache.doris.thrift.TStatusCode;
+import org.apache.doris.thrift.TTableStatus;
import org.apache.doris.transaction.GlobalTransactionMgrIface;
import org.apache.doris.transaction.TransactionState;
import org.apache.doris.utframe.UtFrameUtils;
+import com.google.common.collect.Sets;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
@@ -160,6 +164,178 @@ public class FrontendServiceImplTest {
Assert.assertTrue(result.getTables().isEmpty());
}
+ @Test
+ public void testListTableStatusPrunesUnrequestedExpensiveColumns() throws
Exception {
+ String createOlapTblStmt = "CREATE TABLE test.prune_status_columns(\n"
+ + " k1 INT,\n"
+ + " v1 INT\n"
+ + ")\n"
+ + "DUPLICATE KEY(k1)\n"
+ + "DISTRIBUTED BY HASH(k1) BUCKETS 1\n"
+ + "PROPERTIES(\"replication_num\" = \"1\");";
+ createTable(createOlapTblStmt);
+ Database db =
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+ OlapTable table = (OlapTable)
db.getTableOrAnalysisException("prune_status_columns");
+ OlapTable spyTable = Mockito.spy(table);
+ db.unregisterTable(table.getName());
+ db.registerTable(spyTable);
+ try {
+ FrontendServiceImpl impl = new FrontendServiceImpl(exeEnv);
+ TGetTablesParams params = new TGetTablesParams();
+ params.setCatalog(InternalCatalog.INTERNAL_CATALOG_NAME);
+ params.setDb("test");
+ params.setTable("prune_status_columns");
+ params.setRequiredColumns(Collections.singleton("UPDATE_TIME"));
+
params.setCurrentUserIdent(connectContext.getCurrentUserIdentity().toThrift());
+
+ TListTableStatusResult result = impl.listTableStatus(params);
+
+ Assert.assertEquals(1, result.getTablesSize());
+ TTableStatus status = result.getTables().get(0);
+ Assert.assertTrue(status.isSetUpdateTime());
+ Assert.assertFalse(status.isSetRows());
+ Assert.assertFalse(status.isSetDataLength());
+ Assert.assertFalse(status.isSetAvgRowLength());
+ Assert.assertFalse(status.isSetIndexLength());
+ Mockito.verify(spyTable, Mockito.never()).getCachedRowCount();
+ Mockito.verify(spyTable, Mockito.never()).getDataLength();
+ Mockito.verify(spyTable, Mockito.never()).getAvgRowLength();
+ Mockito.verify(spyTable, Mockito.never()).getIndexLength();
+ } finally {
+ db.unregisterTable(spyTable.getName());
+ db.registerTable(table);
+ }
+ }
+
+ @Test
+ public void testListTableStatusSetsLastCheckTimeForCheckTimeProjection()
throws Exception {
+ String createOlapTblStmt = "CREATE TABLE
test.prune_check_time_status_column(\n"
+ + " k1 INT,\n"
+ + " v1 INT\n"
+ + ")\n"
+ + "DUPLICATE KEY(k1)\n"
+ + "DISTRIBUTED BY HASH(k1) BUCKETS 1\n"
+ + "PROPERTIES(\"replication_num\" = \"1\");";
+ createTable(createOlapTblStmt);
+ Database db =
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+ OlapTable table = (OlapTable)
db.getTableOrAnalysisException("prune_check_time_status_column");
+ OlapTable spyTable = Mockito.spy(table);
+ Mockito.doReturn(10_000L).when(spyTable).getLastCheckTime();
+ db.unregisterTable(table.getName());
+ db.registerTable(spyTable);
+ try {
+ FrontendServiceImpl impl = new FrontendServiceImpl(exeEnv);
+ TGetTablesParams params = new TGetTablesParams();
+ params.setCatalog(InternalCatalog.INTERNAL_CATALOG_NAME);
+ params.setDb("test");
+ params.setTable("prune_check_time_status_column");
+ params.setRequiredColumns(Collections.singleton("CHECK_TIME"));
+
params.setCurrentUserIdent(connectContext.getCurrentUserIdentity().toThrift());
+
+ TListTableStatusResult result = impl.listTableStatus(params);
+
+ Assert.assertEquals(1, result.getTablesSize());
+ TTableStatus status = result.getTables().get(0);
+ Assert.assertTrue(status.isSetLastCheckTime());
+ Assert.assertEquals(10L, status.getLastCheckTime());
+ Assert.assertTrue(status.isSetCheckTime());
+ Assert.assertEquals(10L, status.getCheckTime());
+ Assert.assertFalse(status.isSetUpdateTime());
+ Assert.assertFalse(status.isSetRows());
+ Mockito.verify(spyTable, Mockito.never()).getTableStatusStats();
+ } finally {
+ db.unregisterTable(spyTable.getName());
+ db.registerTable(table);
+ }
+ }
+
+ @Test
+ public void
testListTableStatusPrunesAllOptionalColumnsWhenRequiredColumnsIsEmpty() throws
Exception {
+ String createOlapTblStmt = "CREATE TABLE
test.prune_all_optional_status_columns(\n"
+ + " k1 INT,\n"
+ + " v1 INT\n"
+ + ")\n"
+ + "DUPLICATE KEY(k1)\n"
+ + "DISTRIBUTED BY HASH(k1) BUCKETS 1\n"
+ + "PROPERTIES(\"replication_num\" = \"1\");";
+ createTable(createOlapTblStmt);
+ Database db =
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+ OlapTable table = (OlapTable)
db.getTableOrAnalysisException("prune_all_optional_status_columns");
+ OlapTable spyTable = Mockito.spy(table);
+ db.unregisterTable(table.getName());
+ db.registerTable(spyTable);
+ try {
+ FrontendServiceImpl impl = new FrontendServiceImpl(exeEnv);
+ TGetTablesParams params = new TGetTablesParams();
+ params.setCatalog(InternalCatalog.INTERNAL_CATALOG_NAME);
+ params.setDb("test");
+ params.setTable("prune_all_optional_status_columns");
+ params.setRequiredColumns(Collections.emptySet());
+
params.setCurrentUserIdent(connectContext.getCurrentUserIdentity().toThrift());
+
+ TListTableStatusResult result = impl.listTableStatus(params);
+
+ Assert.assertEquals(1, result.getTablesSize());
+ TTableStatus status = result.getTables().get(0);
+ Assert.assertFalse(status.isSetEngine());
+ Assert.assertFalse(status.isSetUpdateTime());
+ Assert.assertFalse(status.isSetRows());
+ Assert.assertFalse(status.isSetDataLength());
+ Assert.assertFalse(status.isSetAvgRowLength());
+ Assert.assertFalse(status.isSetIndexLength());
+ Mockito.verify(spyTable, Mockito.never()).getTableStatusStats();
+ } finally {
+ db.unregisterTable(spyTable.getName());
+ db.registerTable(table);
+ }
+ }
+
+ @Test
+ public void testListTableStatusUsesCombinedStatsForExpensiveColumns()
throws Exception {
+ String createOlapTblStmt = "CREATE TABLE test.combined_status_stats(\n"
+ + " k1 INT,\n"
+ + " v1 INT\n"
+ + ")\n"
+ + "DUPLICATE KEY(k1)\n"
+ + "DISTRIBUTED BY HASH(k1) BUCKETS 1\n"
+ + "PROPERTIES(\"replication_num\" = \"1\");";
+ createTable(createOlapTblStmt);
+ Database db =
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+ OlapTable table = (OlapTable)
db.getTableOrAnalysisException("combined_status_stats");
+ OlapTable spyTable = Mockito.spy(table);
+ Mockito.doReturn(new TableIf.TableStatusStats(10L, 20L, 2L, 30L))
+ .when(spyTable).getTableStatusStats();
+ db.unregisterTable(table.getName());
+ db.registerTable(spyTable);
+ try {
+ FrontendServiceImpl impl = new FrontendServiceImpl(exeEnv);
+ TGetTablesParams params = new TGetTablesParams();
+ params.setCatalog(InternalCatalog.INTERNAL_CATALOG_NAME);
+ params.setDb("test");
+ params.setTable("combined_status_stats");
+ params.setRequiredColumns(Sets.newHashSet("table_rows",
"Data_Length", "avg_row_length",
+ "INDEX_LENGTH"));
+
params.setCurrentUserIdent(connectContext.getCurrentUserIdentity().toThrift());
+
+ TListTableStatusResult result = impl.listTableStatus(params);
+
+ Assert.assertEquals(1, result.getTablesSize());
+ TTableStatus status = result.getTables().get(0);
+ Assert.assertEquals(10L, status.getRows());
+ Assert.assertEquals(20L, status.getDataLength());
+ Assert.assertEquals(2L, status.getAvgRowLength());
+ Assert.assertEquals(30L, status.getIndexLength());
+ Mockito.verify(spyTable).getTableStatusStats();
+ Mockito.verify(spyTable, Mockito.never()).getCachedRowCount();
+ Mockito.verify(spyTable, Mockito.never()).getDataLength();
+ Mockito.verify(spyTable, Mockito.never()).getAvgRowLength();
+ Mockito.verify(spyTable, Mockito.never()).getIndexLength();
+ } finally {
+ db.unregisterTable(spyTable.getName());
+ db.registerTable(table);
+ }
+ }
+
@Test
public void testCreatePartitionRange() throws Exception {
diff --git a/gensrc/thrift/FrontendService.thrift
b/gensrc/thrift/FrontendService.thrift
index b7111df7066..13e8e57e055 100644
--- a/gensrc/thrift/FrontendService.thrift
+++ b/gensrc/thrift/FrontendService.thrift
@@ -128,6 +128,9 @@ struct TGetTablesParams {
// Reserved for downstream field `current_roles` to keep thrift field ids
// wire-compatible across maintained branches. Do not reuse this id.
9: optional set<string> reserved_field_9
+ // Columns needed by schema table callers. If unset, the callee returns the
+ // full table status for backward compatibility.
+ 10: optional set<string> required_columns
}
struct TTableStatus {
diff --git
a/regression-test/suites/information_schema_p0/test_tables_status_column_pruning.groovy
b/regression-test/suites/information_schema_p0/test_tables_status_column_pruning.groovy
new file mode 100644
index 00000000000..e6a3245d580
--- /dev/null
+++
b/regression-test/suites/information_schema_p0/test_tables_status_column_pruning.groovy
@@ -0,0 +1,97 @@
+// 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_tables_status_column_pruning") {
+ sql "SET enable_nereids_planner=true"
+ sql "SET enable_fallback_to_original_planner=false"
+
+ def assertNoTabletStatusColumns = {
+ notContains "col=TABLE_ROWS"
+ notContains "col=DATA_LENGTH"
+ notContains "col=AVG_ROW_LENGTH"
+ notContains "col=INDEX_LENGTH"
+ }
+
+ explain {
+ verbose true
+ sql """
+ SELECT UPDATE_TIME
+ FROM information_schema.tables
+ WHERE TABLE_SCHEMA = 'information_schema'
+ """
+ contains "TABLE: information_schema.tables"
+ contains "final projections: UPDATE_TIME"
+ contains "col=TABLE_SCHEMA"
+ contains "col=UPDATE_TIME"
+ notContains "col=TABLE_NAME"
+ assertNoTabletStatusColumns.delegate = delegate
+ assertNoTabletStatusColumns.call()
+ }
+
+ explain {
+ verbose true
+ sql """
+ SELECT TABLE_NAME, UPDATE_TIME
+ FROM information_schema.tables
+ WHERE TABLE_SCHEMA = 'information_schema'
+ """
+ contains "TABLE: information_schema.tables"
+ contains "final projections: TABLE_NAME"
+ contains "UPDATE_TIME"
+ contains "col=TABLE_SCHEMA"
+ contains "col=TABLE_NAME"
+ contains "col=UPDATE_TIME"
+ assertNoTabletStatusColumns.delegate = delegate
+ assertNoTabletStatusColumns.call()
+ }
+
+ explain {
+ verbose true
+ sql """
+ SELECT TABLE_NAME, UPDATE_TIME
+ FROM information_schema.tables
+ WHERE TABLE_SCHEMA = 'information_schema'
+ AND TABLE_NAME = 'tables'
+ """
+ contains "TABLE: information_schema.tables"
+ contains "final projections: TABLE_NAME"
+ contains "UPDATE_TIME"
+ contains "col=TABLE_SCHEMA"
+ contains "col=TABLE_NAME"
+ contains "col=UPDATE_TIME"
+ assertNoTabletStatusColumns.delegate = delegate
+ assertNoTabletStatusColumns.call()
+ }
+
+ explain {
+ verbose true
+ sql """
+ SELECT TABLE_NAME, ENGINE, VERSION, ROW_FORMAT, TABLE_ROWS,
AVG_ROW_LENGTH,
+ DATA_LENGTH, MAX_DATA_LENGTH, INDEX_LENGTH, DATA_FREE,
AUTO_INCREMENT,
+ CREATE_TIME, UPDATE_TIME, CHECK_TIME, TABLE_COLLATION,
CHECKSUM,
+ CREATE_OPTIONS, TABLE_COMMENT
+ FROM information_schema.tables
+ WHERE TABLE_SCHEMA = 'information_schema'
+ AND TABLE_NAME LIKE 'tables'
+ """
+ contains "TABLE: information_schema.tables"
+ contains "col=TABLE_ROWS"
+ contains "col=DATA_LENGTH"
+ contains "col=AVG_ROW_LENGTH"
+ contains "col=INDEX_LENGTH"
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]