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

9aman 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 1d0aae09627 Reject time-based flush threshold for pauseless tables 
(#20168)
1d0aae09627 is described below

commit 1d0aae096273782890db7e02b731b45063d2bdb4
Author: 9aman <[email protected]>
AuthorDate: Tue Nov 10 15:27:26 2026 +0530

    Reject time-based flush threshold for pauseless tables (#20168)
---
 .../segment/local/utils/TableConfigUtils.java      | 30 +++++++++++++++-
 .../segment/local/utils/TableConfigUtilsTest.java  | 40 ++++++++++++++++++++++
 2 files changed, 69 insertions(+), 1 deletion(-)

diff --git 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java
 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java
index f3b9fc3a809..242278577e8 100644
--- 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java
+++ 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java
@@ -48,6 +48,7 @@ import 
org.apache.pinot.common.request.context.ExpressionContext;
 import org.apache.pinot.common.request.context.FunctionContext;
 import org.apache.pinot.common.request.context.RequestContextUtils;
 import org.apache.pinot.common.tier.TierFactory;
+import org.apache.pinot.common.utils.PauselessConsumptionUtils;
 import org.apache.pinot.common.utils.config.TagNameUtils;
 import org.apache.pinot.segment.local.aggregator.ValueAggregator;
 import org.apache.pinot.segment.local.aggregator.ValueAggregatorFactory;
@@ -527,13 +528,26 @@ public final class TableConfigUtils {
         || 
CommonConstants.HTTPS_PROTOCOL.equalsIgnoreCase(peerSegmentDownloadScheme);
   }
 
+  /// Returns whether the stored table config is already pauseless and sets 
`key` to `value` in one of its stream
+  /// configs. This value stays valid, so that updates to an existing table 
and to its schema keep working.
+  private static boolean hasSamePauselessTimeFlushThreshold(@Nullable 
TableConfig existingTableConfig, String key,
+      String value) {
+    if (existingTableConfig == null || 
!PauselessConsumptionUtils.isPauselessEnabled(existingTableConfig)) {
+      return false;
+    }
+    // Pauseless consumption is enabled in the stream ingestion config, so the 
ingestion config is not null
+    return 
existingTableConfig.getIngestionConfig().getStreamIngestionConfig().getStreamConfigMaps().stream()
+        .anyMatch(streamConfigMap -> value.equals(streamConfigMap.get(key)));
+  }
+
   /// Validates the table's [IngestionConfig] together with the closely 
related metrics-aggregation config, covering:
   /// - Metrics aggregation (both the `aggregateMetrics` flag and ingestion 
`aggregationConfigs`), delegated to
   ///   [#validateMetricsAggregation].
   /// - Batch ingestion: each batch config map is well-formed, and a dimension 
table has batch ingestion configured
   ///   with `REFRESH` segment ingestion type.
   /// - Stream ingestion: streams are declared in only one place, at least one 
stream is present, and pauseless
-  ///   consumption has a valid `peerSegmentDownloadScheme`.
+  ///   consumption has a valid `peerSegmentDownloadScheme` and no time flush 
threshold, except the unchanged value of
+  ///   a stored pauseless table.
   /// - Filter config: the filter function is valid and not a disabled Groovy 
expression.
   /// - Source field configs: no source field is duplicated within the same 
complex-type phase.
   /// - Enrichment configs: each config is valid.
@@ -608,6 +622,20 @@ public final class TableConfigUtils {
         } else {
           LOGGER.warn("It's not recommended to create pauseless tables with 
replication 1 for stability reasons.");
         }
+        // Each replica checks a time flush threshold on its own clock, and a 
replica that restarts near the end time
+        // extends it. So the replicas of a pauseless segment can stop at 
different offsets. A replica past the
+        // committed end offset can only download the segment, so if the 
committer fails before the upload, no replica
+        // has a copy. Reject an explicit time threshold (the default 
threshold still applies). A stored pauseless
+        // table keeps its unchanged value, so that updates to the table and 
to its schema keep working.
+        for (Map<String, String> streamConfigMap : streamConfigMaps) {
+          for (String key : 
List.of(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME,
+              StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME + 
StreamConfigProperties.LLC_SUFFIX)) {
+            String value = streamConfigMap.get(key);
+            Preconditions.checkState(
+                value == null || 
hasSamePauselessTimeFlushThreshold(existingTableConfig, key, value),
+                "%s is not supported with pauseless consumption enabled, use a 
row or size based flush threshold", key);
+          }
+        }
       }
     }
 
diff --git 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/utils/TableConfigUtilsTest.java
 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/utils/TableConfigUtilsTest.java
index 6deb0aca427..9a952662bf6 100644
--- 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/utils/TableConfigUtilsTest.java
+++ 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/utils/TableConfigUtilsTest.java
@@ -1201,6 +1201,46 @@ public class TableConfigUtilsTest {
     }
   }
 
+  @Test
+  public void pauselessTimeFlushThresholdTest() {
+    Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME)
+        .addDateTime("timeColumn", DataType.TIMESTAMP, "1:MILLISECONDS:EPOCH", 
"1:MILLISECONDS")
+        .build();
+    for (String key : 
List.of(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME,
+        StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME + 
StreamConfigProperties.LLC_SUFFIX)) {
+      TableConfig tableConfig = getTimeFlushThresholdTableConfig(key, "30m", 
true);
+      IllegalStateException e =
+          expectThrows(IllegalStateException.class, () -> 
TableConfigUtils.validate(tableConfig, schema));
+      assertTrue(e.getMessage().contains(key), e.getMessage());
+
+      // The same config is valid when pauseless consumption is disabled
+      TableConfig nonPauselessTableConfig = 
getTimeFlushThresholdTableConfig(key, "30m", false);
+      TableConfigUtils.validate(nonPauselessTableConfig, schema);
+
+      // A stored pauseless table keeps its value, which also lets a schema 
update revalidate it. A changed value, or
+      // enabling pauseless consumption on a table with the key, is rejected.
+      TableConfigUtils.validate(tableConfig, schema, null, tableConfig);
+      TableConfig changedTableConfig = getTimeFlushThresholdTableConfig(key, 
"5m", true);
+      expectThrows(IllegalStateException.class,
+          () -> TableConfigUtils.validate(changedTableConfig, schema, null, 
tableConfig));
+      expectThrows(IllegalStateException.class,
+          () -> TableConfigUtils.validate(tableConfig, schema, null, 
nonPauselessTableConfig));
+    }
+  }
+
+  private TableConfig getTimeFlushThresholdTableConfig(String key, String 
value, boolean pauselessEnabled) {
+    Map<String, String> streamConfigs = getStreamConfigs();
+    streamConfigs.put(key, value);
+    StreamIngestionConfig streamIngestionConfig = new 
StreamIngestionConfig(List.of(streamConfigs));
+    streamIngestionConfig.setPauselessConsumptionEnabled(pauselessEnabled);
+    IngestionConfig ingestionConfig = new IngestionConfig();
+    ingestionConfig.setStreamIngestionConfig(streamIngestionConfig);
+    return new TableConfigBuilder(TableType.REALTIME).setTableName(TABLE_NAME)
+        .setTimeColumnName("timeColumn")
+        .setIngestionConfig(ingestionConfig)
+        .build();
+  }
+
   @Test
   public void ingestionServerIngestionOomProtectionTest() {
     Schema schema = new 
Schema.SchemaBuilder().setSchemaName(TABLE_NAME).build();


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

Reply via email to