Spring Boot アプリケーションをMySQLで開発運用してみた奮闘記 DBREJP編

# このページについて

[MySQL Advent Calendar 2020](https://qiita.com/advent-calendar/2020/mysql) 24日目 です。 

[MySQL Advent Calendar 15日目 | Spring BootアプリケーションをAzure Database for MySQLで開発運用してみた奮闘記](https://qiita.com/zoosm3/items/4bcceefbd228bbecacc4) の続編です。先に15日目に目を通されるとわかりやすいかもしれませんが、読まなくても差し支えありません。

## この記事で取り扱うこと
- この記事のDBREは、[DBREJPコミュニティが実現したい世界観](https://speakerdeck.com/_awache/introducedbrejp?slide=7)の『Databaseを聖域化しすぎない』に沿います
- データベース開発の開発プロセス、MySQLならではの役割横断的な対処が必要なエンジニアのネタが満載です

## この記事で取り扱わないこと
- 技術的、特にMySQLの詳細に関わる部分
- 環境記載以外での動作や考慮事項の詳細 (AWS RDSなど)
- タイトルにDBREとありますが、ネットワークや信頼性向上のことは扱いません
- Spring Bootに関係する話は登場しません

## 注意点
1. 本編は全ていち個人の考察と感想であり、組織やプロジェクト要件、監査要件、技術スタック、スキル等条件によって対処方法は変わることがあります
2. 機能開発中心で運用やインフラの考慮が至っていないところもあります
3. この記事の真似をして起こる損害には関知しません

# 本編はここから
## 本編の舞台
AWSで動いているPHP 7 + Laravelで出来たB2Cサービス(以降便宜上『元システム』)のフルリニューアルです。

### 環境
リニューアル後は15日目の環境の通り、Azureで動く Java 11 + Spring Bootのアプリケーションになりました。
データベースは AWS RDS (MySQL 5.6) から Azure Database for MySQL (MySQL 5.7)になり、元システムから通算1年くらいかけてデータ移行を行いました。設計半年、データ投入と不具合修正半年くらいです。

### 組織
- チームは開発チーム、データ移行チーム、インフラ・保守チームがあります
- 開発が10名、それ以外は3~4名のスモールチームです
- Azure構築は他部署に外注しました
- 作者はなんでも屋さんポジションです。実機能開発は担当外ですが仕様も把握しており、コア処理のプロダクションコードや設定も書きました
  - 初期フェーズはアーキテクトで開発準備をしていましたが、慢性的な人手不足でインフラチームに巻き込まれ兼任しました
  - 初回リリース後は開発支援や運用支援をしました

## 開発編
### データモデリングの進め方
作者でデータモデリングの基本骨子を整備しました。

1週間くらい要求分析、要件定義のドキュメントを読みテーブルとカラムを整理し、不明な箇所は元システムをデバッグしてAPIのレスポンスJSONから推定しました。
要件が整理できたので論理データモデリングとカラムの整理を行おうとしたのですが、まだ曖昧な箇所があり無理でした。

ここではモデリングのやり方に考えかたの差があったのが問題でした。
論理データモデルの設計から開始するのはWebではよくあると思いますが、要件が広範に渡る場合にはすぐにとりかかれるとは限りません。
まずは概念データモデルを作成してエンティティとキー項目になる属性、主要な属性の洗い出しをしてからER図の基本骨子を作成することになりました。

結局リレーションを洗い出すためには制約事項も必要など追加考慮事項が出てきてしまい、基本設計のER図作成で継続検討することになりました。

業務が広範な場合は概念データモデルの洗い出しをしっかり行った方が課題がわかりやすいし、その後の設計も進めやすいです。
データベーススペシャリスト試験が役に立ちました。

### ER図の設計管理ツールの選定
- テキストで差分が管理できる (Gitリポジトリ管理の意味がある)
- 開発ツールがなくても利用できる

ことを念頭に入れてER図の作成には [a5m2](https://a5m2.mmatsubara.com/) を採用しました。

#### a5m2のMac版がない問題
実はER図ツール選定の段階では全員Windowsだったのですが、諸事情で一部Macを利用することになり、ER図を参照できないメンバーが現れました。
Mac OS CatalinaなのでWineが動かず、仕方ないのでER図のみはWindowsマシンで見てもらいました。
PC2台持ちは大変なので、Macでa5m2を使うときは仮想Windowsを使うと便利かもしれません。

### ORマッパ選定
背景は [15日目のSQLを業務ロジックと分離して管理する](https://qiita.com/zoosm3/items/4bcceefbd228bbecacc4#sql%E3%82%92%E6%A5%AD%E5%8B%99%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF%E3%81%A8%E5%88%86%E9%9B%A2%E3%81%97%E3%81%A6%E7%AE%A1%E7%90%86%E3%81%99%E3%82%8B) の通りですが、Javaなので色々なORマッパーがあるため選定しました。

#### Step 1 候補となるORマッパを選定する
- [Java ORマッパー選定のポイント #jsug](https://www.slideshare.net/masatoshitada7/java-or-jsug)
- [ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ](https://www.slideshare.net/nabedge/ordb)

を見ながら、候補を4つに絞りました。

- Spring Data JDBC
- MyBatis
- DBFlute
- DOMA2

#### Step 2 皆で比較検討
利用経験者がいそうなもの、性能トラブルが起こりにくそうなもの、OSSサポートの度合い、SQLの実装方法、DBマイグレーション機能を比較して、開発チームと関係者で協議しました。
最終的にはOSSサポートと利用経験が決め手になり、MyBatisを採用することにしました。

#### Step 3　自動生成のソース管理を整備
MyBatis Generatorで自動生成されるクラスだけでは業務の実装が行えないため、自動生成ソースと業務上個別に実装したソースコードの管理ルールを決めました。
自動生成されたクラスは手を加えず、個別に実装したクラスやSQLとは分離し、継承関係なども付けずに完全に独立にしました。

#### MyBatis導入後
実装の修正が必要で手戻りが発生し当初は反発もありましたが、慣れたら好評でした。
SQLをソースコードと分離できるため、SQL自体のコードレビューもでき、パフォーマンスリスクのあるSQL(フルスキャンなど)を早期に発見して本番リリース前に修正することができました。

### 止まないテーブル定義変更
開発から数ヶ月するとテーブル変更定義が相次ぎました。
早朝無断でのテーブル定義変更周知、日に数回、月に10数回と変更が相次ぎ初回本番リリース直前まで続きました。

#### なぜ変更が頻発するのか
- 諸事情で移行データのクレンジングを早期に行えなかったため、データ移行するためにテーブル定義を修正した   
- 仕様変更があった
- テーブル設計時点でのケアレスミスがあった

#### 対策
仕様変更以外は手の施しようがありました。

- データのクレンジングと分析は、できるだけ要件定義段階からやる
- DDL/DMLの変更管理表を作成し、変更内容、担当者、リリース予定日、差分DDL/DMLのパス、環境へのデプロイ記録を一元管理する
- 複数の変更が発生した場合は変更管理表に順に記入し、担当順番と終了連絡を徹底する
- 差分DDL/DMLの配置先をプロジェクトで規定し、ER図とセットで管理する
- Pull Requestベースで差分DDL/DMLのレビューと、マスタDDLの差分の確認をおこなう

#### 苦労したこと
- データベース定義を変更する経験があまりなく、この自体にどう対処して良いかわからなかった
- 本番リリース日の意識が足りなかった

DBFluteを参考にしつつ、保守運用関係者とルールの見える化、説明、浸透に取り組みました。

http://dbflute.seasar.org/ja/manual/function/generator/task/replaceschema/livingdbmigration.html
https://www.slideshare.net/jflute/jjug-bizreach-dbflute-2014

### データ移行にMySQLのレプリケーションが使えない
当初は　元システムのMySQL 5.6を Azure Database for MySQLにレプリケーションし、トリガーでデータ移行しようと考えましたが元データに問題があり自動で投入すると不整合が起きるため無理でした。


## 運用編

### MySQLスロークエリ対策と初期設定漏れ

データ移行が概ね終了して本運用開始から1か月後くらい、数回スロークエリやコネクション絡みで障害を起こしました。
結局のところ、MySQLの `max_execution_time` がAzure Database for MySQLのデフォルトのままで長すぎ、スロークエリ発生時に待ちすぎたのが原因でした。

#### なぜ障害は起きたのか
- MySQLのサーバパラメータを十分にチューニングする体制がなかったから
   - インフラ構築が外注のため、MySQLの構築も一部断られるなど紆余曲折がありました
- Azure Database for MySQLの理解が足りていなかったから
   - パラメータがチューニング済みというわけではありませんでした
- 運用担当者がエンジニアでなくMySQLもJavaアプリケーションの知識もないから対処しなかった
- スロークエリログをアラート設定していないので誰も気がつかなかった
- SQLのレビューが漏れているから
- スロークエリになったSQLの結合条件がテーブル定義に即していないから
- 負荷試験が十分でなかったから

#### その場の対策
- MySQLのサーバパラメータを見直す
- スロークエリを起こしたSQLを改善する

#### 今後の課題
- MySQLの設計構築はできるだけ内製で知識のある人が行う
- フルマネージドMySQLサービスを使う場合でも、OSSのMySQLの勉強が必要
- システム運用は必ずエンジニアをアサインし、担当サービスの背景と利用技術を把握する
- SQLのレビューは開発担当者が行う
- SQLコーディング時にはテーブル定義を把握する
- SQLが書きにくいと感じたらテーブル設計を疑う
- 必ず現実的な条件で負荷試験を行う
- 本番リリース前に監視設計と設定を必ず行う

#### なぜこうなるのか
- クラウドの責任分解点を理解していないから
- 要件定義時のインフラ選定で運用要件を考慮していないから

#### 対策
- 要件定義時に運用要件を含めましょう
- 技術選定時、インフラ設計時には運用要件を考慮しましょう

### 監視改善

Azure Monitorだけだと物足りないため他ツールでMySQLを監視しました。
変動が激しい値、振り上がりが激しい値を見ていると原因はスロークエリでした。
障害発生後の一時期Azure Database for MySQLのI/O系の不具合を疑われたのですが、そんなことはありませんでした。

#### 心得

- 商用運用後の原因に仮説や想像は当てはまらないことが多々あります。ログと監視ツール、メトリクスなどその場の情報をよく見て事実を把握して調査しましょう
- クラウドを使う場合
  - 利用者側の責任を把握し、開発者側で出来ることを対処しましょう
  - サポートに問い合わせる前に開発者側で出来ることを対処しましょう

### 本番データ利用指針の作成

不具合調査などで本番データをテスト環境に投入したい場合が出てきたので、下記の観点で本番データ利用指針と本番データマスキング手順を決めました。

- 投入して良い環境を決める
- 操作して良い担当者を決める
- 個人情報のマスク方法を考える
- データベースに保持されている外部システムのIDで、意図しない第三者を参照しないようにする

本来は本番のレプリカがあれば不要な手順かもしれません。
MySQLのmysqldumpはホットバックアップですが、本番資産は迂闊に取得して良いものではないので慎重に行う必要があります。

### mainしかない本番データベース
このサービスはmainしかありません。冗長化はAzure Database for MySQLで担保されているのでmain1本でも不便はありませんが、データ調査や抽出時にmainを参照するのは運用上好ましくありません。

#### なぜこうなる
- レプリカの意義が人によってバラバラだったので、負荷分散なら先にSQL性能を出すのが先決となった
   - Active-Standby構成の一貫
   - 負荷分散のため
   - 本番サービスに影響を与えず運用をするため
- 結論の管理ができていなかった

レプリカ構築自体はAzureで簡単にできますが、維持体制が取れそうにないので対処は見送られています。

### データベースの定期バックアップを取っていない事件

運用設計が遅れたためバックアップを取らない時代がありました。
運用設計後にAzure Database for MySQLの機能で簡単に実装してことなきを得ました。

https://docs.microsoft.com/ja-jp/azure/mysql/concepts-backup#point-in-time-restore

# まとめと教訓

## 設計開発
- データ設計にあたっては概念データモデリングから行うことも検討しましょう
- セキュリティと保守性の観点から、SQLはソースコードと分離して管理実装できるようフレームワークを導入できるとベストだと思います
- DBFluteはORマッパとして使わなくても、設計やスキーマ管理のノウハウが役に立ちました
- SQLのレビュー、DDL/DMLのレビュー、データパッチのレビュー。とにかく本番に投入するものは事前にレビューと試験をしましょう
- 負荷試験もやりましょう

## 運用
- サーバ設定や不具合を疑う前にアプリケーションのことも見直しましょう
- 監視設計、運用設計は本番リリース前に終わらせましょう
- トラブル時はログやメトリクスを見て原因を特定しましょう
- MySQLのスロークエリもアラート設定しましょう
- 本番データは指針を決めて使いましょう

## マネジメント
- 結論の管理を忘れると重要なものをやり過ごすことになります
- 初期開発時は機能開発だけでなく運用設計、監視設計のスケジュールと人員も確保しましょう
- 変更管理、構成管理は遅くとも本番リリースまでに必ず策定しましょう
- 工程完了定義は明確にし、本番リリース前には必ず変更管理、構成管理、負荷試験、運用設計、監視設計と監視設定を完了させておきましょう
- クラウドの責任分解点を理解し、利用者側の責任を果たしているかのケアを先に行いましょう
- 業務に必要な技術要素を把握し、本番運用までの品質を担保できる技術力のあるエンジニアをアサインしましょう
- 保守運用にもエンジニアを必ずアサインしましょう

# 最終的には
- いろいろありましたが、MySQLは便利だし、フルマネージドデータベースPaaSを使ったので運用は比較的楽でした
- データベースはアプリケーション、インフラ、保守運用、マネジメント等多岐の分野に渡ります。
自分一人で全てができるわけではなく、いろいろな立場の方の理解協力が欠かせません
  - それでもできるだけ開発チームとDBチームは内製の方が横断的に取り組みやすいかもしれません
  - データベースは業務的要素もあるので、『インフラは入れ物の構築』という感覚のお付き合いは避けた方が無難かと思います

ここまで読んでくださりありがとうございます。
良いお年を。