This is an automated email from the ASF dual-hosted git repository.
voonhous pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git
The following commit(s) were added to refs/heads/master by this push:
new 5bccaabf56b6 perf(common): cache nested Avro record class lookup
(#19972)
5bccaabf56b6 is described below
commit 5bccaabf56b60a0efd9665b5b6c9d115993f1e8e
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.
---
.../apache/hudi/common/avro/HoodieAvroUtils.java | 24 ++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)
diff --git
a/hudi-common/src/main/java/org/apache/hudi/common/avro/HoodieAvroUtils.java
b/hudi-common/src/main/java/org/apache/hudi/common/avro/HoodieAvroUtils.java
index e45d3f033542..21485fe7876c 100644
--- a/hudi-common/src/main/java/org/apache/hudi/common/avro/HoodieAvroUtils.java
+++ b/hudi-common/src/main/java/org/apache/hudi/common/avro/HoodieAvroUtils.java
@@ -105,6 +105,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;
@@ -168,6 +169,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
@@ -1767,14 +1775,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) {