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

Laravel:5億越えのレコードをEloquentで消費メモリを抑えながら処理する方法
私の作成したアプリにて不具合発生の連絡がありました。
結論を先に言ってしまえば、レコード数が多すぎて私の書いたコードではTimeOut 起こして終了してました。
その方法もあるのですが、今回は処理時間を早くして解決するアプローチを記載していきます。
止まっていたのはLaravelスケジュラーから起動したQueueWorker
QueueWorker用のコンテナを建てていたのですが、コイツがErrorで落ちてQueue自体が動いてませんでした。
データ取り込みが出来ないので、使っていただいている企業さんはすぐ気付いて連絡くれたようです。
Logを確認すると存在するカラムに対してERROR: Undefined property
と言われています。
経験上【「ある」のに「ない」】と言われる場合は、大概TimeOutなどでデータ欠損が出てる時です。
TimeOutの原因は非正常系処理のモレだったりするので、コードを追っていきます。
で、見てみても問題なさそうだったのですが、間違いなく止まっている。
何かの拍子に止まっただけ?
具体的な対策は一切できず、コンテナ再起動すると、Queueが順調に流れ出します。
これで完了かと思いきや、2週間後にまた連絡が。
ここでもまた再起動をしたのですが、今度は3時間後にコンテナが止まってしまいます。
ここまで頻度上がればね、絶対何かあることは確定です。
で、テーブルの確認をしてみると、レコードが5億件超えてました。
レコードの大きいテーブルの処理で固まる場合はインデックスを疑え
大体これで解決しますよね。今回も確かにindex対象外のカラムに対してWhere
をかけてました。
で、これが悩みポイント。
JSONのレコードは基本的にIndexを貼れません。(テキストとかと同じです)
やってやれ無い事は無いというレベルでインデックスを貼る方法はあります。
が、5億のレコードに対しては(何が起こるかわからず)やる気にはなれません。
問題は大量のデータに対してget()を使って取得している事
get()
便利なのでついつい多用してしまいますが、コイツは「指示したレコード全部取ってこい」という命令です。
5億が 1/10 に抽出できたとしても5000万レコードを一気に取得して処理しようという命令になります。
メモリ食うし、メモリ食えばこれもフリーズ原因だしね。
大量レコードの処理には chunk() を使え!
chunk($n) はページネーションの様なものと覚えておくと理解しやすいです。
chunk(指定個数) :条件に合うレコードを指定個数ずつ取り出して処理を実行する
具体的にはこんな感じで記載します。
1 2 3 4 5 6 7 8 |
MqttData::where('JSONDATA->id', $device->device_code) ->where('deleted_at', NULL) ->orderBy('datetime', 'asc') ->chunk(200, function ($datas) { /* 抽出したレコードに対して実行する処理を記載 */ \<span class="pl-v">Log</span>::<span class="pl-en">debug</span>(<span class="pl-en">json_encode</span>(<span class="pl-s1"><span class="pl-c1">$datas</span></span>)); } ); |
こうすると、何千万レコードあっても200件づつ小分けして処理を続け完走します。
メモリに余裕がある場合は、まとめる数を1000にしたりすれば速度が速くなります。
まとめ
最初からget()使わ無いで chunk() で処理しておけばよかったんですけどね。
レコード数の増加ペースの読み間違いが大きな原因でした。
大反省な事例ですが、いい経験になりました。
-
前の記事
Laravel:ハッシュ化したパスワードとの一致確認 2021.09.22
-
次の記事
Laravel:数字8桁の一意の乱数を生成する5つの方法 2021.10.05
コメントを残す