はじめに
マスタデータ180万件を移行したいという依頼がありました。
大量のデータをインポートするRuby on RailsのGemとしてactiverecord-importが有名ですが、100万とかなると実行に時間がかかりそうでした。
その際にpostgres-copyというGemで実行時間がかからず移行できたので、そのときの手順を記載します。
※S3はマスタデータファイルの保存先として使用します。初めはGitに含めてパッチを当てようとしましたが、Gitに大きいファイルを含めてしまうとリGitのパフォーマンスが低下して重くなってしまうため、Gitに含めずデータ移行をします。
前提
- Ruby on Rails
- PostgreSQL
- AWS S3
postgres-copy、aws-sdk-s3の導入
Gemfileにpostgres-copy、aws-sdk-s3を追加します。
gem 'postgres-copy'
gem 'aws-sdk-s3'
その後bundle installをします(Dockerを使っています)
docker-compose build
docker-compose up -d
インポートするファイルを準備する
インポートするファイル(CSV)を用意します。
180万件入っているCSVファイルはそのままインポートできませんので、ファイルを分割します。
ファイルサイズが大きすぎますと後ほどS3からCSVファイルを読み取って登録する際に読み取る時間がかかりますので、ファイルサイズは調整をして下さい。
下記はsample.csvをsample-<連番>.csvに分割するコマンドです。
split -l 10000 -d -a 3 sample.csv sample-
上記コマンドで1万行にCSVファイルを分割することができました。
ただし、拡張子が付いていないので、拡張子をつけます。
分割したファイルをS3に保存します。
データ移行のパッチを作成する
rails cで実行するパッチのコマンドを用意します。
下記のようなコマンドでS3に保存したファイルを取得して、データを登録します。
client = Aws::S3::Client.new(
region: ENV['AWS_REGION'],
access_key_id: ENV['AWS_ACCESS_KEY_ID'],
secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
)
rows = []
(0..184).each do |i| # ここの184を分割したファイルの連番の一番大きい数に変更する。
formatted_number = format("%03d", i)
puts "処理中: #{formatted_number}"
file = client.get_object(bucket: 'sample-import-bucket', key: "sample-#{formatted_number}.csv")
csv = CSV.parse(file.body.read)
csv.each do |row|
rows << {
id: row[0],
title: row[1],
body: row[2],
}
end
end
Benchmark.realtime do
Post.insert_all!(rows)
end
実行結果
約4分ぐらいで実行することができました。
Benchmark.realtime do
Post.insert_all!(rows)
end
(省略)
=> 230.73561293902458
注意点
insert_allではなくinsert_all!を使いましょう
insert_allは重複データがあった場合に無視します。insert_all!の場合、レコードの重複があったら、ActiveRecord::RecordNotUniqueの例外がraiseされますので、なるべくinsert_all!を使いましょう。
不正なデータがないことを事前に確認する
insert_allの場合はRailsのvalidationを設定していてもスキップされます。
取り込む前に、データが正しいことを確認しましょう。