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

kharekartik 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 9c3a16be74d Fix retry of partial table deletion (#16835)
9c3a16be74d is described below

commit 9c3a16be74d3f4ed150b1de9acb289c0edbcaece
Author: Shounak kulkarni <[email protected]>
AuthorDate: Wed Sep 17 19:25:50 2025 +0530

    Fix retry of partial table deletion (#16835)
    
    * Bubble up meaningful exception for missing IS during table update
    
    * Update table config only when required. Ignore table update failures.
    
    * Add test to ensure table deletion retry goes through
---
 .../api/resources/PinotTableRestletResource.java   | 13 ++++++--
 .../helix/core/PinotHelixResourceManager.java      |  4 +++
 .../api/PinotTableRestletResourceTest.java         | 36 ++++++++++++++++++++++
 .../utils/builder/ControllerRequestURLBuilder.java |  4 +++
 4 files changed, 55 insertions(+), 2 deletions(-)

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 898fd26e7b0..a27d5ce3853 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
@@ -509,11 +509,20 @@ public class PinotTableRestletResource {
     }
     Map<String, Map<String, String>> taskTypeConfigsMap = 
tableConfig.getTaskConfig().getTaskTypeConfigsMap();
     Set<String> taskTypes = taskTypeConfigsMap.keySet();
+    boolean tableConfigChanged = false;
     for (String taskType : taskTypes) {
       // remove the task schedules to avoid task being scheduled during table 
deletion
-      taskTypeConfigsMap.get(taskType).remove(PinotTaskManager.SCHEDULE_KEY);
+      tableConfigChanged =
+          tableConfigChanged || 
taskTypeConfigsMap.get(taskType).remove(PinotTaskManager.SCHEDULE_KEY) != null;
+    }
+    if (tableConfigChanged) {
+      try {
+        pinotHelixResourceManager.updateTableConfig(tableConfig);
+      } catch (Exception e) {
+        LOGGER.warn("Unable to remove the task schedules, going ahead with 
table deletion anyways. "
+            + "Reason for failure : {}", e.getMessage());
+      }
     }
-    pinotHelixResourceManager.updateTableConfig(tableConfig);
     List<String> pendingTasks = new ArrayList<>();
     for (String taskType : taskTypes) {
       Map<String, TaskState> taskStates;
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
index 97e2cb0a908..fe20b4359de 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
@@ -2187,6 +2187,10 @@ public class PinotHelixResourceManager {
 
     // Update IdealState replication
     IdealState idealState = 
_helixAdmin.getResourceIdealState(_helixClusterName, tableNameWithType);
+    Preconditions.checkArgument(idealState != null,
+        "Ideal state is not present for the table " + tableNameWithType + ". "
+            + "Its possible due to ongoing/incomplete table deletion. "
+            + "Please re-trigger the table delete operation to clean it up and 
recreate the table");
     String replicationConfigured = 
Integer.toString(tableConfig.getReplication());
     if (!idealState.getReplicas().equals(replicationConfigured)) {
       HelixHelper.updateIdealState(_helixZkManager, tableNameWithType, is -> {
diff --git 
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTableRestletResourceTest.java
 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTableRestletResourceTest.java
index 7af7ad37345..f0261829338 100644
--- 
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTableRestletResourceTest.java
+++ 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTableRestletResourceTest.java
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -63,6 +65,7 @@ import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
 import org.apache.pinot.spi.utils.builder.TableNameBuilder;
 import org.apache.pinot.util.TestUtils;
 import org.mockito.Mockito;
+import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
@@ -1052,6 +1055,39 @@ public class PinotTableRestletResourceTest extends 
ControllerTest {
         
InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.name(),
 false);
   }
 
+  @Test
+  public void testTableDeletionFromPreviousIncompleteDeletion()
+      throws Exception {
+    String tableName = "testTableDeletionValidation";
+    DEFAULT_INSTANCE.addDummySchema(tableName);
+
+    TableConfig offlineTableConfig = getOfflineTableBuilder(tableName)
+        .setTaskConfig(new TableTaskConfig(ImmutableMap.of(
+            MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE, 
Map.of("schedule", "0 0 * * * ? *"))))
+        .build();
+
+    String tableNameWithType = offlineTableConfig.getTableName();
+    String creationResponse = sendPostRequest(_createTableUrl, 
offlineTableConfig.toJsonString());
+    assertEquals(creationResponse,
+        "{\"unrecognizedProperties\":{},\"status\":\"Table " + 
tableNameWithType + " successfully added\"}");
+
+    String encodedISPath =
+        URLEncoder.encode("/ControllerTest/IDEALSTATES/" + tableNameWithType, 
Charset.defaultCharset());
+    
sendDeleteRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forZkDelete(encodedISPath));
+    // Table deletion will throw exception but internally it should clean up 
all the dangling table resources
+    Assert.expectThrows(IOException.class,
+        () -> 
sendDeleteRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableDelete(tableName)));
+
+    String encodedTableConfigPath = 
URLEncoder.encode("/ControllerTest/PROPERTYSTORE/CONFIGS/TABLE/"
+        + tableNameWithType, Charset.defaultCharset());
+    try {
+      
sendGetRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forZkGet(encodedTableConfigPath));
+      fail("Table config node should be deleted so get request should fail");
+    } catch (IOException e) {
+      assertTrue(e.getMessage().contains(tableNameWithType + " does not 
exist"));
+    }
+  }
+
   @Test
   public void testTableTasksValidationWithNoDanglingTasks()
       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 1413a367954..693de77733f 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
@@ -652,6 +652,10 @@ public class ControllerRequestURLBuilder {
     return StringUtil.join("/", _baseUrl, "zk/delete");
   }
 
+  public String forZkDelete(String path) {
+    return StringUtil.join("/", _baseUrl, "zk/delete", "?path=" + path);
+  }
+
   public String forZkGet(String path) {
     return StringUtil.join("/", _baseUrl, "zk/get", "?path=" + path);
   }


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

Reply via email to