This is an automated email from the ASF dual-hosted git repository.

saurabhd336 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 49196ec7df Add table indexes API (#11576)
49196ec7df is described below

commit 49196ec7dfb63f7444ae5128183ce209f247456b
Author: Saurabh Dubey <saurabhd...@gmail.com>
AuthorDate: Mon Sep 18 20:16:40 2023 +0530

    Add table indexes API (#11576)
    
    * Add table indexes API
    
    * Add tests
    
    * Lint fix
    
    * Address comments
    
    * Fix test
    
    ---------
    
    Co-authored-by: Saurabh Dubey <saurabh.dubey@Saurabhs-MacBook-Pro.local>
---
 .../server/TableIndexMetadataResponse.java         | 44 ++++++++++++++
 .../api/resources/PinotTableRestletResource.java   | 68 ++++++++++++++++++++++
 .../tests/OfflineClusterIntegrationTest.java       | 34 +++++++++++
 .../pinot/server/api/resources/TablesResource.java | 49 ++++++++++++++++
 .../pinot/server/api/TablesResourceTest.java       | 37 ++++++++++++
 5 files changed, 232 insertions(+)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/response/server/TableIndexMetadataResponse.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/response/server/TableIndexMetadataResponse.java
new file mode 100644
index 0000000000..eeb836c919
--- /dev/null
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/response/server/TableIndexMetadataResponse.java
@@ -0,0 +1,44 @@
+/**
+ * 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.pinot.common.response.server;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Map;
+
+
+public class TableIndexMetadataResponse {
+  private final long _totalOnlineSegments;
+  private final Map<String, Map<String, Integer>> _columnToIndexesCount;
+
+  @JsonCreator
+  public TableIndexMetadataResponse(@JsonProperty("totalOnlineSegments") long 
totalOnlineSegments,
+      @JsonProperty("columnToIndexesCount") Map<String, Map<String, Integer>> 
columnToIndexesCount) {
+    _totalOnlineSegments = totalOnlineSegments;
+    _columnToIndexesCount = columnToIndexesCount;
+  }
+
+  public long getTotalOnlineSegments() {
+    return _totalOnlineSegments;
+  }
+
+  public Map<String, Map<String, Integer>> getColumnToIndexesCount() {
+    return _columnToIndexesCount;
+  }
+}
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
index c79b6e4d04..0cf4e4f8ad 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
@@ -81,6 +81,7 @@ import org.apache.pinot.common.metadata.ZKMetadataProvider;
 import org.apache.pinot.common.metadata.controllerjob.ControllerJobType;
 import org.apache.pinot.common.metrics.ControllerMeter;
 import org.apache.pinot.common.metrics.ControllerMetrics;
+import org.apache.pinot.common.response.server.TableIndexMetadataResponse;
 import org.apache.pinot.common.restlet.resources.TableSegmentValidationInfo;
 import org.apache.pinot.common.utils.helix.HelixHelper;
 import org.apache.pinot.controller.ControllerConf;
@@ -964,6 +965,73 @@ public class PinotTableRestletResource {
     return segmentsMetadata;
   }
 
+  @GET
+  @Path("tables/{tableName}/indexes")
+  @Authorize(targetType = TargetType.TABLE, paramName = "tableName", action = 
Actions.Table.GET_METADATA)
+  @Produces(MediaType.APPLICATION_JSON)
+  @ApiOperation(value = "Get the aggregate index details of all segments for a 
table", notes = "Get the aggregate "
+      + "index details of all segments for a table")
+  public String getTableIndexes(
+      @ApiParam(value = "Name of the table", required = true) 
@PathParam("tableName") String tableName,
+      @ApiParam(value = "OFFLINE|REALTIME") @QueryParam("type") String 
tableTypeStr) {
+    LOGGER.info("Received a request to fetch aggregate metadata for a table 
{}", tableName);
+    TableType tableType = Constants.validateTableType(tableTypeStr);
+    String tableNameWithType =
+        
ResourceUtils.getExistingTableNamesWithType(_pinotHelixResourceManager, 
tableName, tableType, LOGGER).get(0);
+
+    String tableIndexMetadata;
+    try {
+      JsonNode segmentsMetadataJson = 
getAggregateIndexMetadataFromServer(tableNameWithType);
+      tableIndexMetadata = 
JsonUtils.objectToPrettyString(segmentsMetadataJson);
+    } catch (InvalidConfigException e) {
+      throw new ControllerApplicationException(LOGGER, e.getMessage(), 
Response.Status.BAD_REQUEST);
+    } catch (IOException ioe) {
+      throw new ControllerApplicationException(LOGGER, "Error parsing Pinot 
server response: " + ioe.getMessage(),
+          Response.Status.INTERNAL_SERVER_ERROR, ioe);
+    }
+    return tableIndexMetadata;
+  }
+
+  private JsonNode getAggregateIndexMetadataFromServer(String 
tableNameWithType)
+      throws InvalidConfigException, JsonProcessingException {
+    final Map<String, List<String>> serverToSegments =
+        _pinotHelixResourceManager.getServerToSegmentsMap(tableNameWithType);
+
+    BiMap<String, String> serverEndPoints =
+        
_pinotHelixResourceManager.getDataInstanceAdminEndpoints(serverToSegments.keySet());
+    CompletionServiceHelper completionServiceHelper =
+        new CompletionServiceHelper(_executor, _connectionManager, 
serverEndPoints);
+
+    List<String> serverUrls = new ArrayList<>();
+    BiMap<String, String> endpointsToServers = serverEndPoints.inverse();
+    for (String endpoint : endpointsToServers.keySet()) {
+      String segmentIndexesEndpoint = endpoint + 
String.format("/tables/%s/indexes", tableNameWithType);
+      serverUrls.add(segmentIndexesEndpoint);
+    }
+
+    CompletionServiceHelper.CompletionServiceResponse serviceResponse =
+        completionServiceHelper.doMultiGetRequest(serverUrls, null, true, 
10000);
+
+    int totalSegments = 0;
+    Map<String, Map<String, Integer>> columnToIndexCountMap = new HashMap<>();
+    for (Map.Entry<String, String> streamResponse : 
serviceResponse._httpResponses.entrySet()) {
+      String responseString = streamResponse.getValue();
+      TableIndexMetadataResponse response = 
JsonUtils.stringToObject(responseString, TableIndexMetadataResponse.class);
+      totalSegments += response.getTotalOnlineSegments();
+      response.getColumnToIndexesCount().forEach((col, indexToCount) -> {
+        Map<String, Integer> indexCountMap = 
columnToIndexCountMap.computeIfAbsent(col, c -> new HashMap<>());
+        indexToCount.forEach((indexName, count) -> {
+          indexCountMap.merge(indexName, count, Integer::sum);
+        });
+      });
+    }
+
+    TableIndexMetadataResponse tableIndexMetadataResponse =
+        new TableIndexMetadataResponse(totalSegments, columnToIndexCountMap);
+
+    return JsonUtils.objectToJsonNode(tableIndexMetadataResponse);
+  }
+
   /**
    * This is a helper method to get the metadata for all segments for a given 
table name.
    * @param tableNameWithType name of the table along with its type
diff --git 
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java
 
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java
index 0be7e109c6..89d2d24495 100644
--- 
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java
+++ 
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java
@@ -54,12 +54,14 @@ import org.apache.pinot.common.datatable.DataTableFactory;
 import org.apache.pinot.common.exception.HttpErrorStatusException;
 import org.apache.pinot.common.exception.QueryException;
 import org.apache.pinot.common.metadata.segment.SegmentZKMetadata;
+import org.apache.pinot.common.response.server.TableIndexMetadataResponse;
 import org.apache.pinot.common.utils.FileUploadDownloadClient;
 import org.apache.pinot.common.utils.ServiceStatus;
 import org.apache.pinot.common.utils.SimpleHttpResponse;
 import org.apache.pinot.common.utils.http.HttpClient;
 import org.apache.pinot.core.common.datatable.DataTableBuilderFactory;
 import org.apache.pinot.core.operator.query.NonScanBasedAggregationOperator;
+import org.apache.pinot.segment.spi.index.StandardIndexes;
 import 
org.apache.pinot.segment.spi.index.startree.AggregationFunctionColumnPair;
 import org.apache.pinot.spi.config.instance.InstanceType;
 import org.apache.pinot.spi.config.table.IndexingConfig;
@@ -2911,6 +2913,38 @@ public class OfflineClusterIntegrationTest extends 
BaseClusterIntegrationTestSet
     super.testHardcodedServerPartitionedSqlQueries();
   }
 
+  @Test
+  public void testIndexMetadataAPI()
+      throws Exception {
+    TableIndexMetadataResponse tableIndexMetadataResponse =
+        JsonUtils.stringToObject(sendGetRequest(getControllerBaseApiUrl() + 
"/tables/mytable/indexes?type=OFFLINE"),
+            TableIndexMetadataResponse.class);
+
+    getInvertedIndexColumns().forEach(column -> {
+      Assert.assertEquals(
+          (long) 
tableIndexMetadataResponse.getColumnToIndexesCount().get(column).get(StandardIndexes.INVERTED_ID),
+          tableIndexMetadataResponse.getTotalOnlineSegments());
+    });
+
+    getNoDictionaryColumns().forEach(column -> {
+      Assert.assertEquals(
+          (long) 
tableIndexMetadataResponse.getColumnToIndexesCount().get(column).get(StandardIndexes.DICTIONARY_ID),
+          0);
+    });
+
+    getRangeIndexColumns().forEach(column -> {
+      Assert.assertEquals(
+          (long) 
tableIndexMetadataResponse.getColumnToIndexesCount().get(column).get(StandardIndexes.RANGE_ID),
+          tableIndexMetadataResponse.getTotalOnlineSegments());
+    });
+
+    getBloomFilterColumns().forEach(column -> {
+      Assert.assertEquals(
+          (long) 
tableIndexMetadataResponse.getColumnToIndexesCount().get(column).get(StandardIndexes.BLOOM_FILTER_ID),
+          tableIndexMetadataResponse.getTotalOnlineSegments());
+    });
+  }
+
   @Test
   public void testAggregateMetadataAPI()
       throws IOException {
diff --git 
a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
 
b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
index 0e261e143d..b08833b966 100644
--- 
a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
+++ 
b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
@@ -62,6 +62,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.helix.model.IdealState;
 import org.apache.pinot.common.metadata.ZKMetadataProvider;
 import org.apache.pinot.common.metadata.segment.SegmentZKMetadata;
+import org.apache.pinot.common.response.server.TableIndexMetadataResponse;
 import org.apache.pinot.common.restlet.resources.ResourceUtils;
 import org.apache.pinot.common.restlet.resources.SegmentConsumerInfo;
 import org.apache.pinot.common.restlet.resources.TableMetadataInfo;
@@ -83,6 +84,8 @@ import 
org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentImp
 import org.apache.pinot.segment.spi.ColumnMetadata;
 import org.apache.pinot.segment.spi.ImmutableSegment;
 import org.apache.pinot.segment.spi.IndexSegment;
+import org.apache.pinot.segment.spi.datasource.DataSource;
+import org.apache.pinot.segment.spi.index.IndexService;
 import org.apache.pinot.segment.spi.index.IndexType;
 import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl;
 import org.apache.pinot.server.access.AccessControlFactory;
@@ -93,6 +96,7 @@ import org.apache.pinot.spi.data.FieldSpec;
 import org.apache.pinot.spi.data.FieldSpec.DataType;
 import org.apache.pinot.spi.stream.ConsumerPartitionState;
 import 
org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel;
+import org.apache.pinot.spi.utils.JsonUtils;
 import org.apache.pinot.spi.utils.builder.TableNameBuilder;
 import org.roaringbitmap.buffer.MutableRoaringBitmap;
 import org.slf4j.Logger;
@@ -285,6 +289,51 @@ public class TablesResource {
     return ResourceUtils.convertToJsonString(tableMetadataInfo);
   }
 
+  @GET
+  @Encoded
+  @Path("/tables/{tableName}/indexes")
+  @Produces(MediaType.APPLICATION_JSON)
+  @ApiOperation(value = "Provide index metadata", notes = "Provide index 
details for the table")
+  @ApiResponses(value = {
+      @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500, 
message = "Internal server error",
+      response = ErrorInfo.class), @ApiResponse(code = 404, message = "Table 
or segment not found", response =
+      ErrorInfo.class)
+  })
+  public String getTableIndexes(
+      @ApiParam(value = "Table name including type", required = true, example 
= "myTable_OFFLINE")
+      @PathParam("tableName") String tableName)
+      throws Exception {
+    TableDataManager tableDataManager = 
ServerResourceUtils.checkGetTableDataManager(_serverInstance, tableName);
+    List<SegmentDataManager> allSegments = 
tableDataManager.acquireAllSegments();
+    try {
+      int totalSegmentCount = 0;
+      Map<String, Map<String, Integer>> columnToIndexesCount = new HashMap<>();
+      for (SegmentDataManager segmentDataManager : allSegments) {
+        if (segmentDataManager instanceof RealtimeSegmentDataManager) {
+          // REALTIME segments may not have indexes since not all indexes have 
mutable implementations
+          continue;
+        }
+        totalSegmentCount++;
+        IndexSegment segment = segmentDataManager.getSegment();
+        segment.getColumnNames().forEach(col -> {
+          columnToIndexesCount.putIfAbsent(col, new HashMap<>());
+          DataSource colDataSource = segment.getDataSource(col);
+          IndexService.getInstance().getAllIndexes().forEach(idxType -> {
+            int count = colDataSource.getIndex(idxType) != null ? 1 : 0;
+            columnToIndexesCount.get(col).merge(idxType.getId(), count, 
Integer::sum);
+          });
+        });
+      }
+      TableIndexMetadataResponse tableIndexMetadataResponse =
+          new TableIndexMetadataResponse(totalSegmentCount, 
columnToIndexesCount);
+      return JsonUtils.objectToString(tableIndexMetadataResponse);
+    } finally {
+      for (SegmentDataManager segmentDataManager : allSegments) {
+        tableDataManager.releaseSegment(segmentDataManager);
+      }
+    }
+  }
+
   @GET
   @Encoded
   @Path("/tables/{tableName}/segments/{segmentName}/metadata")
diff --git 
a/pinot-server/src/test/java/org/apache/pinot/server/api/TablesResourceTest.java
 
b/pinot-server/src/test/java/org/apache/pinot/server/api/TablesResourceTest.java
index 7e7c4b7fba..488350aa17 100644
--- 
a/pinot-server/src/test/java/org/apache/pinot/server/api/TablesResourceTest.java
+++ 
b/pinot-server/src/test/java/org/apache/pinot/server/api/TablesResourceTest.java
@@ -22,9 +22,12 @@ import com.fasterxml.jackson.databind.JsonNode;
 import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import javax.ws.rs.core.Response;
 import org.apache.commons.io.FileUtils;
+import org.apache.pinot.common.response.server.TableIndexMetadataResponse;
 import org.apache.pinot.common.restlet.resources.TableMetadataInfo;
 import org.apache.pinot.common.restlet.resources.TableSegments;
 import org.apache.pinot.common.restlet.resources.TablesList;
@@ -35,6 +38,8 @@ import org.apache.pinot.segment.spi.ImmutableSegment;
 import org.apache.pinot.segment.spi.IndexSegment;
 import org.apache.pinot.segment.spi.SegmentMetadata;
 import org.apache.pinot.segment.spi.V1Constants;
+import org.apache.pinot.segment.spi.datasource.DataSource;
+import org.apache.pinot.segment.spi.index.IndexService;
 import org.apache.pinot.segment.spi.index.StandardIndexes;
 import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl;
 import 
org.apache.pinot.segment.spi.index.mutable.ThreadSafeMutableRoaringBitmap;
@@ -109,6 +114,38 @@ public class TablesResourceTest extends BaseResourceTest {
     Assert.assertEquals(response.getStatus(), 
Response.Status.NOT_FOUND.getStatusCode());
   }
 
+  @Test
+  public void getTableIndexes()
+      throws Exception {
+    String tableIndexesPath =
+        "/tables/" + 
TableNameBuilder.forType(TableType.OFFLINE).tableNameWithType(TABLE_NAME) + 
"/indexes";
+
+    JsonNode jsonResponse = 
JsonUtils.stringToJsonNode(_webTarget.path(tableIndexesPath).request().get(String.class));
+    TableIndexMetadataResponse tableIndexMetadataResponse =
+        JsonUtils.jsonNodeToObject(jsonResponse, 
TableIndexMetadataResponse.class);
+    Assert.assertNotNull(tableIndexMetadataResponse);
+    Assert.assertEquals(tableIndexMetadataResponse.getTotalOnlineSegments(), 
_offlineIndexSegments.size());
+
+    Map<String, Map<String, Integer>> columnToIndexCountMap = new HashMap<>();
+    for (ImmutableSegment segment : _offlineIndexSegments) {
+      segment.getColumnNames().forEach(colName -> {
+        DataSource dataSource = segment.getDataSource(colName);
+        columnToIndexCountMap.putIfAbsent(colName, new HashMap<>());
+        IndexService.getInstance().getAllIndexes().forEach(indexType -> {
+          int count = dataSource.getIndex(indexType) != null ? 1 : 0;
+          columnToIndexCountMap.get(colName).merge(indexType.getId(), count, 
Integer::sum);
+        });
+      });
+    }
+
+    Assert.assertEquals(tableIndexMetadataResponse.getColumnToIndexesCount(), 
columnToIndexCountMap);
+
+    // No such table
+    Response response = 
_webTarget.path("/tables/noSuchTable/indexes").request().get(Response.class);
+    Assert.assertNotNull(response);
+    Assert.assertEquals(response.getStatus(), 
Response.Status.NOT_FOUND.getStatusCode());
+  }
+
   @Test
   public void getTableMetadata()
       throws Exception {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org
For additional commands, e-mail: commits-h...@pinot.apache.org

Reply via email to