Laravel:5億越えのレコードをEloquentで消費メモリを抑えながら処理する方法

Laravel:5億越えのレコードをEloquentで消費メモリを抑えながら処理する方法

Laravel:5億越えのレコードをEloquentで消費メモリを抑えながら処理する方法

私の作成したアプリにて不具合発生の連絡がありました。
結論を先に言ってしまえば、レコード数が多すぎて私の書いたコードではTimeOut 起こして終了してました。

TimeOut時間を伸ばす。

その方法もあるのですが、今回は処理時間を早くして解決するアプローチを記載していきます。

止まっていたのはLaravelスケジュラーから起動したQueueWorker

QueueWorker用のコンテナを建てていたのですが、コイツがErrorで落ちてQueue自体が動いてませんでした。
データ取り込みが出来ないので、使っていただいている企業さんはすぐ気付いて連絡くれたようです。

Logを確認すると存在するカラムに対してERROR: Undefined property と言われています。
経験上【「ある」のに「ない」】と言われる場合は、大概TimeOutなどでデータ欠損が出てる時です。

TimeOutの原因は非正常系処理のモレだったりするので、コードを追っていきます。
で、見てみても問題なさそうだったのですが、間違いなく止まっている。

何かの拍子に止まっただけ?

具体的な対策は一切できず、コンテナ再起動すると、Queueが順調に流れ出します。
これで完了かと思いきや、2週間後にまた連絡が。
ここでもまた再起動をしたのですが、今度は3時間後にコンテナが止まってしまいます。

根本的でデッカい問題が存在している

ここまで頻度上がればね、絶対何かあることは確定です。
で、テーブルの確認をしてみると、レコードが5億件超えてました。

レコードの大きいテーブルの処理で固まる場合はインデックスを疑え

大体これで解決しますよね。今回も確かにindex対象外のカラムに対してWhereをかけてました。
で、これが悩みポイント。

JSONを格納したカラムのJSON内の要素でWhereをかけている

JSONのレコードは基本的にIndexを貼れません。(テキストとかと同じです)
やってやれ無い事は無いというレベルでインデックスを貼る方法はあります。
が、5億のレコードに対しては(何が起こるかわからず)やる気にはなれません。

問題は大量のデータに対してget()を使って取得している事

get() 便利なのでついつい多用してしまいますが、コイツは「指示したレコード全部取ってこい」という命令です。
5億が 1/10 に抽出できたとしても5000万レコードを一気に取得して処理しようという命令になります。
メモリ食うし、メモリ食えばこれもフリーズ原因だしね。

大量レコードの処理には chunk() を使え!

chunk($n) はページネーションの様なものと覚えておくと理解しやすいです。

 chunk(指定個数) :条件に合うレコードを指定個数ずつ取り出して処理を実行する

具体的にはこんな感じで記載します。

こうすると、何千万レコードあっても200件づつ小分けして処理を続け完走します。
メモリに余裕がある場合は、まとめる数を1000にしたりすれば速度が速くなります。

まとめ

最初からget()使わ無いで chunk() で処理しておけばよかったんですけどね。
レコード数の増加ペースの読み間違いが大きな原因でした。

大反省な事例ですが、いい経験になりました。