中間計算バッチがDB負荷を上げていた問題を解消した
2018-12-20
この記事は@hatappiに捧げる記事です。@hatappiと僕、お互いのよりより学びになればいいなと思って書きました。
一方、結構ボカして書いているので、他の方にはあんまりおもしろくないかもしれません。
システム概要
- メインサービスと、そのサービスの制御をするための補助サービスがある
- 補助サービスは画面を伴うサービスで、ユーザ入力を正規化されたDB1に書き込む
- 中間バッチが、補助サービスが書き込んだ値に基づいて事前計算をし、メインサービスの都合に合わせたデータ構造でDB2に記録
- メインサービスは、中間バッチの結果を読んで動作する
起こった問題
- 中間バッチが書き込むDB2のレコード数が想定以上に増えた(数百万)
- 中間バッチがDB2を更新する際に、全レコードをdelete/insertしていた
- つまり、数百万レコードを毎度消して、入れ直していた
- その結果、DB2の負荷が許容範囲を超えたりSlaveのレプリケーション遅延が大きくなったりした
暫定対応
- とりあえずやばかったのでDBをスケールアップした
- AuroraのFOは数秒しかダウンタイムがなくて(マルチAZの場合)素晴らしいですね
- うちのサービスはDBが居なくてもサービスは稼働できるので良いですね
- Goの
database/sqlパッケージ
はコネクション管理までやってくれるけど、優秀ですね
根本対応
- 全レコードのdelete/insertではなく、差分更新にした
- アプリケーションレイヤーで差分を計算し、変更が発生した分だけdeleteとinsertを発行
- 変更が発生するレコードの割合は小さいことがわかっていた
newRecords := CreateNewRecords()
oldRecords := GetRecords(conn)
deletingRecords := sub(oldRecords, newRecords)
insertingRecords := sub(newRecords, oldRecords)
startTransaction(conn, func() {
if err := bulkDelete(tx, deletingRecords); err != nil {
return err
}
if err := bulkInsert(tx, insertingRecords); err != nil {
return err
}
})