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

voonhous pushed a commit to branch release-1.2.1
in repository https://gitbox.apache.org/repos/asf/hudi.git

commit c14a871d57e80493cc9e336a044a25c6000d977b
Author: voonhous <[email protected]>
AuthorDate: Thu Sep 17 18:45:36 2026 +0800

    perf(common): cache nested Avro record class lookup (#19972)
    
    #19956 replaced SpecificData#getClass with an uncached Class.forName
    per nested record, dropping the class cache Avro keeps by schema full
    name. Delete block reads hit it twice per delete record (the record
    and its ordering-value wrapper), costing ~450-880 ns per lookup vs
    ~3-5 ns before.
    
    Cache the resolved class by schema full name, as SpecificData does.
    
    (cherry picked from commit 5bccaabf56b60a0efd9665b5b6c9d115993f1e8e)
---
 .../java/org/apache/hudi/avro/HoodieAvroUtils.java | 24 ++++++++++++++++------
 1 file changed, 18 insertions(+), 6 deletions(-)

diff --git 
a/hudi-common/src/main/java/org/apache/hudi/avro/HoodieAvroUtils.java 
b/hudi-common/src/main/java/org/apache/hudi/avro/HoodieAvroUtils.java
index d97fe8fdcb60..28d08c25ae86 100644
--- a/hudi-common/src/main/java/org/apache/hudi/avro/HoodieAvroUtils.java
+++ b/hudi-common/src/main/java/org/apache/hudi/avro/HoodieAvroUtils.java
@@ -98,6 +98,7 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
 import static org.apache.avro.Schema.Type.ARRAY;
@@ -124,6 +125,13 @@ public class HoodieAvroUtils {
 
   private static final Properties PROPERTIES = new Properties();
 
+  /**
+   * Generated SpecificRecord classes keyed by schema full name, mirroring the 
class cache inside
+   * {@link SpecificData#getClass(Schema)} that the direct {@code 
Class.forName} lookup in
+   * {@link #getSpecificRecordClass(Schema, SpecificData)} bypasses. Bounded 
because only compiled SCHEMA$ schemas reach it.
+   */
+  private static final Map<String, Class<? extends SpecificRecordBase>> 
SPECIFIC_RECORD_CLASS_CACHE = new ConcurrentHashMap<>();
+
   /**
    * Resolves the Avro library version, preferring Maven's generated 
pom.properties over
    * {@link Package#getImplementationVersion()}. The latter comes from 
whatever manifest happens to
@@ -1563,14 +1571,18 @@ public class HoodieAvroUtils {
    * {@link SpecificData#getClass(Schema)}), which rejects Hudi's generated 
classes.
    *
    * <p>Only pass schemas taken from a compiled SCHEMA$, never a schema read 
from storage, which is what the validation guards against.
+   *
+   * <p>The result is cached by schema full name so the per-record path does 
not pay {@code Class.forName}.
    */
   private static Class<? extends SpecificRecordBase> 
getSpecificRecordClass(Schema recordSchema, SpecificData specificData) {
-    String className = SpecificData.getClassName(recordSchema);
-    try {
-      return Class.forName(className, false, 
specificData.getClassLoader()).asSubclass(SpecificRecordBase.class);
-    } catch (ClassNotFoundException e) {
-      throw new HoodieException("Failed to load SpecificRecord class " + 
className + " for Avro schema " + recordSchema.getFullName(), e);
-    }
+    return 
SPECIFIC_RECORD_CLASS_CACHE.computeIfAbsent(recordSchema.getFullName(), 
fullName -> {
+      String className = SpecificData.getClassName(recordSchema);
+      try {
+        return Class.forName(className, false, 
specificData.getClassLoader()).asSubclass(SpecificRecordBase.class);
+      } catch (ClassNotFoundException e) {
+        throw new HoodieException("Failed to load SpecificRecord class " + 
className + " for Avro schema " + fullName, e);
+      }
+    });
   }
 
   private static Object convertFieldToSpecificRecordValue(Schema fieldSchema, 
Object value, SpecificData specificData) {

Reply via email to