Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,13 @@
@PublicEvolving
public interface Admin extends AutoCloseable {

/** Get the current server node information. asynchronously. */
/**
* Gets the current server node information and a machine resource snapshot asynchronously.
*
* <p>The returned {@link ServerNode} contains {@link ServerNode#resourceInfo()} for each
* currently available node. The resource snapshot includes CPU, memory and data disk
* information and is collected when this method is called.
*/
CompletableFuture<List<ServerNode>> getServerNodes();

/**
Expand Down Expand Up @@ -289,6 +295,17 @@ CompletableFuture<Void> createTable(
*/
CompletableFuture<List<String>> listTables(String databaseName);

/**
* Lists all tables in a database together with their current statistics asynchronously.
*
* <p>The returned table metadata is static metadata, while the statistics contain a snapshot of
* the data size and row count collected from the tablet servers.
*
* @param databaseName the name of the database
* @return a future containing the table metadata and statistics
*/
CompletableFuture<List<TableInfoWithStats>> listTableDetails(String databaseName);

/**
* Alter a table with the given {@code tableChanges}.
*
Expand Down Expand Up @@ -558,6 +575,9 @@ ListOffsetsResult listOffsets(
/**
* Asynchronously gets the statistics of this table.
*
* <p>The returned statistics include the row count and the current local physical data size
* reported by tablet leaders. The data size is nullable for servers that do not provide it.
*
* @return A future TableStats
*/
CompletableFuture<TableStats> getTableStats(TablePath tablePath);
Expand Down
154 changes: 140 additions & 14 deletions fluss-client/src/main/java/org/apache/fluss/client/admin/FlussAdmin.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import org.apache.fluss.client.metadata.RemoteLogManifestInfo;
import org.apache.fluss.client.utils.ClientRpcMessageUtils;
import org.apache.fluss.cluster.Cluster;
import org.apache.fluss.cluster.NodeResourceInfo;
import org.apache.fluss.cluster.ServerNode;
import org.apache.fluss.cluster.ServerType;
import org.apache.fluss.cluster.rebalance.GoalType;
import org.apache.fluss.cluster.rebalance.RebalanceProgress;
import org.apache.fluss.cluster.rebalance.ServerTag;
Expand Down Expand Up @@ -76,6 +78,8 @@
import org.apache.fluss.rpc.messages.GetLakeSnapshotRequest;
import org.apache.fluss.rpc.messages.GetLatestKvSnapshotsRequest;
import org.apache.fluss.rpc.messages.GetProducerOffsetsRequest;
import org.apache.fluss.rpc.messages.GetServerInfoRequest;
import org.apache.fluss.rpc.messages.GetServerInfoResponse;
import org.apache.fluss.rpc.messages.GetTableInfoRequest;
import org.apache.fluss.rpc.messages.GetTableSchemaRequest;
import org.apache.fluss.rpc.messages.GetTableStatsRequest;
Expand Down Expand Up @@ -180,6 +184,10 @@ public FlussAdmin(RpcClient client, MetadataUpdater metadataUpdater) {

@Override
public CompletableFuture<List<ServerNode>> getServerNodes() {
return getServerNodesWithoutResourceInfo().thenCompose(this::attachResourceInfo);
}

private CompletableFuture<List<ServerNode>> getServerNodesWithoutResourceInfo() {
CompletableFuture<List<ServerNode>> future = new CompletableFuture<>();
CompletableFuture.runAsync(
() -> {
Expand All @@ -203,6 +211,57 @@ public CompletableFuture<List<ServerNode>> getServerNodes() {
return future;
}

private CompletableFuture<List<ServerNode>> attachResourceInfo(List<ServerNode> serverNodes) {
List<CompletableFuture<ServerNode>> futures = new ArrayList<>(serverNodes.size());
for (ServerNode serverNode : serverNodes) {
futures.add(
getNodeResourceInfo(serverNode)
.thenApply(resourceInfo -> serverNode.withResourceInfo(resourceInfo)));
}
return CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0]))
.thenApply(
ignored -> {
List<ServerNode> nodes = new ArrayList<>(futures.size());
for (CompletableFuture<ServerNode> future : futures) {
nodes.add(future.join());
}
return nodes;
});
}

private CompletableFuture<NodeResourceInfo> getNodeResourceInfo(ServerNode serverNode) {
CompletableFuture<GetServerInfoResponse> responseFuture;
if (serverNode.serverType() == ServerType.COORDINATOR) {
responseFuture =
metadataUpdater
.newCoordinatorServerClient()
.getServerInfo(new GetServerInfoRequest());
} else {
TabletServerGateway tabletGateway =
metadataUpdater.newTabletServerClientForNode(serverNode.id());
if (tabletGateway == null) {
CompletableFuture<NodeResourceInfo> unavailable = new CompletableFuture<>();
unavailable.completeExceptionally(
new FlussRuntimeException(
"Tablet server is no longer available: " + serverNode));
return unavailable;
}
responseFuture = tabletGateway.getServerInfo(new GetServerInfoRequest());
}
return responseFuture.thenApply(FlussAdmin::toNodeResourceInfo);
}

private static NodeResourceInfo toNodeResourceInfo(GetServerInfoResponse response) {
return new NodeResourceInfo(
response.getCpuCores(),
response.getMemoryTotalBytes(),
response.getCpuUsageRatio(),
response.getMemoryUsedBytes(),
response.hasDataDiskTotalBytes() ? response.getDataDiskTotalBytes() : null,
response.hasDataDiskUsedBytes() ? response.getDataDiskUsedBytes() : null,
response.getCollectedAtMs());
}

@Override
public CompletableFuture<SchemaInfo> getTableSchema(TablePath tablePath) {
GetTableSchemaRequest request = new GetTableSchemaRequest();
Expand Down Expand Up @@ -373,6 +432,39 @@ public CompletableFuture<List<String>> listTables(String databaseName) {
return readOnlyGateway.listTables(request).thenApply(ListTablesResponse::getTableNamesList);
}

@Override
public CompletableFuture<List<TableInfoWithStats>> listTableDetails(String databaseName) {
return listTables(databaseName)
.thenCompose(
tableNames -> {
List<CompletableFuture<TableInfoWithStats>> detailFutures =
new ArrayList<>(tableNames.size());
for (String tableName : tableNames) {
TablePath tablePath = TablePath.of(databaseName, tableName);
CompletableFuture<TableInfo> tableInfoFuture =
getTableInfo(tablePath);
CompletableFuture<TableStats> tableStatsFuture =
getTableStats(tablePath);
detailFutures.add(
tableInfoFuture.thenCombine(
tableStatsFuture, TableInfoWithStats::new));
}

return CompletableFuture.allOf(
detailFutures.toArray(new CompletableFuture<?>[0]))
.thenApply(
ignored -> {
List<TableInfoWithStats> details =
new ArrayList<>(detailFutures.size());
for (CompletableFuture<TableInfoWithStats> future :
detailFutures) {
details.add(future.join());
}
return details;
});
});
}

@Override
public CompletableFuture<List<PartitionInfo>> listPartitionInfos(TablePath tablePath) {
return listPartitionInfos(tablePath, null);
Expand Down Expand Up @@ -557,27 +649,42 @@ public CompletableFuture<TableStats> getTableStats(TablePath tablePath) {
partitionInfos = Collections.singletonList(null);
}
// create all TableBuckets for each partition and bucket combination
Map<TableBucket, CompletableFuture<Long>> bucketToRowCountMap = new HashMap<>();
Map<TableBucket, CompletableFuture<BucketStats>> bucketToStatsMap = new HashMap<>();
for (PartitionInfo partitionInfo : partitionInfos) {
for (int bucket = 0; bucket < bucketCount; bucket++) {
TableBucket tb =
new TableBucket(
tableInfo.getTableId(),
partitionInfo == null ? null : partitionInfo.getPartitionId(),
bucket);
bucketToRowCountMap.put(tb, new CompletableFuture<>());
bucketToStatsMap.put(tb, new CompletableFuture<>());
}
}
Map<Integer, GetTableStatsRequest> requestMap =
prepareTableStatsRequests(
metadataUpdater, bucketToRowCountMap.keySet(), tablePath);
metadataUpdater, bucketToStatsMap.keySet(), tablePath);
sendTableStatsRequest(
metadataUpdater, tableInfo.getTableId(), requestMap, bucketToRowCountMap);
return FutureUtils.combineAll(bucketToRowCountMap.values())
metadataUpdater, tableInfo.getTableId(), requestMap, bucketToStatsMap);
return FutureUtils.combineAll(bucketToStatsMap.values())
.thenApply(
counts -> {
long totalRowCount = counts.stream().reduce(0L, Long::sum);
return new TableStats(totalRowCount);
stats -> {
long totalRowCount = 0L;
long totalDataSizeBytes = 0L;
long collectedAtMs = -1L;
boolean dataSizeAvailable = true;
for (BucketStats stat : stats) {
totalRowCount += stat.rowCount;
if (stat.dataSizeBytes == null) {
dataSizeAvailable = false;
} else {
totalDataSizeBytes += stat.dataSizeBytes;
}
collectedAtMs = Math.max(collectedAtMs, stat.collectedAtMs);
}
return new TableStats(
totalRowCount,
dataSizeAvailable ? totalDataSizeBytes : null,
collectedAtMs);
});
} catch (Exception e) {
throw new FlussRuntimeException(
Expand Down Expand Up @@ -826,7 +933,7 @@ private static void sendTableStatsRequest(
MetadataUpdater metadataUpdater,
long tableId,
Map<Integer, GetTableStatsRequest> leaderToRequestMap,
Map<TableBucket, CompletableFuture<Long>> bucketToRowCountMap) {
Map<TableBucket, CompletableFuture<BucketStats>> bucketToStatsMap) {
leaderToRequestMap.forEach(
(leader, request) -> {
TabletServerGateway gateway =
Expand All @@ -839,7 +946,7 @@ private static void sendTableStatsRequest(
.whenComplete(
(response, t) ->
handleTableStatsResponse(
response, t, tableId, bucketToRowCountMap));
response, t, tableId, bucketToStatsMap));
}
});
}
Expand All @@ -848,28 +955,47 @@ private static void handleTableStatsResponse(
GetTableStatsResponse response,
Throwable t,
long tableId,
Map<TableBucket, CompletableFuture<Long>> bucketToRowCountMap) {
Map<TableBucket, CompletableFuture<BucketStats>> bucketToStatsMap) {
if (t != null) {
// fail all futures to fail fast
bucketToRowCountMap.values().forEach(f -> f.completeExceptionally(t));
bucketToStatsMap.values().forEach(f -> f.completeExceptionally(t));
return;
}
long collectedAtMs = response.hasCollectedAtMs() ? response.getCollectedAtMs() : -1L;
for (PbTableStatsRespForBucket resp : response.getBucketsRespsList()) {
TableBucket tb =
new TableBucket(
tableId,
resp.hasPartitionId() ? resp.getPartitionId() : null,
resp.getBucketId());
if (resp.hasErrorCode()) {
bucketToRowCountMap
bucketToStatsMap
.get(tb)
.completeExceptionally(ApiError.fromErrorMessage(resp).exception());
} else {
bucketToRowCountMap.get(tb).complete(resp.getRowCount());
bucketToStatsMap
.get(tb)
.complete(
new BucketStats(
resp.getRowCount(),
resp.hasDataSizeBytes() ? resp.getDataSizeBytes() : null,
collectedAtMs));
}
}
}

private static final class BucketStats {
private final long rowCount;
private final @Nullable Long dataSizeBytes;
private final long collectedAtMs;

private BucketStats(long rowCount, @Nullable Long dataSizeBytes, long collectedAtMs) {
this.rowCount = rowCount;
this.dataSizeBytes = dataSizeBytes;
this.collectedAtMs = collectedAtMs;
}
}

private static Map<Integer, ListOffsetsRequest> prepareListOffsetsRequests(
MetadataUpdater metadataUpdater,
long tableId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.fluss.client.admin;

import org.apache.fluss.annotation.PublicEvolving;
import org.apache.fluss.metadata.TableInfo;
import org.apache.fluss.metadata.TableStats;

import java.util.Objects;

/**
* A table's static metadata together with its current statistics.
*
* @since 0.9
*/
@PublicEvolving
public final class TableInfoWithStats {

private final TableInfo tableInfo;
private final TableStats tableStats;

/** Creates a table information and statistics result. */
public TableInfoWithStats(TableInfo tableInfo, TableStats tableStats) {
this.tableInfo = tableInfo;
this.tableStats = tableStats;
}

/** Returns the static table metadata. */
public TableInfo getTableInfo() {
return tableInfo;
}

/** Returns the current table statistics. */
public TableStats getTableStats() {
return tableStats;
}

@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
TableInfoWithStats that = (TableInfoWithStats) other;
return Objects.equals(tableInfo, that.tableInfo)
&& Objects.equals(tableStats, that.tableStats);
}

@Override
public int hashCode() {
return Objects.hash(tableInfo, tableStats);
}

@Override
public String toString() {
return "TableInfoWithStats{"
+ "tableInfo="
+ tableInfo
+ ", tableStats="
+ tableStats
+ '}';
}
}
Loading