こんにちは。もぐめっとです。
先日、機会があってdivに行くことがありました。
とてもおしゃれなオフィスで素晴らしいところでした。
いつか私もおしゃれなオフィスを持てるように頑張りたく存じます。
本日の記事のきっかけはこれになります。
普段、1ヶ月450万書き込みくらいなのにそれをものの1時間で2倍ちかい数字を叩き出してしましました。
ということでバッチ処理でやらかしてしまったのでその原因と対策のお話をします。
原因は多重ループ
なんとなくお察しの方はわかるとおもいますが、実装として無限ループっぽいことしてしまったんですよね。。。
問題となったコードがこんな感じです。
(async () => {
const snapshots = await hogeCollection.get()
console.log(snapshots.size)
for (const document of snapshots.docs) {
const data = { hoge: 20 }
await batchMultiByQuerySnapshot(snapshots, (batch, document) => {
batch.update(document, data)
})
}
})()
batchMultiByQuerySnapshotは500個ごとに渡された配列を区切ってbatch.commit()してくれる関数になります。
どこが原因かというとここです。
for (const document of snapshots.docs) {
batchMultiByQuerySnapshotにsnapshotsをわたしていてそれで済む話だったので、forで回す必要がなかったのですが、なぜかsnapshotsのdocumentの数だけループを回してしまいました。
全量データは8万件あったのですが、8万の8万乗、、、ものすごい数のバッチがまわってしまいました。
普段無限ループが置きないようにwhileは使わないようにしているのですが、for文でも事件はおこるという例になります。
対策
これの対策としては下記の2点が考えられます
- バッチ処理中のログを出力するようにして状況を把握できるようにする
- コードレビューをちゃんとする
バッチ処理中のログを出力するようにして状況を把握できるようにする
これに関してはbatch.commit()する段階で今何回目のコミットなのかを出力するように変更しました。
これによりバッチの進捗も把握できるようになったので無限ループにも気づきやすくなったと思います。
コードレビューをちゃんとする
こちらのサービスはバックエンドは私一人なのでレビューできる人がいないため対策としては打つことができないのですが、もしチームで開発しているなら当たり前ですがちゃんとコードレビューをするようにしましょう。
おまけ対策
根本的な原因の対策ではないのですが、最初にデータを取ってくるここの処理について、実は改善ができます。
const snapshots = await hogeCollection.get()
実はこれをするとRDBでいうselect * from hoge
をしているようなもので、全データ転送しているので転送コストがかかってしまっています。
なので、下記実装にしてあげると必要なフィールドしかとってこないのでデータ転送節約になります。
const snapshots = await hogeCollection.select('hoge').get()
まとめ
バッチ処理をするときは進捗が見えるようにして異常がないかをかならず見守るようにしましょう!
最後に、ワンナイト人狼オンラインというゲームを作ってます!よかったら遊んでね!
他にもCameconやOffchaといったサービスも作ってるのでよかったら使ってね!
また、チームビルディングや技術顧問、Firebaseの設計やアドバイスといったお話も受け付けてますので御用の方は弊社までお問い合わせください。