nisshiee.org

中間計算バッチがDB負荷を上げていた問題を解消した

2018-12-20

この記事は@hatappiに捧げる記事です。@hatappiと僕、お互いのよりより学びになればいいなと思って書きました。
一方、結構ボカして書いているので、他の方にはあんまりおもしろくないかもしれません。

システム概要

  • メインサービスと、そのサービスの制御をするための補助サービスがある
  • 補助サービスは画面を伴うサービスで、ユーザ入力を正規化されたDB1に書き込む
  • 中間バッチが、補助サービスが書き込んだ値に基づいて事前計算をし、メインサービスの都合に合わせたデータ構造でDB2に記録
  • メインサービスは、中間バッチの結果を読んで動作する

起こった問題

  • 中間バッチが書き込むDB2のレコード数が想定以上に増えた(数百万)
  • 中間バッチがDB2を更新する際に、全レコードをdelete/insertしていた
    • つまり、数百万レコードを毎度消して、入れ直していた
    • その結果、DB2の負荷が許容範囲を超えたりSlaveのレプリケーション遅延が大きくなったりした

ヤバくなったDBのCPU使用率

暫定対応

  • とりあえずやばかったのでDBをスケールアップした
    • AuroraのFOは数秒しかダウンタイムがなくて(マルチAZの場合)素晴らしいですね
    • うちのサービスはDBが居なくてもサービスは稼働できるので良いですね
    • Goのdatabase/sqlパッケージはコネクション管理までやってくれるけど、優秀ですね

スケールアップ前後のDBのCPU使用率

根本対応

  • 全レコードの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
    }
})

コードイメージ

差分更新に変更後のDBのCPU使用率