codope commented on code in PR #19128:
URL: https://github.com/apache/hudi/pull/19128#discussion_r3568116213


##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/config/HoodieWriteConfig.java:
##########
@@ -1827,8 +1827,12 @@ public int getTimelineManifestRetainedVersions() {
     return getInt(HoodieArchivalConfig.TIMELINE_MANIFEST_RETAINED_VERSIONS);
   }
 
-  public int getParquetSmallFileLimit() {
-    return getInt(HoodieCompactionConfig.PARQUET_SMALL_FILE_LIMIT);
+  public long getParquetSmallFileLimit() {
+    // Effective threshold is fraction * max.file.size. The fraction config is 
the single source of
+    // truth at runtime; it is either user-set or derived by its infer 
function from the byte-based
+    // `hoodie.parquet.small.file.limit` at build time. See 
HoodieCompactionConfig.
+    double fraction = 
getDouble(HoodieCompactionConfig.PARQUET_SMALL_FILE_FRACTION_MAX_FILE_SIZE);
+    return (long) (fraction * getParquetMaxFileSize());

Review Comment:
   Like I mentioned in 
https://github.com/apache/hudi/pull/19128/changes#r3568112184  computing 
fraction * max here can yield a wrong threshold in the standalone compaction 
config path. We can instead do the derivation lazily with explicit precedence 
so it's correct regardless of build order.



##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/config/HoodieCompactionConfig.java:
##########
@@ -392,6 +421,11 @@ public Builder compactionSmallFileSize(long 
smallFileLimitBytes) {
       return this;
     }
 
+    public Builder withParquetSmallFileFractionOfMaxFileSize(double fraction) {
+      compactionConfig.setValue(PARQUET_SMALL_FILE_FRACTION_MAX_FILE_SIZE, 
String.valueOf(fraction));

Review Comment:
   +1 we can probably even drop the parquet from the method name and simply 
call it `compactionSmallFileFraction`



##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/config/HoodieWriteConfig.java:
##########
@@ -1827,8 +1827,12 @@ public int getTimelineManifestRetainedVersions() {
     return getInt(HoodieArchivalConfig.TIMELINE_MANIFEST_RETAINED_VERSIONS);
   }
 
-  public int getParquetSmallFileLimit() {
-    return getInt(HoodieCompactionConfig.PARQUET_SMALL_FILE_LIMIT);
+  public long getParquetSmallFileLimit() {
+    // Effective threshold is fraction * max.file.size. The fraction config is 
the single source of
+    // truth at runtime; it is either user-set or derived by its infer 
function from the byte-based
+    // `hoodie.parquet.small.file.limit` at build time. See 
HoodieCompactionConfig.
+    double fraction = 
getDouble(HoodieCompactionConfig.PARQUET_SMALL_FILE_FRACTION_MAX_FILE_SIZE);

Review Comment:
   use `getDoubleOrDefault`



##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/config/HoodieCompactionConfig.java:
##########
@@ -105,7 +107,34 @@ public class HoodieCompactionConfig extends HoodieConfig {
       .withDocumentation("During upsert operation, we opportunistically expand 
existing small files on storage, instead of writing"
           + " new files, to keep number of files to an optimum. This config 
sets the file size limit below which a file on storage "
           + " becomes a candidate to be selected as such a `small file`. By 
default, treat any file <= 100MB as a small file."
-          + " Also note that if this set <= 0, will not try to get small files 
and directly write new files");
+          + " Also note that if this set <= 0, will not try to get small files 
and directly write new files."
+          + " As of the fraction-based small-file config, this byte value is 
no longer read directly at runtime — it feeds"
+          + " the infer function of 
`hoodie.parquet.small.file.fraction.max.file.size` so tables that only set this 
key keep"
+          + " working. If both this and the fraction config are set 
explicitly, the fraction config takes precedence.");
+
+  public static final ConfigProperty<Double> 
PARQUET_SMALL_FILE_FRACTION_MAX_FILE_SIZE = ConfigProperty
+      .key("hoodie.parquet.small.file.fraction.max.file.size")
+      .defaultValue(0.0)
+      .markAdvanced()
+      .sinceVersion("1.3.0")
+      .withInferFunction(cfg -> {
+        // Fall back to the built-in defaults of the byte-based configs when 
the keys are not yet
+        // present in props. This happens when 
HoodieCompactionConfig.Builder.build() runs
+        // standalone (before HoodieStorageConfig defaults are merged in the 
outer
+        // HoodieWriteConfig.Builder.setDefaults path). Using *OrDefault 
ensures the fraction is
+        // still correctly inferred as 100MB/120MB in that path.
+        long maxFileBytes = 
cfg.getLongOrDefault(HoodieStorageConfig.PARQUET_MAX_FILE_SIZE);

Review Comment:
   So, this infer reads `HoodieStorageConfig.PARQUET_MAX_FILE_SIZE`, but the 
property lives in `HoodieCompactionConfig`. IIUC, infer functions run at each 
config class's own build() and the result is frozen as an explicit property. So 
when a caller builds a standalone HoodieCompactionConfig (the most common 
pattern), storage config isn't in scope and 
`getLongOrDefault(PARQUET_MAX_FILE_SIZE)` returns the built-in 120 MB, not the 
user's actual max. And then the fraction is baked against 120 MB.
   
   In HoodieWriteConfig.Builder.setDefaults() it's never re-derived: 
`setDefaultOnCondition(!isCompactionConfigSet, …)` is skipped when the user 
supplied the compaction config, and `setDefaults(HoodieWriteConfig.class)` only 
iterates fields declared in that class. At runtime `getParquetSmallFileLimit() 
= bakedFraction * actualMax`, i.e. wrong-fraction * correct-max.
   
   This `.withCompactionConfig(HoodieCompactionConfig.newBuilder()…build())` + 
non-default parquetMaxFileSize pattern spans across test files and prod 
FlinkWriteClients.java which is very likely why the CI is failing.
   
   We can drop the infer and do the derivation where the max is actually known, 
lazily in `getParquetSmallFileLimit(`), or at the `HoodieWriteConfig` level 
after storage is merged. Genrally, an infer that depends on a property from a 
different config class does not sounds like a good idea.



##########
hudi-client/hudi-client-common/src/test/java/org/apache/hudi/config/TestHoodieWriteConfig.java:
##########
@@ -234,6 +234,77 @@ public void testInferCleaningPolicy() {
     assertEquals(HoodieCleaningPolicy.KEEP_LATEST_BY_HOURS, 
writeConfig.getCleanerPolicy());
   }
 
+  @Test
+  public void testParquetSmallFileFractionDerivation() {

Review Comment:
   we should add a test covering common usage (standalone compaction config + 
non-default max). Probably, it's already there that's why test failures in CI.



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