Apache Parquet は分析ワークロードを対象とした列指向のストレージ形式ですが、あらゆる種類の構造化データの保存に使用でき、さまざまなユースケースに対応できます。
その最も注目すべき機能の 1 つは、処理プロセスの両方の段階で異なる圧縮技術を使用してデータを効率的に圧縮できることです。これにより、ストレージ コストが削減され、読み取りパフォーマンスが向上します。
この記事では、Java での Parquet のファイル圧縮について説明し、使用例を示し、そのパフォーマンスを分析します。
従来の行ベースのストレージ形式とは異なり、Parquet は列指向のアプローチを使用し、同じ種類のデータの局所性と値の冗長性に基づいて、より具体的で効率的な圧縮技術を使用できます。
Parquet は情報をバイナリ形式で書き込み、それぞれ異なる技術を使用して 2 つの異なるレベルで圧縮を適用します。
圧縮アルゴリズムはファイル レベルで構成されますが、各列のエンコーディングは内部ヒューリスティックを使用して自動的に選択されます (少なくとも parquet-java 実装では)。
さまざまな圧縮テクノロジーのパフォーマンスはデータに大きく依存するため、最速の処理時間と最小のストレージ消費量を保証する万能のソリューションはありません。 独自のテストを実行する必要があります。
設定は簡単で、書き込み時に明示的に設定するだけです。ファイルを読み取るときに、Parquet は使用されている圧縮アルゴリズムを検出し、対応する解凍アルゴリズムを適用します。
プロトコル バッファーと Avro を使用する Carpet と Parquet で圧縮アルゴリズムを構成するには、ビルダーの withCompressionCodec メソッドを呼び出すだけです。
カーペット
<code class="language-java">CarpetWriter<T> writer = new CarpetWriter.Builder<>(outputFile, clazz) .withCompressionCodec(CompressionCodecName.ZSTD) .build();</code>
アブロ
<code class="language-java">ParquetWriter<Organization> writer = AvroParquetWriter.<Organization>builder(outputFile) .withSchema(new Organization().getSchema()) .withCompressionCodec(CompressionCodecName.ZSTD) .build();</code>
プロトコルバッファ
<code class="language-java">ParquetWriter<Organization> writer = ProtoParquetWriter.<Organization>builder(outputFile) .withMessage(Organization.class) .withCompressionCodec(CompressionCodecName.ZSTD) .build();</code>
値は、CompressionCodecName 列挙で使用可能な値のいずれかである必要があります: UNCOMPRESSED、SNAPPY、GZIP、LZO、BROTLI、LZ4、ZSTD、LZ4_RAW (LZ4 は非推奨なので、LZ4_RAW を使用する必要があります)。
一部の圧縮アルゴリズムには、圧縮レベルを微調整する方法が用意されています。このレベルは通常、繰り返しパターンを見つけるのにどれだけの労力を費やす必要があるかに関係しており、圧縮レベルが高くなるほど、圧縮プロセスに必要な時間とメモリが増えます。
デフォルト値が付属していますが、コーデックごとに異なるキーを使用する場合でも、Parquet の汎用構成メカニズムを使用して変更できます。
さらに、選択する値は標準ではなく、各コーデックに依存するため、各レベルが提供するものを理解するには、各アルゴリズムのドキュメントを参照する必要があります。
ZSTD
レベル設定を参照するために、ZSTD コーデックは定数 ZstandardCodec.PARQUET_COMPRESS_ZSTD_LEVEL
を宣言します。
可能な値の範囲は 1 ~ 22 で、デフォルト値は 3 です。
<code class="language-java">CarpetWriter<T> writer = new CarpetWriter.Builder<>(outputFile, clazz) .withCompressionCodec(CompressionCodecName.ZSTD) .build();</code>
LZO
レベル設定を参照するために、LZO コーデックは定数 LzoCodec.LZO_COMPRESSION_LEVEL_KEY
を宣言します。
可能な値の範囲は 1 ~ 9、99、999 で、デフォルト値は「999」です。
<code class="language-java">ParquetWriter<Organization> writer = AvroParquetWriter.<Organization>builder(outputFile) .withSchema(new Organization().getSchema()) .withCompressionCodec(CompressionCodecName.ZSTD) .build();</code>
GZIP
定数は宣言されていません。文字列「zlib.compress.level」を直接使用する必要があります。可能な値の範囲は 0 ~ 9 で、デフォルト値は「6」です。
<code class="language-java">ParquetWriter<Organization> writer = ProtoParquetWriter.<Organization>builder(outputFile) .withMessage(Organization.class) .withCompressionCodec(CompressionCodecName.ZSTD) .build();</code>
さまざまな圧縮アルゴリズムのパフォーマンスを分析するために、異なるタイプのデータを含む 2 つの公開データセットを使用します。
Parquet Java で有効になっているいくつかの圧縮アルゴリズム (UNCOMPRESSED、SNAPPY、GZIP、LZO、ZSTD、LZ4_RAW) を評価します。
予想どおり、parquet-java が提供するデフォルト設定と各アルゴリズムのデフォルト圧縮レベルで Carpet を使用します。
ソース コードは GitHub で見つけることができます。テストは AMD Ryzen 7 4800HS CPU と JDK 17 を搭載したラップトップで行われました。
各圧縮のパフォーマンスを理解するために、同等の CSV ファイルを参照として使用します。
格式 | gov.it | 纽约出租车 |
---|---|---|
CSV | 1761 MB | 2983 MB |
未压缩 | 564 MB | 760 MB |
SNAPPY | 220 MB | 542 MB |
GZIP | **146 MB** | 448 MB |
ZSTD | 148 MB | **430 MB** |
LZ4_RAW | 209 MB | 547 MB |
LZO | 215 MB | 518 MB |
2 つのテストのうち、GZip と Zstandard を使用した圧縮が最も効率的でした。
Parquet エンコード技術のみを使用すると、ファイル サイズを元の CSV サイズの 25% ~ 32% に削減できます。さらに圧縮を適用すると、CSV サイズの 9% ~ 15% に縮小されます。
情報を圧縮するとどれくらいのオーバーヘッドが発生しますか?
同じ情報を 3 回書き込み、平均秒数を計算すると、次のようになります。
算法 | gov.it | 纽约出租车 |
---|---|---|
未压缩 | 25.0 | 57.9 |
SNAPPY | 25.2 | 56.4 |
GZIP | 39.3 | 91.1 |
ZSTD | 27.3 | 64.1 |
LZ4_RAW | **24.9** | 56.5 |
LZO | 26.0 | **56.1** |
SNAPPY、LZ4、LZO は圧縮なしの場合と同様の時間を達成しますが、ZSTD ではオーバーヘッドが追加されます。 GZIP のパフォーマンスは最悪で、書き込み時間が 50% 遅くなりました。
ファイルの読み取りは、必要な計算量が少ないため、書き込みよりも高速です。
ファイル内のすべての列を読み取るのにかかる時間 (秒):
算法 | gov.it | 纽约出租车 |
---|---|---|
未压缩 | 11.4 | 37.4 |
SNAPPY | **12.5** | **39.9** |
GZIP | 13.6 | 40.9 |
ZSTD | 13.1 | 41.5 |
LZ4_RAW | 12.8 | 41.6 |
LZO | 13.1 | 41.1 |
読み取り時間は非圧縮情報の読み取り時間に近く、解凍のオーバーヘッドは 10% ~ 20% です。
読み取り時間と書き込み時間の点で他のアルゴリズムより大幅に優れているアルゴリズムはなく、すべて同様の範囲内にあります。 ほとんどの場合、情報を圧縮すると、スペースの節約 (および送信) 時間の損失を補うことができます。
これら 2 つの使用例では、どちらかのアルゴリズムを選択する決定要因は、おそらく達成される圧縮率であり、ZSTD と Gzip が顕著です (ただし、書き込み時間は劣ります)。
各アルゴリズムにはそれぞれ利点があるため、最良のオプションは、データを使用してテストし、どの要素がより重要であるかを検討することです。
人生のすべてのことと同様、それはトレードオフであり、それを最もよく補うものを考える必要があります。 Carpet では、何も設定しない場合、デフォルトで圧縮に Snappy が使用されます。
値は、CompressionCodecName 列挙で使用可能な値の 1 つである必要があります。各列挙値には、アルゴリズムを実装するクラスの名前が関連付けられます:
<code class="language-java">CarpetWriter<T> writer = new CarpetWriter.Builder<>(outputFile, clazz) .withCompressionCodec(CompressionCodecName.ZSTD) .build();</code>
Parquet はリフレクションを使用して指定されたクラスをインスタンス化します。このクラスは CompressionCodec インターフェイスを実装する必要があります。そのソース コードを見ると、それが Parquet ではなく Hadoop プロジェクトにあることがわかります。これは、Parquet が Java 実装において Hadoop といかにうまく連携しているかを示しています。
これらのコーデックのいずれかを使用するには、その実装を含む JAR を依存関係として追加していることを確認する必要があります。
parquet-java を追加するときに、推移的な依存関係にすべての実装が存在するわけではありません。あるいは、Hadoop の依存関係を積極的に除外しすぎている可能性があります。
org.apache.parquet:parquet-hadoop 依存関係に、SnappyCodec、ZstandardCodec、Lz4RawCodec の実装を含めます。これらの 3 つのアルゴリズムの実際の実装とともに、snappy-java、zstd-jni、aircompressor 依存関係を推移的にインポートします。 。
hadoop-common:hadoop-common 依存関係には、GzipCodec の実装が含まれます。
BrotliCodec と LzoCodec の実装はどこですか? これらは Parquet または Hadoop の依存関係に含まれていない。そのため、追加の依存関係を追加せずにこれらを使用すると、アプリケーションはこれらの形式で圧縮されたファイルを使用できなくなります。
以上がParquet Java の圧縮アルゴリズムの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。