その検証を完了してから、web api サーバーの可用性検証に取り掛かる。シェルスクリプトで数十から数百リクエストを並行にリクエストして、そのときのサーバーの状態を監視したりしながら、id 連携の処理が正常に動くことを確認する。mongodb のトランザクション導入 により、同じ主キーのドキュメントを並行に更新しようとすると WriteConflict エラーが発生する。これは仕方がない。同じコレクションであってもキーが異なればトランザクションのエラーは発生しないことを確認できた。メモリリークもみられない。web api サーバーの大半は私が設計して開発しているのでこんなレベルで問題にならないはずだが、やはり検証して確認できると安心できる。
api ハンドラーがメッセージを管理するジョブ情報を mongodb に保存する (この時点では未コミット)
api ハンドラーがトランザクションをコミットする
api ハンドラー内の producer が rabbitmq にメッセージを送信する
consumer がメッセージを受信する
consumer が mongodb からジョブ情報を参照する
api ハンドラーがレスポンスを返す
consumer がメッセージを処理する
これは単純にトランザクションのコミットタイミングと mq へのメッセージ送受信のタイミングを見直せばよいという話しではない。本質的に mongodb で管理しているジョブ情報と rabbitmq へ送信しているメッセージの整合性を保証することはできないということを表している。producer はメッセージ送信に失敗する可能性があるから、そのときにジョブ情報を書き換える必要はあるが、その前にトランザクションをコミットしてしまっているため、api ハンドラー内でデータのコミットタイミングが複数になってしまう。トランザクションを導入したメリットが失われてしまい、Unit of work のパターンも実現できない。consumer の処理に必要な情報を mongodb にあるデータとメッセージの2つに分割しているところが整合性の問題を引き起こしている。アーキテクチャ上の設計ミスと言える。consumer の処理に必要な情報はすべてメッセージに含めてしまい、メッセージを処理した後に mongodb に結果を書き込むといった設計にすべきだった。
初期実装のときからジョブ情報を mongodb で管理する必要はあるのか?という懸念を私はもっていた。要件や機能が曖昧な状況でもあり、メンバーもなんとなく db に管理情報を残しておいた方が将来的な変更に対応できて安心といった理由だったと思う。当時は整合性の問題が起きることに、私が気付いていなかったためにこの設計を見直すように強く指摘できなかった。トランザクションを導入したことで consumer が必要な情報を db に保持すると、db とメッセージ処理のタイミングにおける整合性の問題が生じるという学びになった。
いまとなってはこのジョブ情報を使う他の機能もあるため、この設計を見直すことはできない。今後の開発プロジェクトで db とメッセージを扱うときはこの経験を活かすためにふりかえりとして書いておく。