RubyでApache Arrow/Parquetを試してみた ~Red Arrow/Red Parquet入門~
はじめに
「Apache Arrow/Parquetって何がすごいの?」をRubyで体感したくて試してみました。
この記事では、Arrow/Parquetの概要と、Rubyでの使い方(Red Arrow/Red Parquet)を紹介します。
Arrow/Parquetとは?
Apache Arrow
- インメモリ・フォーマット(列指向)
- 異なる言語間で高速にデータをやり取りできる
- 公式: arrow.apache.org
Apache Parquet
- ストレージ・フォーマット(列指向ファイル形式)
- データ保存・取得が効率的
- 公式: parquet.apache.org
Red Arrow(Ruby用Arrow Gem)
- Arrowのデータ構造(Arrow::Tableなど)をRubyで操作できる
- GitHub: apache/arrow/ruby/red-arrow
Red Parquet(Ruby用Parquet Gem)
- Parquetファイルの読み書きができる
- GitHub: red-data-tools/red-parquet
ざっくりまとめ
- Arrow: メモリ上の共通語(高速処理・言語間連携)
- Parquet: ディスク保存の効率化(I/O最適化)
- Red Arrow: RubyでArrowフォーマットのデータ構造(Arrow::Tableなど)を高速に扱えるGem
- Red Parquet: RubyでParquetファイルの読み書きができるGem(Arrowデータとの連携も簡単)
試す
ディレクトリ構成
redarrow/
├── Dockerfile
├── generate_file.rb
├── read_csv_with_redarrow.rb
├── read_csv_with_std.rb
Dockerfile
# 最新の安定版Rubyイメージを使用
FROM ruby:latest
RUN apt-get update && \
apt-get install -y --no-install-recommends \
wget \
ca-certificates \
lsb-release \
build-essential \
pkg-config \
libglib2.0-dev \
libgirepository1.0-dev \
&& rm -rf /var/lib/apt/lists/*
RUN wget -q -O /tmp/apache-arrow-apt-source.deb https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb && \
apt-get install -y --no-install-recommends /tmp/apache-arrow-apt-source.deb && \
rm /tmp/apache-arrow-apt-source.deb && \
apt-get update && \
apt-get install -y --no-install-recommends \
libarrow-glib-dev \
libparquet-glib-dev \
&& rm -rf /var/lib/apt/lists/*
RUN gem install rubygems-requirements-system --no-document
RUN gem install red-arrow red-parquet --no-document
# 作業ディレクトリの設定
WORKDIR /usr/src/app
CMD ["/bin/bash"]
generate_file.rb
require 'pathname'
require 'fileutils'
CSV_FILE = "large_sample_data.csv"
PARQUET_FILE = CSV_FILE.sub(".csv", ".parquet")
NUM_RECORDS = 10_000_000
def generate_large_csv(file_path, num_records)
puts "--- CSVファイル生成開始 (#{num_records}行) ---"
start_time = Time.now
header = "id,value,category\n"
data = num_records.times.map do |i|
id = i + 1
value = (Math.sin(i / 100.0) * 1000).round(2)
category = "GROUP_#{i % 5}"
"#{id},#{value},#{category}\n"
end
File.write(file_path, header + data.join)
duration = (Time.now - start_time).round(3)
csv_size_mb = File.size(file_path) / (1024.0**2)
puts "CSVファイル生成完了。"
puts " 所要時間: #{duration}秒"
puts " ファイルサイズ: #{csv_size_mb.round(2)} MB"
end
[CSV_FILE, PARQUET_FILE].each do |f|
if File.exist?(f)
File.delete(f)
puts "既存ファイルを削除しました: #{f}"
end
end
begin
generate_large_csv(CSV_FILE, NUM_RECORDS)
puts "\nデータ生成処理完了。読み込み時間計測には、他のスクリプトを実行してください。"
rescue => e
puts "\nエラーが発生しました: #{e.class} - #{e.message}"
end
read_csv_with_redarrow.rb
require 'arrow'
require 'parquet'
require 'pathname'
require 'fileutils'
CSV_FILE = "large_sample_data.csv"
PARQUET_FILE = CSV_FILE.sub(".csv", ".parquet")
NUM_RECORDS = 10_000_000
def process_data_with_redarrow(csv_file, parquet_file)
unless File.exist?(csv_file)
puts "エラー: データファイルが見つかりません: #{csv_file}"
return
end
puts "\n--- Apache Arrow/Parquet 処理開始 ---"
if File.exist?(parquet_file)
File.delete(parquet_file)
puts "既存Parquetファイルを削除しました: #{parquet_file}"
end
# 1. CSV -> Arrow Table 読み込み
puts "フェーズ 1: CSV -> Arrow Table ロード開始"
csv_read_start_time = Time.now
table = Arrow::Table.load(csv_file)
csv_read_duration = (Time.now - csv_read_start_time).round(3)
puts "CSVロード完了。所要時間: #{csv_read_duration}秒"
# 2. Arrow Table -> Parquetへの書き出し
puts "フェーズ 2: Arrow Table -> Parquet ファイル書き出し開始"
parquet_write_start_time = Time.now
table.save(parquet_file, format: :parquet)
parquet_write_duration = (Time.now - parquet_write_start_time).round(3)
puts "Parquet書き出し完了。所要時間: #{parquet_write_duration}秒"
# 3. Parquetの読み込み
puts "フェーズ 3: Parquet ファイル -> Arrow Table ロード開始"
parquet_read_start_time = Time.now
parquet_table = Arrow::Table.load(parquet_file)
parquet_read_duration = (Time.now - parquet_read_start_time).round(3)
puts "Parquetロード完了。所要時間: #{parquet_read_duration}秒"
# ファイルサイズ計測
csv_size_mb = File.size(csv_file) / (1024.0**2)
parquet_size_mb = File.size(parquet_file) / (1024.0**2)
# 計測結果の比較表示
puts "\n--- 処理結果サマリー ---"
puts "処理時間:"
puts " CSV読み込み時間: #{csv_read_duration}秒"
puts " Parquet書き出し時間: #{parquet_write_duration}秒"
puts " Parquet読み込み時間: #{parquet_read_duration}秒"
puts "\nファイルサイズ:"
puts " CSVファイルサイズ: #{csv_size_mb.round(2)} MB"
puts " Parquetファイルサイズ: #{parquet_size_mb.round(2)} MB"
end
begin
process_data_with_redarrow(CSV_FILE, PARQUET_FILE)
puts "\n処理を終了します。"
rescue => e
puts "\nエラーが発生しました: #{e.class} - #{e.message}"
end
read_csv_with_std.rb
require 'csv'
require 'pathname'
require 'fileutils'
CSV_FILE = "large_sample_data.csv"
def read_csv_read_io_only(csv_file)
unless File.exist?(csv_file)
puts "エラー: データファイルが見つかりません: #{csv_file}"
return
end
puts "\n--- CSVファイルの読み込み開始 ---"
puts "注意: ファイル全体がメモリにロードされます。"
read_start_time = Time.now
csv_table = CSV.read(csv_file, headers: true)
load_duration = (Time.now - read_start_time).round(3)
record_count = csv_table.size
puts "CSVデータ読み込み完了。"
puts " 所要時間: #{load_duration}秒"
puts " レコード数: #{record_count}"
return load_duration
end
begin
read_csv_read_io_only(CSV_FILE)
puts "\n処理を終了します。"
rescue => e
puts "\nエラーが発生しました: #{e.class} - #{e.message}"
end
docker imageつくって中に入る
docker build --no-cache -t red-arrow-test .
docker run --rm -it -v "$(pwd)":/usr/src/app red-arrow-test /bin/bash
1000万行の4列のcsvデータつくる
ruby generate_file.rb
rubyの標準のCSV.readをつかって作成したファイル読み込んでみる
ruby read_csv_with_std.rb
Red ArrowをつかってCSVファイルを読み込んだり、パーケット出力してみたりする
ruby read_csv_with_redarrow.rb
ベンチマーク結果
とても単純なデータ生成しかしていないので、不正確だとはおもいますが、下記な感じ
データ生成
- 1,000万行のCSVファイルを生成
- 所要時間: 7.774秒
- ファイルサイズ: 221.44 MB
標準CSV(Ruby標準CSVライブラリ)
- 読み込み時間: 42.086秒
- レコード数: 10,000,000
Red Arrow/Red Parquet
- CSV → Arrow Table 読み込み: 0.672秒
- Arrow Table → Parquet書き出し: 0.695秒
- Parquet → Arrow Table 読み込み: 0.433秒
- Parquetファイルサイズ: 158.05 MB
サマリー
| 処理内容 | 標準CSV | Red Arrow/Parquet |
|---|---|---|
| CSV読み込み | 42.086秒 | 0.672秒 |
| Parquet書き出し | - | 0.695秒 |
| Parquet読み込み | - | 0.433秒 |
| CSVファイルサイズ | 221.44MB | 221.44MB |
| Parquetファイルサイズ | - | 158.05MB |
所感
- Red Arrow/Parquetは圧倒的に高速(CSV読み込みで約60倍速い)
- Parquetはファイルサイズも小さく、I/O効率も良い
- 大量データ処理や分析用途では、Arrow/Parquet+Red Arrow/Red Parquetの組み合わせが非常に有効
まとめ
今回はApache ArrowとApache ParquetをRubyから扱い、どれくらい高速に大量データを処理できるかをざっくり試してみました。
RubyのGem(Red Arrow/Red Parquet)を使うことで、標準CSVより圧倒的に速く・効率的にデータを読み込めることが分かりました。
実際にベンチマークしてみて、
「大量データでもストレスなく扱える」「ファイルサイズも小さくなる」など、
データ分析やETL処理の現場でも十分使える手応えを感じました。
次は、Arrow/Parquetで読み込んだデータを使って、
Rubyで可視化・機械学習などの実践を調査したいと思います。
参考情報・素晴らしいGemを公開してくださっている皆様に感謝します。
red-data-tools
ClearCodeさんのブログ