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

jtao 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 cb718b88875 Bypass storage quota check for segment refresh with 
smaller size even if the table is over quota (#16535)
cb718b88875 is described below

commit cb718b8887592cc407068135f7f942adbbf1d196
Author: Jiapeng Tao <[email protected]>
AuthorDate: Wed Aug 13 09:43:22 2025 -0700

    Bypass storage quota check for segment refresh with smaller size even if 
the table is over quota (#16535)
    
    Bypass storage quota check for segment refresh with smaller size even if 
the table is over quota
---
 .../PinotSegmentUploadDownloadRestletResource.java |  2 +-
 .../api/upload/SegmentValidationUtils.java         |  6 +++--
 .../controller/validation/StorageQuotaChecker.java | 31 ++++++++++++++++------
 .../validation/StorageQuotaCheckerTest.java        | 17 +++++++++++-
 4 files changed, 44 insertions(+), 12 deletions(-)

diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadDownloadRestletResource.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadDownloadRestletResource.java
index d4e07f364aa..9f2771cb3f9 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadDownloadRestletResource.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadDownloadRestletResource.java
@@ -400,7 +400,7 @@ public class PinotSegmentUploadDownloadRestletResource {
       } else {
         untarredSegmentSizeInBytes = FileUtils.sizeOfDirectory(tempSegmentDir);
       }
-      SegmentValidationUtils.checkStorageQuota(segmentName, 
untarredSegmentSizeInBytes, tableConfig,
+      SegmentValidationUtils.checkStorageQuota(segmentName, 
segmentSizeInBytes, untarredSegmentSizeInBytes, tableConfig,
           _storageQuotaChecker);
 
       // Encrypt segment
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/upload/SegmentValidationUtils.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/upload/SegmentValidationUtils.java
index ee6219876fb..30f25fd8535 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/upload/SegmentValidationUtils.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/upload/SegmentValidationUtils.java
@@ -57,11 +57,13 @@ public class SegmentValidationUtils {
     }
   }
 
-  public static void checkStorageQuota(String segmentName, long 
segmentSizeInBytes, TableConfig tableConfig,
+  public static void checkStorageQuota(String segmentName, long 
tarSegmentSizeInBytes, long untarredSegmentSizeInBytes,
+      TableConfig tableConfig,
       StorageQuotaChecker quotaChecker) {
     StorageQuotaChecker.QuotaCheckerResponse response;
     try {
-      response = quotaChecker.isSegmentStorageWithinQuota(tableConfig, 
segmentName, segmentSizeInBytes);
+      response = quotaChecker
+          .isSegmentStorageWithinQuota(tableConfig, segmentName, 
tarSegmentSizeInBytes, untarredSegmentSizeInBytes);
     } catch (Exception e) {
       throw new ControllerApplicationException(LOGGER,
           String.format("Caught exception while checking the storage quota for 
segment: %s of table: %s", segmentName,
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/validation/StorageQuotaChecker.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/validation/StorageQuotaChecker.java
index 3c0359c9600..0c163af17f1 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/validation/StorageQuotaChecker.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/validation/StorageQuotaChecker.java
@@ -20,6 +20,7 @@ package org.apache.pinot.controller.validation;
 
 import com.google.common.base.Preconditions;
 import org.apache.pinot.common.exception.InvalidConfigException;
+import org.apache.pinot.common.metadata.segment.SegmentZKMetadata;
 import org.apache.pinot.common.metrics.ControllerGauge;
 import org.apache.pinot.common.metrics.ControllerMetrics;
 import org.apache.pinot.controller.ControllerConf;
@@ -81,7 +82,7 @@ public class StorageQuotaChecker {
    * Returns whether the new added segment is within the storage quota.
    */
   public QuotaCheckerResponse isSegmentStorageWithinQuota(TableConfig 
tableConfig, String segmentName,
-      long segmentSizeInBytes)
+      long tarSegmentSizeInBytes, long untarredSegmentSizeInBytes)
       throws InvalidConfigException {
     if (!_isEnabled) {
       return success("Storage quota check is disabled, skipping the check");
@@ -124,7 +125,7 @@ public class StorageQuotaChecker {
     // The logic inside this if block is applicable for missing segments as 
well as
     // when we are checking the quota for only existing segments 
(segmentSizeInBytes == 0)
     // as in both cases quota is checked across existing segments estimated 
size alone
-    if (segmentSizeInBytes == 0 || tableSubtypeSize._missingSegments > 0) {
+    if (untarredSegmentSizeInBytes == 0 || tableSubtypeSize._missingSegments > 
0) {
       emitStorageQuotaUtilizationMetric(tableNameWithType, tableSubtypeSize, 
allowedStorageBytes);
       if (tableSubtypeSize._estimatedSizeInBytes > allowedStorageBytes) {
         return failure("Table " + tableNameWithType + " already over quota. 
Estimated size for all replicas is "
@@ -139,6 +140,10 @@ public class StorageQuotaChecker {
     // If the segment exists(refresh), get the existing size
     TableSizeReader.SegmentSizeDetails sizeDetails = 
tableSubtypeSize._segments.get(segmentName);
     long existingSegmentSizeBytes = sizeDetails != null ? 
sizeDetails._estimatedSizeInBytes : 0;
+    SegmentZKMetadata existingSegmentZkMetadata =
+        _pinotHelixResourceManager.getSegmentZKMetadata(tableNameWithType, 
segmentName);
+    long existingTarSegmentSize = existingSegmentZkMetadata != null
+        ? _pinotHelixResourceManager.getSegmentZKMetadata(tableNameWithType, 
segmentName).getSizeInBytes() : 0;
 
     // Since tableNameWithType comes with the table type(OFFLINE), thus we 
guarantee that
     // tableSubtypeSize.estimatedSizeInBytes is the offline table size.
@@ -150,9 +155,19 @@ public class StorageQuotaChecker {
 
     emitStorageQuotaUtilizationMetric(tableNameWithType, tableSubtypeSize, 
allowedStorageBytes);
 
+    if (existingSegmentZkMetadata != null && tarSegmentSizeInBytes <= 
existingTarSegmentSize) {
+      // If the segment already exists and the tarred size of the incoming 
segment is less than or equal to the
+      // existing segment size, we can skip the quota check.
+      String message = String.format(
+          "Skipping storage quota check for segment %s of table %s since 
incoming tarred segment size %s is less than "
+              + "or equal to existing segment size %s", segmentName, 
tableNameWithType,
+          DataSizeUtils.fromBytes(tarSegmentSizeInBytes), 
DataSizeUtils.fromBytes(existingTarSegmentSize));
+      LOGGER.info(message);
+      return success(message);
+    }
     // Note: incomingSegmentSizeBytes is uncompressed data size for just 1 
replica,
     // while estimatedFinalSizeBytes is for all replicas of all segments put 
together.
-    long totalIncomingSegmentSizeBytes = segmentSizeInBytes * numReplicas;
+    long totalIncomingSegmentSizeBytes = untarredSegmentSizeInBytes * 
numReplicas;
     long estimatedFinalSizeBytes =
         tableSubtypeSize._estimatedSizeInBytes - existingSegmentSizeBytes + 
totalIncomingSegmentSizeBytes;
     if (estimatedFinalSizeBytes <= allowedStorageBytes) {
@@ -168,7 +183,7 @@ public class StorageQuotaChecker {
             DataSizeUtils.fromBytes(allowedStorageBytes), 
quotaConfig.getStorage(), numReplicas,
             DataSizeUtils.fromBytes(estimatedFinalSizeBytes),
             DataSizeUtils.fromBytes(tableSubtypeSize._estimatedSizeInBytes),
-            DataSizeUtils.fromBytes(totalIncomingSegmentSizeBytes), 
DataSizeUtils.fromBytes(segmentSizeInBytes),
+            DataSizeUtils.fromBytes(totalIncomingSegmentSizeBytes), 
DataSizeUtils.fromBytes(untarredSegmentSizeInBytes),
             numReplicas);
       } else {
         // refresh use case
@@ -181,7 +196,7 @@ public class StorageQuotaChecker {
                 + "segment size", segmentName, tableNameWithType, 
DataSizeUtils.fromBytes(allowedStorageBytes),
             quotaConfig.getStorage(), numReplicas, 
DataSizeUtils.fromBytes(estimatedFinalSizeBytes),
             DataSizeUtils.fromBytes(tableSubtypeSize._estimatedSizeInBytes),
-            DataSizeUtils.fromBytes(totalIncomingSegmentSizeBytes), 
DataSizeUtils.fromBytes(segmentSizeInBytes),
+            DataSizeUtils.fromBytes(totalIncomingSegmentSizeBytes), 
DataSizeUtils.fromBytes(untarredSegmentSizeInBytes),
             numReplicas, DataSizeUtils.fromBytes(existingSegmentSizeBytes));
       }
       LOGGER.info(message);
@@ -203,8 +218,8 @@ public class StorageQuotaChecker {
                 + "allowed storage size = configured quota: %s * number 
replicas: %d", tableNameWithType,
             DataSizeUtils.fromBytes(estimatedFinalSizeBytes), 
DataSizeUtils.fromBytes(allowedStorageBytes),
             DataSizeUtils.fromBytes(tableSubtypeSize._estimatedSizeInBytes),
-            DataSizeUtils.fromBytes(existingSegmentSizeBytes), 
DataSizeUtils.fromBytes(segmentSizeInBytes), numReplicas,
-            quotaConfig.getStorage(), numReplicas);
+            DataSizeUtils.fromBytes(existingSegmentSizeBytes), 
DataSizeUtils.fromBytes(untarredSegmentSizeInBytes),
+            numReplicas, quotaConfig.getStorage(), numReplicas);
       }
       LOGGER.warn(message);
       return failure(message);
@@ -229,7 +244,7 @@ public class StorageQuotaChecker {
    */
   public boolean isTableStorageQuotaExceeded(TableConfig tableConfig) {
     try {
-      return !isSegmentStorageWithinQuota(tableConfig, null, 
0)._isSegmentWithinQuota;
+      return !isSegmentStorageWithinQuota(tableConfig, null, 0, 
0)._isSegmentWithinQuota;
     } catch (InvalidConfigException e) {
       // skip the check upon exception
       return false;
diff --git 
a/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java
 
b/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java
index 9e6b896143d..2163409f4a2 100644
--- 
a/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java
+++ 
b/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java
@@ -20,6 +20,7 @@ package org.apache.pinot.controller.validation;
 
 import java.util.Collections;
 import org.apache.pinot.common.exception.InvalidConfigException;
+import org.apache.pinot.common.metadata.segment.SegmentZKMetadata;
 import org.apache.pinot.common.metrics.ControllerGauge;
 import org.apache.pinot.common.metrics.ControllerMetrics;
 import org.apache.pinot.common.metrics.MetricValueUtils;
@@ -32,6 +33,7 @@ import org.apache.pinot.spi.config.table.TableConfig;
 import org.apache.pinot.spi.config.table.TableType;
 import org.apache.pinot.spi.metrics.PinotMetricUtils;
 import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
+import org.apache.pinot.spi.utils.builder.TableNameBuilder;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -178,12 +180,25 @@ public class StorageQuotaCheckerTest {
     assertEquals(
         MetricValueUtils.getTableGaugeValue(controllerMetrics, 
OFFLINE_TABLE_NAME,
             ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE), 4 * 1024);
+
+    // Exceed quota but refreshing segment with equal or smaller size, should 
pass and update metrics
+    mockTableSizeResult(OFFLINE_TABLE_NAME, 4 * 1024, 0);
+    SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(SEGMENT_NAME);
+    segmentZKMetadata.setSizeInBytes(SEGMENT_SIZE_IN_BYTES);
+    when(pinotHelixResourceManager.getSegmentZKMetadata(
+        
TableNameBuilder.forType(tableConfig.getTableType()).tableNameWithType(tableConfig.getTableName()),
+        SEGMENT_NAME)).thenReturn(segmentZKMetadata);
+    assertTrue(isSegmentWithinQuota(tableConfig));
+    assertEquals(
+        MetricValueUtils.getTableGaugeValue(controllerMetrics, 
OFFLINE_TABLE_NAME,
+            ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE), 4 * 1024);
   }
 
   private boolean isSegmentWithinQuota(TableConfig tableConfig)
       throws InvalidConfigException {
     return _storageQuotaChecker
-        .isSegmentStorageWithinQuota(tableConfig, SEGMENT_NAME, 
SEGMENT_SIZE_IN_BYTES)._isSegmentWithinQuota;
+        .isSegmentStorageWithinQuota(tableConfig, SEGMENT_NAME, 
SEGMENT_SIZE_IN_BYTES, SEGMENT_SIZE_IN_BYTES)
+        ._isSegmentWithinQuota;
   }
 
   public void mockTableSizeResult(String tableName, long tableSizeInBytes, int 
numMissingSegments)


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

Reply via email to