shangxinli commented on code in PR #19868:
URL: https://github.com/apache/hudi/pull/19868#discussion_r3991768247


##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/metadata/HoodieBackedTableMetadataWriter.java:
##########
@@ -1543,8 +1543,20 @@ public void performTableServices(Option<String> 
inFlightInstantTimestamp, boolea
           .filterCompletedInstants()
           .lastInstant();
       if (!lastInstant.isPresent()) {
+        metrics.ifPresent(m -> m.updateDeltaCommitsSinceLastCompaction(0L));
         return;
       }
+      // Report how far the metadata table has drifted from its last 
compaction, so that a stalled
+      // compaction is visible before the log to base file ratio degrades read 
performance. This is the
+      // backlog as it stands before the compaction below runs, so on a 
healthy table it peaks at
+      // hoodie.metadata.compact.max.delta.commits on the cycle that compacts 
and drops on the next one.
+      // It counts the same completed instants that 
ScheduleCompactionActionExecutor compares against
+      // that threshold, and is not re-sampled afterwards because doing so 
would need another timeline
+      // reload on the write path.
+      metrics.ifPresent(m -> m.updateDeltaCommitsSinceLastCompaction(

Review Comment:
   Confirmed, and moved it into the `finally` block. One correction to the 
suggestion though: `getActiveTimeline()` there is not reliable either, since 
every caller passes `requiresTimelineRefresh=false` 
(`HoodieTableMetadataWriter.java:178`) and neither `runAnyPendingCompactions` 
nor `writeClient.compact` refreshes this meta client. Sampling off 
`reloadActiveTimeline()` instead, wrapped so a metric failure cannot mask the 
original exception.



##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/metadata/HoodieBackedTableMetadataWriter.java:
##########
@@ -1543,8 +1543,20 @@ public void performTableServices(Option<String> 
inFlightInstantTimestamp, boolea
           .filterCompletedInstants()
           .lastInstant();
       if (!lastInstant.isPresent()) {
+        metrics.ifPresent(m -> m.updateDeltaCommitsSinceLastCompaction(0L));
         return;
       }
+      // Report how far the metadata table has drifted from its last 
compaction, so that a stalled
+      // compaction is visible before the log to base file ratio degrades read 
performance. This is the
+      // backlog as it stands before the compaction below runs, so on a 
healthy table it peaks at
+      // hoodie.metadata.compact.max.delta.commits on the cycle that compacts 
and drops on the next one.
+      // It counts the same completed instants that 
ScheduleCompactionActionExecutor compares against
+      // that threshold, and is not re-sampled afterwards because doing so 
would need another timeline
+      // reload on the write path.
+      metrics.ifPresent(m -> m.updateDeltaCommitsSinceLastCompaction(
+          
CompactionUtils.getCompletedDeltaCommitsSinceLatestCompaction(activeTimeline)
+              .map(deltaCommitsInfo -> (long) 
deltaCommitsInfo.getLeft().countInstants())
+              .orElse(0L)));

Review Comment:
   Right as it stood, but moving the sample into `finally` drops the 
`lastInstant` guard from that path, so an empty delta-commit timeline now 
reaches it. Keeping `.orElse(0L)` as the real zero case.



##########
hudi-common/src/main/java/org/apache/hudi/metadata/HoodieMetadataMetrics.java:
##########
@@ -164,6 +181,48 @@ public void updateSizeMetrics(HoodieTableMetaClient 
metaClient, HoodieBackedTabl
     for (Map.Entry<String, String> e : stats.entrySet()) {
       setMetric(e.getKey(), Long.parseLong(e.getValue()));
     }
+    long totalBaseFiles = sumStat(stats, metadataPartitions, 
STAT_COUNT_BASE_FILES);
+    long totalLogFiles = sumStat(stats, metadataPartitions, 
STAT_COUNT_LOG_FILES);
+    setMetric(STAT_TOTAL_BASE_FILE_COUNT, totalBaseFiles);
+    setMetric(STAT_TOTAL_LOG_FILE_COUNT, totalLogFiles);
+    setMetric(STAT_LOG_TO_BASE_FILE_RATIO_PERCENT, 
logToBaseFileRatioPercent(totalBaseFiles, totalLogFiles));
+  }
+
+  /**
+   * Sums a per-partition stat, keyed as {@code <partition>.<statName>}, over 
the enabled metadata partitions.
+   */
+  @VisibleForTesting
+  static long sumStat(Map<String, String> stats, Set<String> 
metadataPartitions, String statName) {
+    return metadataPartitions.stream()
+        .mapToLong(partition -> Long.parseLong(stats.getOrDefault(partition + 
"." + statName, "0")))
+        .sum();
+  }
+
+  /**
+   * Log files per base file expressed as a percentage, so that it can be 
reported through a long valued gauge.
+   *
+   * <p>Returns 0 when there is no base file to divide by. That case is not 
necessarily healthy: a metadata
+   * table holding log files but no base file has never been compacted. Read 
this gauge together with
+   * {@link #STAT_TOTAL_BASE_FILE_COUNT} and {@link 
#STAT_TOTAL_LOG_FILE_COUNT} rather than on its own.
+   */
+  @VisibleForTesting
+  static long logToBaseFileRatioPercent(long totalBaseFiles, long 
totalLogFiles) {
+    return totalBaseFiles > 0 ? (totalLogFiles * 100L) / totalBaseFiles : 0L;
+  }
+
+  /**
+   * Reports the number of completed metadata table delta commits since the 
last completed compaction.
+   *
+   * <p>This counts the same instants that {@code 
ScheduleCompactionActionExecutor#needCompact} compares
+   * against the metadata table's delta commit threshold, which is configured 
by
+   * {@code hoodie.metadata.compact.max.delta.commits} (default 10) under the 
default {@code NUM_COMMITS}
+   * trigger strategy. Compaction fires once the count reaches that threshold, 
and the gauge is sampled
+   * before table services run, so on a healthy table it peaks at the 
threshold on the cycle where
+   * compaction fires and drops on the next one. Alert on a multiple of the 
threshold; a stalled

Review Comment:
   Correct, reworded: it plateaus near `hoodie.keep.min.commits` once archival 
stops pinning the last compaction. I left the 2x out though, since the plateau 
depends on the archival config as much as on the compaction threshold. With the 
sample now post-service a healthy table returns to about zero each cycle, so 
the guidance is "stays at or above the threshold across consecutive cycles".



##########
hudi-common/src/main/java/org/apache/hudi/metadata/HoodieMetadataMetrics.java:
##########
@@ -84,6 +85,22 @@ public class HoodieMetadataMetrics implements Serializable {
   public static final String LOG_COMPACTION_FAILURES = 
"logcompaction_failures";
   public static final String PENDING_COMPACTIONS_FAILURES = 
"pending_compactions_failures";
 
+  // Metadata table compaction health. The existing per-partition 
baseFileCount/logFileCount gauges
+  // show the current shape of the metadata table, but not whether compaction 
is keeping up with it.
+  // Completed delta commits on the metadata table since the last completed 
compaction, sampled before
+  // table services run. On a healthy table this sawtooths up to 
hoodie.metadata.compact.max.delta.commits
+  // and falls back after each compaction, so alert on a multiple of that 
config rather than on the
+  // config value itself. A value that climbs past the peak and keeps going 
means metadata table
+  // compaction is not being scheduled or is failing.
+  public static final String STAT_DELTA_COMMITS_SINCE_LAST_COMPACTION = 
"deltaCommitsSinceLastCompaction";

Review Comment:
   Good catch, verified at `CloudWatchReporter.java:282`. Now prefixed with the 
MDT table name at the call site, under the same `StringUtils.nonEmpty` guard as 
`TABLE_SERVICE_EXECUTION_*`. Worth noting `Metrics#registerGauge` applies no 
prefix of its own, only `registerGauges` does.



##########
hudi-common/src/main/java/org/apache/hudi/metadata/HoodieMetadataMetrics.java:
##########
@@ -164,6 +181,48 @@ public void updateSizeMetrics(HoodieTableMetaClient 
metaClient, HoodieBackedTabl
     for (Map.Entry<String, String> e : stats.entrySet()) {
       setMetric(e.getKey(), Long.parseLong(e.getValue()));
     }
+    long totalBaseFiles = sumStat(stats, metadataPartitions, 
STAT_COUNT_BASE_FILES);
+    long totalLogFiles = sumStat(stats, metadataPartitions, 
STAT_COUNT_LOG_FILES);
+    setMetric(STAT_TOTAL_BASE_FILE_COUNT, totalBaseFiles);
+    setMetric(STAT_TOTAL_LOG_FILE_COUNT, totalLogFiles);
+    setMetric(STAT_LOG_TO_BASE_FILE_RATIO_PERCENT, 
logToBaseFileRatioPercent(totalBaseFiles, totalLogFiles));

Review Comment:
   Dropped the three size gauges from this PR, so this no longer applies here. 
The gap is real and pre-existing though: `updateSizeMetrics` should be covered 
in `testMetadataMetrics` regardless, and I am happy to file that separately.



##########
hudi-common/src/main/java/org/apache/hudi/metadata/HoodieMetadataMetrics.java:
##########
@@ -164,6 +181,48 @@ public void updateSizeMetrics(HoodieTableMetaClient 
metaClient, HoodieBackedTabl
     for (Map.Entry<String, String> e : stats.entrySet()) {
       setMetric(e.getKey(), Long.parseLong(e.getValue()));
     }
+    long totalBaseFiles = sumStat(stats, metadataPartitions, 
STAT_COUNT_BASE_FILES);
+    long totalLogFiles = sumStat(stats, metadataPartitions, 
STAT_COUNT_LOG_FILES);
+    setMetric(STAT_TOTAL_BASE_FILE_COUNT, totalBaseFiles);
+    setMetric(STAT_TOTAL_LOG_FILE_COUNT, totalLogFiles);
+    setMetric(STAT_LOG_TO_BASE_FILE_RATIO_PERCENT, 
logToBaseFileRatioPercent(totalBaseFiles, totalLogFiles));
+  }
+
+  /**
+   * Sums a per-partition stat, keyed as {@code <partition>.<statName>}, over 
the enabled metadata partitions.
+   */
+  @VisibleForTesting
+  static long sumStat(Map<String, String> stats, Set<String> 
metadataPartitions, String statName) {
+    return metadataPartitions.stream()
+        .mapToLong(partition -> Long.parseLong(stats.getOrDefault(partition + 
"." + statName, "0")))
+        .sum();
+  }
+
+  /**
+   * Log files per base file expressed as a percentage, so that it can be 
reported through a long valued gauge.
+   *
+   * <p>Returns 0 when there is no base file to divide by. That case is not 
necessarily healthy: a metadata
+   * table holding log files but no base file has never been compacted. Read 
this gauge together with
+   * {@link #STAT_TOTAL_BASE_FILE_COUNT} and {@link 
#STAT_TOTAL_LOG_FILE_COUNT} rather than on its own.
+   */
+  @VisibleForTesting
+  static long logToBaseFileRatioPercent(long totalBaseFiles, long 
totalLogFiles) {
+    return totalBaseFiles > 0 ? (totalLogFiles * 100L) / totalBaseFiles : 0L;

Review Comment:
   Your worked case is right, a sum ratio is dominated by whichever partition 
has the most file groups. Rather than switch to a max I dropped the ratio, 
since the per-partition counts are more actionable than either aggregate.



-- 
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]

Reply via email to