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

danielcweeks pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/main by this push:
     new cfc5504ad8 parquet: don't parse a full Hadoop config file for each 
Parquet write if not needed (#18398)
cfc5504ad8 is described below

commit cfc5504ad8b806c253b2a5773c0078a7b1a7b79b
Author: Nicholas Barton <[email protected]>
AuthorDate: Thu Oct 8 17:46:21 2026 -0400

    parquet: don't parse a full Hadoop config file for each Parquet write if 
not needed (#18398)
    
    * core: dont parse a full Hadoop config file for each write if not needed
---
 .../java/org/apache/iceberg/parquet/Parquet.java   |  9 ++-
 .../iceberg/parquet/ParquetCodecFactory.java       | 35 +++++++---
 .../java/org/apache/iceberg/parquet/ParquetIO.java |  8 ++-
 .../org/apache/iceberg/parquet/ParquetWriter.java  |  6 +-
 .../iceberg/parquet/TestParquetCodecFactory.java   | 74 ++++++++++++++++++++++
 .../apache/iceberg/parquet/TestVariantMetrics.java |  4 +-
 6 files changed, 115 insertions(+), 21 deletions(-)

diff --git a/parquet/src/main/java/org/apache/iceberg/parquet/Parquet.java 
b/parquet/src/main/java/org/apache/iceberg/parquet/Parquet.java
index d5a036e1f1..71d9e0ddb7 100644
--- a/parquet/src/main/java/org/apache/iceberg/parquet/Parquet.java
+++ b/parquet/src/main/java/org/apache/iceberg/parquet/Parquet.java
@@ -117,6 +117,8 @@ import org.apache.parquet.avro.AvroReadSupport;
 import org.apache.parquet.avro.AvroWriteSupport;
 import org.apache.parquet.column.ParquetProperties;
 import org.apache.parquet.column.ParquetProperties.WriterVersion;
+import org.apache.parquet.conf.HadoopParquetConfiguration;
+import org.apache.parquet.conf.ParquetConfiguration;
 import org.apache.parquet.conf.PlainParquetConfiguration;
 import org.apache.parquet.crypto.FileDecryptionProperties;
 import org.apache.parquet.crypto.FileEncryptionProperties;
@@ -169,7 +171,7 @@ public class Parquet {
 
   public static class WriteBuilder implements InternalData.WriteBuilder {
     private final OutputFile file;
-    private final Configuration conf;
+    private final ParquetConfiguration conf;
     private final Map<String, String> metadata = Maps.newLinkedHashMap();
     private final Map<String, String> config = Maps.newLinkedHashMap();
     private Schema schema = null;
@@ -186,9 +188,10 @@ public class Parquet {
     private WriteBuilder(OutputFile file) {
       this.file = file;
       if (file instanceof HadoopOutputFile) {
-        this.conf = new Configuration(((HadoopOutputFile) file).getConf());
+        this.conf =
+            new HadoopParquetConfiguration(new 
Configuration(((HadoopOutputFile) file).getConf()));
       } else {
-        this.conf = new Configuration();
+        this.conf = new PlainParquetConfiguration();
       }
     }
 
diff --git 
a/parquet/src/main/java/org/apache/iceberg/parquet/ParquetCodecFactory.java 
b/parquet/src/main/java/org/apache/iceberg/parquet/ParquetCodecFactory.java
index bfe87f8e50..f0089b3897 100644
--- a/parquet/src/main/java/org/apache/iceberg/parquet/ParquetCodecFactory.java
+++ b/parquet/src/main/java/org/apache/iceberg/parquet/ParquetCodecFactory.java
@@ -21,18 +21,29 @@ package org.apache.iceberg.parquet;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.io.compress.CompressionCodec;
 import org.apache.hadoop.util.ReflectionUtils;
+import org.apache.parquet.conf.HadoopParquetConfiguration;
+import org.apache.parquet.conf.ParquetConfiguration;
 import org.apache.parquet.hadoop.BadConfigurationException;
 import org.apache.parquet.hadoop.CodecFactory;
 import org.apache.parquet.hadoop.metadata.CompressionCodecName;
+import org.apache.parquet.hadoop.util.ConfigurationUtil;
 
 /**
- * This class implements a codec factory that is used when reading from 
Parquet. It adds a
- * workaround to cache codecs by name and level, not just by name. This can be 
removed when this
- * change is made to Parquet.
+ * A codec factory that caches codecs by compression level as well as by name, 
so that requests for
+ * the same codec at different levels do not share an instance.
  */
 public class ParquetCodecFactory extends CodecFactory {
 
+  /**
+   * @deprecated since 1.13.0; this class is internal to {@link Parquet} and 
should not be
+   *     constructed directly.
+   */
+  @Deprecated
   public ParquetCodecFactory(Configuration configuration, int pageSize) {
+    this(new HadoopParquetConfiguration(configuration), pageSize);
+  }
+
+  ParquetCodecFactory(ParquetConfiguration configuration, int pageSize) {
     super(configuration, pageSize);
   }
 
@@ -57,10 +68,14 @@ public class ParquetCodecFactory extends CodecFactory {
       try {
         codecClass = Class.forName(codecClassName);
       } catch (ClassNotFoundException e) {
-        // Try to load the class using the job classloader
-        codecClass = configuration.getClassLoader().loadClass(codecClassName);
+        // Try to load the class using the configuration's classloader
+        codecClass = conf.getClassByName(codecClassName);
       }
-      codec = (CompressionCodec) ReflectionUtils.newInstance(codecClass, 
configuration);
+      // Hadoop codecs are Configurable, so instantiation needs a Hadoop 
Configuration
+      codec =
+          (CompressionCodec)
+              ReflectionUtils.newInstance(
+                  codecClass, 
ConfigurationUtil.createHadoopConfiguration(conf));
       CODEC_BY_NAME.put(cacheKey, codec);
       return codec;
     } catch (ClassNotFoundException e) {
@@ -73,16 +88,16 @@ public class ParquetCodecFactory extends CodecFactory {
     String level = null;
     switch (codecName) {
       case GZIP:
-        level = configuration.get("zlib.compress.level");
+        level = conf.get("zlib.compress.level");
         break;
       case BROTLI:
-        level = configuration.get("compression.brotli.quality");
+        level = conf.get("compression.brotli.quality");
         break;
       case ZSTD:
-        level = configuration.get("parquet.compression.codec.zstd.level");
+        level = conf.get("parquet.compression.codec.zstd.level");
         if (level == null) {
           // keep "io.compression.codec.zstd.level" for backwards compatibility
-          level = configuration.get("io.compression.codec.zstd.level");
+          level = conf.get("io.compression.codec.zstd.level");
         }
         break;
       default:
diff --git a/parquet/src/main/java/org/apache/iceberg/parquet/ParquetIO.java 
b/parquet/src/main/java/org/apache/iceberg/parquet/ParquetIO.java
index ecca4eb0d6..e22c813125 100644
--- a/parquet/src/main/java/org/apache/iceberg/parquet/ParquetIO.java
+++ b/parquet/src/main/java/org/apache/iceberg/parquet/ParquetIO.java
@@ -25,7 +25,6 @@ import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
-import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FSDataInputStream;
 import org.apache.hadoop.fs.FSDataOutputStream;
 import org.apache.iceberg.exceptions.RuntimeIOException;
@@ -37,6 +36,8 @@ import org.apache.iceberg.io.FileRange;
 import org.apache.iceberg.io.RangeReadable;
 import 
org.apache.iceberg.relocated.com.google.common.annotations.VisibleForTesting;
 import org.apache.parquet.bytes.ByteBufferAllocator;
+import org.apache.parquet.conf.ParquetConfiguration;
+import org.apache.parquet.hadoop.util.ConfigurationUtil;
 import org.apache.parquet.hadoop.util.HadoopStreams;
 import org.apache.parquet.io.DelegatingPositionOutputStream;
 import org.apache.parquet.io.DelegatingSeekableInputStream;
@@ -80,11 +81,12 @@ class ParquetIO {
     return new ParquetOutputFile(file);
   }
 
-  static OutputFile file(org.apache.iceberg.io.OutputFile file, Configuration 
conf) {
+  static OutputFile file(org.apache.iceberg.io.OutputFile file, 
ParquetConfiguration conf) {
     if (file instanceof HadoopOutputFile) {
       HadoopOutputFile hfile = (HadoopOutputFile) file;
       try {
-        return 
org.apache.parquet.hadoop.util.HadoopOutputFile.fromPath(hfile.getPath(), conf);
+        return org.apache.parquet.hadoop.util.HadoopOutputFile.fromPath(
+            hfile.getPath(), 
ConfigurationUtil.createHadoopConfiguration(conf));
       } catch (IOException e) {
         throw new RuntimeIOException(
             e, "Failed to create Parquet output file for %s", file.location());
diff --git 
a/parquet/src/main/java/org/apache/iceberg/parquet/ParquetWriter.java 
b/parquet/src/main/java/org/apache/iceberg/parquet/ParquetWriter.java
index ee5e5c264a..72ed83da49 100644
--- a/parquet/src/main/java/org/apache/iceberg/parquet/ParquetWriter.java
+++ b/parquet/src/main/java/org/apache/iceberg/parquet/ParquetWriter.java
@@ -24,7 +24,6 @@ import java.io.UncheckedIOException;
 import java.util.List;
 import java.util.Map;
 import java.util.function.BiFunction;
-import org.apache.hadoop.conf.Configuration;
 import org.apache.iceberg.Metrics;
 import org.apache.iceberg.MetricsConfig;
 import org.apache.iceberg.Schema;
@@ -35,6 +34,7 @@ import 
org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
 import org.apache.parquet.column.ColumnWriteStore;
 import org.apache.parquet.column.ParquetProperties;
 import org.apache.parquet.compression.CompressionCodecFactory;
+import org.apache.parquet.conf.ParquetConfiguration;
 import org.apache.parquet.crypto.FileEncryptionProperties;
 import org.apache.parquet.crypto.InternalFileEncryptor;
 import org.apache.parquet.hadoop.ColumnChunkPageWriteStore;
@@ -57,7 +57,7 @@ class ParquetWriter<T> implements FileAppender<T>, Closeable {
   private final int columnIndexTruncateLength;
   private final ParquetFileWriter.Mode writeMode;
   private final OutputFile output;
-  private final Configuration conf;
+  private final ParquetConfiguration conf;
   private final InternalFileEncryptor fileEncryptor;
   private final boolean trackUncompressedSize;
 
@@ -75,7 +75,7 @@ class ParquetWriter<T> implements FileAppender<T>, Closeable {
 
   @SuppressWarnings("unchecked")
   ParquetWriter(
-      Configuration conf,
+      ParquetConfiguration conf,
       OutputFile output,
       Schema schema,
       MessageType parquetSchema,
diff --git 
a/parquet/src/test/java/org/apache/iceberg/parquet/TestParquetCodecFactory.java 
b/parquet/src/test/java/org/apache/iceberg/parquet/TestParquetCodecFactory.java
new file mode 100644
index 0000000000..cdb95a742e
--- /dev/null
+++ 
b/parquet/src/test/java/org/apache/iceberg/parquet/TestParquetCodecFactory.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iceberg.parquet;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.io.compress.CompressionCodec;
+import org.apache.parquet.conf.ParquetConfiguration;
+import org.apache.parquet.conf.PlainParquetConfiguration;
+import org.apache.parquet.hadoop.metadata.CompressionCodecName;
+import org.junit.jupiter.api.Test;
+
+final class TestParquetCodecFactory {
+
+  private static final int PAGE_SIZE = 1024 * 1024;
+
+  @Test
+  void testCachesCodecsByLevelWithoutHadoopConfiguration() {
+    PlainParquetConfiguration level3 = new PlainParquetConfiguration();
+    level3.set("parquet.compression.codec.zstd.level", "3");
+    PlainParquetConfiguration level5 = new PlainParquetConfiguration();
+    level5.set("parquet.compression.codec.zstd.level", "5");
+
+    CompressionCodec codecAtLevel3 = codec(level3, CompressionCodecName.ZSTD);
+    CompressionCodec codecAtLevel5 = codec(level5, CompressionCodecName.ZSTD);
+
+    assertThat(codecAtLevel3).isNotNull();
+    assertThat(codecAtLevel5).isNotNull().isNotSameAs(codecAtLevel3);
+    assertThat(codec(level3, 
CompressionCodecName.ZSTD)).isSameAs(codecAtLevel3);
+  }
+
+  @Test
+  void testLegacyZstdLevelPropertyIsPartOfTheCacheKey() {
+    PlainParquetConfiguration legacy = new PlainParquetConfiguration();
+    legacy.set("io.compression.codec.zstd.level", "7");
+    PlainParquetConfiguration current = new PlainParquetConfiguration();
+    current.set("parquet.compression.codec.zstd.level", "7");
+
+    assertThat(codec(legacy, CompressionCodecName.ZSTD))
+        .isSameAs(codec(current, CompressionCodecName.ZSTD));
+  }
+
+  @Test
+  @SuppressWarnings("deprecation")
+  void testHadoopConfigurationStillAccepted() {
+    Configuration conf = new Configuration(false);
+    conf.set("zlib.compress.level", "BEST_COMPRESSION");
+    ParquetCodecFactory factory = new ParquetCodecFactory(conf, PAGE_SIZE);
+
+    assertThat(factory.getCompressor(CompressionCodecName.GZIP)).isNotNull();
+    assertThat(factory.getCodec(CompressionCodecName.UNCOMPRESSED)).isNull();
+  }
+
+  private static CompressionCodec codec(ParquetConfiguration conf, 
CompressionCodecName name) {
+    return new ParquetCodecFactory(conf, PAGE_SIZE).getCodec(name);
+  }
+}
diff --git 
a/parquet/src/test/java/org/apache/iceberg/parquet/TestVariantMetrics.java 
b/parquet/src/test/java/org/apache/iceberg/parquet/TestVariantMetrics.java
index e8fb8f1452..202e6362eb 100644
--- a/parquet/src/test/java/org/apache/iceberg/parquet/TestVariantMetrics.java
+++ b/parquet/src/test/java/org/apache/iceberg/parquet/TestVariantMetrics.java
@@ -30,7 +30,6 @@ import java.util.Set;
 import java.util.UUID;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
-import org.apache.hadoop.conf.Configuration;
 import org.apache.iceberg.Metrics;
 import org.apache.iceberg.MetricsConfig;
 import org.apache.iceberg.MetricsTestUtil;
@@ -60,6 +59,7 @@ import org.apache.iceberg.variants.VariantValue;
 import org.apache.iceberg.variants.Variants;
 import org.apache.parquet.column.ParquetProperties;
 import org.apache.parquet.column.statistics.Statistics;
+import org.apache.parquet.conf.PlainParquetConfiguration;
 import org.apache.parquet.hadoop.ParquetFileReader;
 import org.apache.parquet.hadoop.ParquetFileWriter;
 import org.apache.parquet.hadoop.metadata.BlockMetaData;
@@ -878,7 +878,7 @@ public class TestVariantMetrics {
     // Parquet.write() cannot disable stats on variant sub-columns (no field 
IDs)
     ParquetWriter<Record> writer =
         new ParquetWriter<>(
-            new Configuration(),
+            new PlainParquetConfiguration(),
             out,
             SCHEMA,
             parquetSchema,

Reply via email to