4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

qnoteAdvent Calendar 2020

Day 15

Laravel Excel から出力した csv のレコードが重複してしまっていた問題を解決した話

Last updated at Posted at 2020-12-15

はじめに

あるプロジェクトで、 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 で同じような報告がないか探してみました。
が、これは特に見つかりませんでした。

ライブラリの中身をみてみる

中でどのような処理が行われているのかわからなかったので、中のコードを読んでみました。
今回は LaravelQueryBuilder から 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/

同じように LIMITOFFSET を使用しているペジネーションなんかでも注意が必要ですね。

そして ORDER BY にユニークとなるカラムを設定したところ、見事に重複がなくなりました。
これで解決ですね。

さいごに

というわけで結局犯人は自分だったわけですが。。
原因がわかるとすっきりしますが、わからないうちは辛く苦しかったです。。笑
ライブラリを使っているときに予期せぬことが起きた場合には、ライブラリの中身を見ることが大事ですね。

では最後にまとめ

並び順の権を DB に握らせるな!!

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?