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

shauryachats 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 94711630d28 Create /tableConfigs/tune endpoint that validates and 
tunes TableConfigs and returns the tuned config. (#18056)
94711630d28 is described below

commit 94711630d28f1b00578a53dbfd647720da3eac97
Author: Rekha Seethamraju <[email protected]>
AuthorDate: Tue May 19 14:25:41 2026 -0700

    Create /tableConfigs/tune endpoint that validates and tunes TableConfigs 
and returns the tuned config. (#18056)
---
 .../api/resources/TableConfigsRestletResource.java | 56 +++++++++++++---
 .../api/TableConfigsRestletResourceTest.java       | 78 ++++++++++++++++++++++
 .../utils/builder/ControllerRequestURLBuilder.java |  4 ++
 3 files changed, 129 insertions(+), 9 deletions(-)

diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java
index 87f27e0d38c..e37ee4d086f 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java
@@ -240,13 +240,13 @@ public class TableConfigsRestletResource {
       }
 
       if (offlineTableConfig != null) {
-        tuneConfig(offlineTableConfig, schema);
+        applyTuning(offlineTableConfig, schema);
         if (!ignoreActiveTasks) {
           PinotTableRestletResource.tableTasksValidation(offlineTableConfig, 
_pinotHelixTaskResourceManager);
         }
       }
       if (realtimeTableConfig != null) {
-        tuneConfig(realtimeTableConfig, schema);
+        applyTuning(realtimeTableConfig, schema);
         if (!ignoreActiveTasks) {
           PinotTableRestletResource.tableTasksValidation(realtimeTableConfig, 
_pinotHelixTaskResourceManager);
         }
@@ -408,7 +408,7 @@ public class TableConfigsRestletResource {
       LOGGER.info("Updated schema: {}", tableName);
 
       if (offlineTableConfig != null) {
-        tuneConfig(offlineTableConfig, schema);
+        applyTuning(offlineTableConfig, schema);
         if (_pinotHelixResourceManager.hasOfflineTable(tableName)) {
           _pinotHelixResourceManager.updateTableConfig(offlineTableConfig, 
forceTableSchemaUpdate);
           LOGGER.info("Updated offline table config: {}", tableName);
@@ -418,7 +418,7 @@ public class TableConfigsRestletResource {
         }
       }
       if (realtimeTableConfig != null) {
-        tuneConfig(realtimeTableConfig, schema);
+        applyTuning(realtimeTableConfig, schema);
         if (_pinotHelixResourceManager.hasRealtimeTable(tableName)) {
           _pinotHelixResourceManager.updateTableConfig(realtimeTableConfig, 
forceTableSchemaUpdate);
           LOGGER.info("Updated realtime table config: {}", tableName);
@@ -460,6 +460,47 @@ public class TableConfigsRestletResource {
           + "(ALL|TASK|UPSERT|TENANT|MINION_INSTANCES|ACTIVE_TASKS)")
       @QueryParam("validationTypesToSkip") @Nullable String typesToSkip, 
@Context HttpHeaders httpHeaders,
       @Context Request request) {
+    Pair<TableConfigs, Map<String, Object>> tableConfigsAndUnrecognizedProps =
+        parseAndValidateTableConfigs(tableConfigsStr, typesToSkip, 
httpHeaders, request);
+    TableConfigs tableConfigs = tableConfigsAndUnrecognizedProps.getLeft();
+    ObjectNode response = JsonUtils.objectToJsonNode(tableConfigs).deepCopy();
+    response.set("unrecognizedProperties", 
JsonUtils.objectToJsonNode(tableConfigsAndUnrecognizedProps.getRight()));
+    return response.toString();
+  }
+
+  /**
+   * Validates and tunes the {@link TableConfigs} as provided in the 
tableConfigsStr json, by applying tuner configs,
+   * ensuring min replicas and storage quota constraints, and returns the 
tuned TableConfigs.
+   */
+  @POST
+  @Path("/tableConfigs/tune")
+  @Produces(MediaType.APPLICATION_JSON)
+  @ApiOperation(value = "Tune the TableConfigs",
+      notes = "Validates and applies tuning (tuner configs, min replicas, 
storage quota) to the TableConfigs, "
+          + "returning the result that would be stored on create/update")
+  @ManualAuthorization // performed after parsing TableConfigs
+  public String tuneConfig(String tableConfigsStr,
+      @ApiParam(value = "comma separated list of validation type(s) to skip. 
supported types: "
+          + "(ALL|TASK|UPSERT|TENANT|MINION_INSTANCES|ACTIVE_TASKS)")
+      @QueryParam("validationTypesToSkip") @Nullable String typesToSkip, 
@Context HttpHeaders httpHeaders,
+      @Context Request request) {
+    Pair<TableConfigs, Map<String, Object>> tableConfigsAndUnrecognizedProps =
+        parseAndValidateTableConfigs(tableConfigsStr, typesToSkip, 
httpHeaders, request);
+    TableConfigs tableConfigs = tableConfigsAndUnrecognizedProps.getLeft();
+    Schema schema = tableConfigs.getSchema();
+    if (tableConfigs.getOffline() != null) {
+      applyTuning(tableConfigs.getOffline(), schema);
+    }
+    if (tableConfigs.getRealtime() != null) {
+      applyTuning(tableConfigs.getRealtime(), schema);
+    }
+    ObjectNode response = JsonUtils.objectToJsonNode(tableConfigs).deepCopy();
+    response.set("unrecognizedProperties", 
JsonUtils.objectToJsonNode(tableConfigsAndUnrecognizedProps.getRight()));
+    return response.toString();
+  }
+
+  private Pair<TableConfigs, Map<String, Object>> 
parseAndValidateTableConfigs(String tableConfigsStr,
+      @Nullable String typesToSkip, HttpHeaders httpHeaders, Request request) {
     Pair<TableConfigs, Map<String, Object>> tableConfigsAndUnrecognizedProps;
     try {
       tableConfigsAndUnrecognizedProps =
@@ -482,13 +523,10 @@ public class TableConfigsRestletResource {
     if (!accessControl.hasAccess(httpHeaders, TargetType.TABLE, rawTableName, 
Actions.Table.VALIDATE_TABLE_CONFIGS)) {
       throw new ControllerApplicationException(LOGGER, "Permission denied", 
Response.Status.FORBIDDEN);
     }
-
-    ObjectNode response = JsonUtils.objectToJsonNode(tableConfigs).deepCopy();
-    response.set("unrecognizedProperties", 
JsonUtils.objectToJsonNode(tableConfigsAndUnrecognizedProps.getRight()));
-    return response.toString();
+    return tableConfigsAndUnrecognizedProps;
   }
 
-  private void tuneConfig(TableConfig tableConfig, Schema schema) {
+  private void applyTuning(TableConfig tableConfig, Schema schema) {
     TableConfigTunerUtils.applyTunerConfigs(_pinotHelixResourceManager, 
tableConfig, schema, Collections.emptyMap());
     TableConfigUtils.ensureMinReplicas(tableConfig, 
_controllerConf.getDefaultTableMinReplicas());
     TableConfigUtils.ensureStorageQuotaConstraints(tableConfig, 
_controllerConf.getDimTableMaxSize());
diff --git 
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java
 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java
index 75552355c60..1e3e4e4b4c1 100644
--- 
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java
+++ 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java
@@ -698,6 +698,84 @@ public class TableConfigsRestletResourceTest extends 
ControllerTest {
     adminClient.getSchemaClient().deleteSchema(tableName);
   }
 
+  @Test
+  public void testTuneConfig()
+      throws IOException {
+    String tuneConfigUrl = 
DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableConfigsTune();
+
+    String tableName = "testTune";
+    TableConfigs tableConfigs;
+
+    // invalid json
+    try {
+      tableConfigs = new TableConfigs(tableName, createDummySchema(tableName), 
createOfflineTableConfig(tableName),
+          createRealtimeTableConfig(tableName));
+      sendPostRequest(tuneConfigUrl, 
tableConfigs.toPrettyJsonString().replace("\"offline\"", "offline\""));
+      fail("Tune of a TableConfigs with invalid json string should have 
failed");
+    } catch (Exception e) {
+      // expected
+    }
+
+    // null table configs
+    try {
+      tableConfigs = new TableConfigs(tableName, createDummySchema(tableName), 
null, null);
+      sendPostRequest(tuneConfigUrl, tableConfigs.toPrettyJsonString());
+      fail("Tune of a TableConfigs with null offline and realtime tableConfig 
should have failed");
+    } catch (Exception e) {
+      // expected
+    }
+
+    // replicas are bumped up to min replicas
+    String tableName1 = "testTuneReplicas";
+    TableConfig replicaTestOfflineTableConfig = 
createOfflineTableConfig(tableName1);
+    TableConfig replicaTestRealtimeTableConfig = 
createRealtimeTableConfig(tableName1);
+    replicaTestOfflineTableConfig.getValidationConfig().setReplication("1");
+    replicaTestRealtimeTableConfig.getValidationConfig().setReplication("1");
+    tableConfigs = new TableConfigs(tableName1, createDummySchema(tableName1), 
replicaTestOfflineTableConfig,
+        replicaTestRealtimeTableConfig);
+    String response = sendPostRequest(tuneConfigUrl, 
tableConfigs.toPrettyJsonString());
+    TableConfigs tuned = JsonUtils.stringToObject(response, 
TableConfigs.class);
+    Assert.assertEquals(tuned.getOffline().getReplication(), 
DEFAULT_MIN_NUM_REPLICAS);
+    Assert.assertEquals(tuned.getRealtime().getReplication(), 
DEFAULT_MIN_NUM_REPLICAS);
+
+    // dim table storage quota is capped
+    String tableName2 = "testTuneQuota";
+    TableConfig offlineDimTableConfig = 
createOfflineDimTableConfig(tableName2);
+    tableConfigs = new TableConfigs(tableName2, 
createDummySchemaWithPrimaryKey(tableName2), offlineDimTableConfig,
+        null);
+    response = sendPostRequest(tuneConfigUrl, 
tableConfigs.toPrettyJsonString());
+    tuned = JsonUtils.stringToObject(response, TableConfigs.class);
+    Assert.assertEquals(tuned.getOffline().getQuotaConfig().getStorage(),
+        DEFAULT_INSTANCE.getControllerConfig().getDimTableMaxSize());
+
+    // tuner configs are applied
+    String tableName3 = "testTuneTunerConfig";
+    Schema schema3 = createDummySchema(tableName3);
+    tableConfigs = new TableConfigs(tableName3, schema3, 
createOfflineTunerTableConfig(tableName3),
+        createRealtimeTunerTableConfig(tableName3));
+    response = sendPostRequest(tuneConfigUrl, 
tableConfigs.toPrettyJsonString());
+    tuned = JsonUtils.stringToObject(response, TableConfigs.class);
+    
Assert.assertTrue(tuned.getOffline().getIndexingConfig().getInvertedIndexColumns()
+        .containsAll(schema3.getDimensionNames()));
+    
Assert.assertTrue(tuned.getOffline().getIndexingConfig().getNoDictionaryColumns()
+        .containsAll(schema3.getMetricNames()));
+    
Assert.assertTrue(tuned.getRealtime().getIndexingConfig().getInvertedIndexColumns()
+        .containsAll(schema3.getDimensionNames()));
+    
Assert.assertTrue(tuned.getRealtime().getIndexingConfig().getNoDictionaryColumns()
+        .containsAll(schema3.getMetricNames()));
+
+    // response includes unrecognizedProperties
+    String tableName4 = "testTuneUnrecognized";
+    TableConfig offlineTableConfig = createOfflineTableConfig(tableName4);
+    tableConfigs = new TableConfigs(tableName4, createDummySchema(tableName4), 
offlineTableConfig, null);
+    ObjectNode tableConfigsJson = 
JsonUtils.objectToJsonNode(tableConfigs).deepCopy();
+    tableConfigsJson.put("illegalKey1", 1);
+    response = sendPostRequest(tuneConfigUrl, 
tableConfigsJson.toPrettyString());
+    JsonNode responseJson = JsonUtils.stringToJsonNode(response);
+    Assert.assertTrue(responseJson.has("unrecognizedProperties"));
+    
Assert.assertTrue(responseJson.get("unrecognizedProperties").has("/illegalKey1"));
+  }
+
   @Test
   public void testValidateConfigWithClusterValidationSkipTypes()
       throws Exception {
diff --git 
a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java
 
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java
index a7fa0901982..4346cc394b9 100644
--- 
a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java
+++ 
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java
@@ -409,6 +409,10 @@ public class ControllerRequestURLBuilder {
     return StringUtil.join("/", _baseUrl, "tableConfigs", "validate");
   }
 
+  public String forTableConfigsTune() {
+    return StringUtil.join("/", _baseUrl, "tableConfigs", "tune");
+  }
+
   public String forSegmentReload(String tableName, String segmentName, boolean 
forceDownload) {
     return StringUtil.join("/", _baseUrl, "segments", tableName, 
encode(segmentName),
         "reload?forceDownload=" + forceDownload);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to