FrankChen021 commented on code in PR #20379:
URL: https://github.com/apache/druid/pull/20379#discussion_r4283266647


##########
server/src/main/java/org/apache/druid/server/compaction/CompactionStatus.java:
##########
@@ -649,9 +649,16 @@ private CompactionStatus 
baseTableIsUpToDate(CompactionState lastCompactionState
     {
       // The baseTable spec compares its own state; segment/query granularity 
and rollup are covered by their own
       // checks, so the spec excludes them. A null configured baseTable means 
"don't care".
+      //
+      // When the config is not sealed, the compaction task appends the 
columns it finds in the segments to the spec,
+      // so the recorded state is a superset of the configured one and a 
strict comparison would recompact forever.
+      // The relaxed comparison accepts that superset. The cost is that the 
config-derived fingerprint never matches
+      // either, so these datasources always fall through to this 
field-by-field pass instead of the fingerprint fast
+      // path; recording the declared spec instead is not an option, since it 
would misdescribe the segment.
       final BaseTableProjectionSpec configured = 
compactionConfig.getBaseTable();
       final BaseTableProjectionSpec current = 
lastCompactionState.getBaseTable();
-      if (configured == null || (current != null && 
configured.hasEqualCompactionState(current))) {
+      if (configured == null
+          || (current != null && configured.hasEqualCompactionState(current, 
!compactionConfig.isSealed()))) {

Review Comment:
   [P2] Schedule sealed cleanup for undeclared projections
   
   **Finding:** The sealed-aware base-table comparison leaves the projection 
check's null-as-don't-care behavior unchanged. For a catalog base table with 
sealed=true and no projections property, existing segment projections can 
therefore be treated as up to date, so the compaction needed to remove 
undeclared projections is never scheduled.
   
   **Suggestion:** For sealed catalog configs with an absent projections 
property, compare the recorded projections against an explicit empty list while 
preserving null-as-don't-care for unsealed and legacy configs, and add a status 
regression test.



##########
indexing-service/src/main/java/org/apache/druid/indexing/common/task/CompactionTask.java:
##########
@@ -797,16 +831,66 @@ private static DataSchema createDataSchema(
       @Nonnull ClientCompactionTaskGranularitySpec granularitySpec,
       @Nullable List<AggregateProjectionSpec> projections,
       @Nullable BaseTableProjectionSpec baseTable,
+      boolean sealed,
+      boolean rangePartitioned,
       boolean needMultiValuedColumns
   )
   {
     if (baseTable != null) {
       // BaseTable mode: the spec owns dimensions/metrics and (via its 
query-granularity virtual column) query
       // granularity; segment granularity + intervals are the compaction 
config's. Attach the config's query
       // granularity up front (a no-op if absent/NONE); the generator's 
incremental index applies it at build time.
-      // Existing-segment analysis (dims/metrics/rollup inference) is bypassed 
— the spec is authoritative.
-      final BaseTableProjectionSpec baseTableSpec =
-          
baseTable.withQueryGranularity(granularitySpec.getQueryGranularity());
+      // A sealed spec is the complete description of the table, so the 
columns and projections it leaves undeclared
+      // are taken to not exist rather than inferred. Range partitioning is 
the exception that reads the segments even
+      // when sealed: only they can say whether a partition column is 
multi-valued, which MSQ cannot partition by.
+      final boolean analyzeSchema = !sealed;
+      final boolean analyzeProjections = !sealed && projections == null;
+      final boolean analyzeMultiValued = rangePartitioned;
+      BaseTableProjectionSpec baseTableSpec = 
baseTable.withQueryGranularity(granularitySpec.getQueryGranularity());
+      List<AggregateProjectionSpec> projectionSpecs = projections;
+      Set<String> multiValuedDimensions = null;
+      if (analyzeSchema || analyzeMultiValued) {
+        final ExistingSegmentAnalyzer analyzer = new ExistingSegmentAnalyzer(
+            segments,
+            false,
+            false,
+            analyzeSchema,
+            analyzeSchema,
+            analyzeProjections,
+            analyzeMultiValued
+        );
+        fetchAndProcess(analyzer, emitter, metricBuilder);
+        if (analyzeSchema) {
+          // An unsealed spec is partial, so append what it does not declare 
after the declared columns, leaving the
+          // shape the operator asked for unchanged.
+          baseTableSpec = baseTableSpec.withAdditionalColumns(
+              undeclaredColumns(baseTableSpec, analyzer.getDimensionsSpec())
+          );
+          if (baseTableSpec instanceof RollupBaseTableProjectionSpec) {
+            baseTableSpec = ((RollupBaseTableProjectionSpec) 
baseTableSpec).withAdditionalMetrics(
+                undeclaredMetrics(baseTableSpec, analyzer.getMetricsSpec())
+            );
+          } else {
+            // A spec with no metric columns has nowhere to put these, and 
dropping them would delete data the table
+            // never said it did not want. Sealing the table is how an 
operator asks for that.
+            final Set<String> metricNames = analyzer.getMetricNames();
+            if (!metricNames.isEmpty()) {
+              throw InvalidInput.exception(
+                  "Segments of dataSource[%s] store metric column(s) %s, but 
its base table has no metric columns to"
+                  + " keep them in. Declare a rollup base table, or seal the 
table to drop them.",
+                  dataSource,
+                  metricNames
+              );
+            }
+          }
+        }
+        if (analyzeProjections) {
+          projectionSpecs = analyzer.getProjections();

Review Comment:
   [P2] Preserve projections across input segments
   
   **Finding:** When an unsealed base-table compaction leaves projections 
undeclared, this branch consumes ExistingSegmentAnalyzer.getProjections(). That 
analyzer resets its projection map for every segment with non-empty projection 
metadata, so inputs with disjoint projection names retain only the last 
processed segment's definitions and silently drop the others.
   
   **Suggestion:** Accumulate projection definitions across all segments while 
retaining the intended same-name replacement behavior, and add a regression 
test with disjoint projection sets.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to