esm アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

RDBMS付属のツールで大規模データをインポートする

こんにちわ、swamp09です。

先日Railsプロジェクトで遭遇した大規模データのインポートについてお話しします。

ある外部システムから大規模なデータの連携をファイルで行うことになり、ファイルからの大規模データのインポートについていくつか方法を試しました。 大規模データは、月次で連携され毎回データをインポートします。 例として、その大規模データはユーザーデータで CSV 形式で受け取るとして、users.csv を毎月ファイルで受け取りインポートすると思ってください。

最初にやったのは、取り込む CSV ファイルを分割し並列で読み込む方法です。CSVファイルのサイズが1GB以上あり、データ数が100万件を超えていたので、ファイルを一度に読み込みデータをインポートするのはメモリへの負荷が大きすぎました。 ファイルの分割はsplitコマンドを使用し、10万件ずつのファイルに分割しました。 gem の Parallel を使用し、だいたい下記のような形になりました。 Parallel.map を使用するとデフォルトでCPUコア数から並列数を決定してくれます。https://github.com/grosser/parallel/blob/v1.20.1/lib/parallel/processor_count.rb#L8

def import(file_path)
  file_split(file_path)
  split_files = Dir.glob(SPLITTED_FILES_PATH + '*')
  
  Parallel.map(split_files) do |csv_file|
    csv = CSV.read(csv_file)
    User.insert_all(csv)
    File.delete(csv_file)
  end
end

def file_split(file_path)
 system("split -l 100000 -d --additional-suffix=.csv #{file_path} #{SPLITTED_FILES_PATH}splitted_", exception: true)
end

手元の開発環境で試して、約30分でインポートが終わりました。 こちらを試そうとしたのですが、実行する予定のサーバーのマシンパワーに余力がない懸念があり、もう少し良い方法がないか検討することにしました。

そんな時、チームメンバーから MySQL だとファイルの一括インポートできるツールがあるけど、それと似たようなツールあるだろうか、といった話があがりました。プロジェクトで使っていた RDBMS が Oracle だったのですが、調べてみると SQL*Loader というファイルからの一括インポートのツールがあったので試してみることにしました。 SQL*Loader では、制御ファイルを作ってインポートを行います。

users.csv のデータの例としてこのようなデータが入っているとします。

user_id name prefecture birth_day last_sign_in_at
1 田中 太郎 東京都 2000/06/01 2020/01/30
2 山田 次郎 神奈川県 2000/10/01 2020/03/30

上記のデータから、必要なものが user_idnameaddress だけであるとして、3つだけ抽出します。 制御ファイルを下記のように作成します。

LOAD DATA CHARACTERSET JA16SJIS
INFILE 'users.csv'
INTO TABLE users
APPEND
FIELDS TERMINATED BY ','
(
  user_id INTEGER EXTERNAL,
  name CHAR,
  prefecture CHAR,
  birth_day FILLER,
  last_sign_in_at FILLER
)

SQL*Loader で処理すると、ファイルの分割処理も必要なくなり、処理時間も格段に短くなりました。 スクリプトを自作していた時は約30分ぐらいかかっていた処理時間が SQL*Loader では約3分で終わったので、圧倒的スピードで終わるようになりました。

まとめ

Active Record を使って良い感じに速くデータをインポートする方法を考えなければ、と思っていたところRDBMSの付属のツールを使えば一気に解決しました。 他の RDBMS では、MySQL は mysqlimport があり、PostgreSQL では COPY があるようです。

大量のデータをファイルからインポートするケースはあまり多くないかもしれませんが、なにかの参考になれば幸いです。