はじめに
あるプロジェクトで、 Laravel
を使って構築したシステムの DB のデータを csv で出力する要件があり、Laravel Excel
を導入して実現していました。
しかしあるとき、出力した csv のレコードが重複しているという報告があり、実際に全く同じレコードが2件重複して出力されていることがわかりました。
さらには、出力されるはずのデータが含まれていないことも判明しました。
この記事では、この問題を解決するまでの過程を紹介し、自らへの戒めと教訓としたいと思います。
Laravel Excel とは
Laravel
で Excel ファイルの操作を行うためのライブラリです。
PhpSpreadsheet
というライブラリが元になっていて、 Laravel
での操作に特化しています。
今回のプロジェクトでは、この Laravel Excel
を使って、主に csv ファイルの入出力を行っています。
環境
PHP : 7.3.5
Laravel : 5.8
PostgreSQL : 11.3
Laravel Excel : 3.1.9
発生した問題
- 約4万件のデータを抽出した際に、20〜30件ほど重複している
- CSVを出力するたびに、重複するデータが異なる
- データが重複した分だけ、本来であれば存在するデータがなくなっている
- データの総件数は毎回変わらない
犯人探し
PostgreSQL
LEFT JOIN
のやり方が間違っている?
とある関係で Eager Loading
ではなく LEFT JOIN
を使用しているので、これが真っ先に原因かと思ったのですが、単純なクエリだったのでこれは問題なさそうでした。
SELECT
文の書き方が間違っている?
SELECT
でテーブル名を指定しておらず、意図しないテーブルの id
などのデータが出力されてしまっているのかとも思いましたが、テーブル名を指定する書き方をしているのでこれも大丈夫そうでした。
PostgreSQL の バグ?
こんなバグがあったらもっと叩かれてそう。。
Laravel Excel
Github の issue で同じような問題を探してみる
次にライブラリである Laravel Excel
のバグかと思い、 Github で同じような報告がないか探してみました。
が、これは特に見つかりませんでした。
ライブラリの中身をみてみる
中でどのような処理が行われているのかわからなかったので、中のコードを読んでみました。
今回は Laravel
の QueryBuilder
から csv を作成していたので、途中で chunk
メソッド(リファレンス) が使用されていました。
これはDBから取得するデータを小分けにすることで、メモリ使用量を抑え、大量のデータでも出力できるようにしているのだと思います。
再度考察
ここまでで具体的な原因はわからなかったので、再度原因を考察してみることにしました(実際は結構頭抱えてました)。
改めて重複しているデータを眺めてみる
よくよく見てみると、何故か重複データは2000行目や5000行目など、1000行目単位の付近で発生していました。
この 1000 という数字が何かのヒントだと思い、調べてみると、Laravel Excel
の設定ファイルに chunk_size
という項目があり、これが 1000 になっていました。
これを試しに 500 にしてみると、重複が1500行目や4500行目など、500の区切りでも発生するようになりました。
chunk
によって発行されているクエリを調べてみる
SELECT * FROM 'テーブル名' LIMIT 1000 OFFSET 0;
そうか、わかったぞ!(cv:高山みなみ)
原因は...
ORDER BY
に、ユニークになるカラムを指定していないこと でした。
今回のクエリでは、ユニークでないカラムを ORDER BY
に指定していたため、このカラムが同じ値の場合、並び順がDBによって決定されてしまっていました。
これと LIMIT
および OFFSET
が合わさることにより、クエリを発行するたびに並び順が変更され、境目付近のデータが重複してしまっていたのだと考えられます。
この現象については、下記のブログに詳細が記載されていました。
https://blog.mmmcorp.co.jp/blog/2018/03/22/sql_order_by/
同じように LIMIT
、 OFFSET
を使用しているペジネーションなんかでも注意が必要ですね。
そして ORDER BY
にユニークとなるカラムを設定したところ、見事に重複がなくなりました。
これで解決ですね。
さいごに
というわけで結局犯人は自分だったわけですが。。
原因がわかるとすっきりしますが、わからないうちは辛く苦しかったです。。笑
ライブラリを使っているときに予期せぬことが起きた場合には、ライブラリの中身を見ることが大事ですね。
では最後にまとめ