はじめに
担当システムのブログ投稿機能がありまして、お客様からブログが投稿できないというお問い合わせをいただきました。投稿ボタンを押しても反応がないという風にご連絡をいただきました。原因調査のため、ブログの投稿ボタンを押した際、Chromeの開発者ツールのネットワークタブでレスポンスを確認したところ、500エラーで返却されておりました。さらに詳細な原因を特定するためにAWSのログを確認したところ、エラーメッセージ「Rack::Multipart::MultipartTotalPartLimitError (Maximum total multiparts in content reached)」に遭遇しました。
使用技術
Rails 5.1.6
Vue.js 2系
AWS(EC2、RDS、S3、Cloud Watch等)
原因
担当システムのブログ機能にはカテゴリやタグを設定できます。ブログの投稿ボタンを押した際、フロントエンド→バックエンドへのパラメータに過去から現在まで作成していたタグを全て送信していたことが原因でした。
以下のコードでパラメータの数をチェックしており、デフォルトだと4096までになっておりました。それを超えるとエラーメッセージ「Rack::Multipart::MultipartTotalPartLimitError (Maximum total multiparts in content reached)」を出力されるようになっておりました。
一時的な対応
ユーザ様が相当お急ぎで何度も弊社に問い合わせの連絡が来ておりましたため、とにかくブログを投稿できるようにする必要がありました。現在ではパラメータの制限がデフォルト値でしたため、それを一時的に制限解除するようにしました。
以下を「config/initializers/multipart_part_limit.rb」を新規で作成して、以下を定義しました。そして、定義後に本番環境のプロセスを再起動しました。(githubのコードを見ると、今回のエラーメッセージではmultipart_part_limitの制限は解除する必要はなかったかもしれませんが、、)
Rack::Utils.multipart_part_limit=0
Rack::Utils.multipart_total_part_limit=0
ちなみにですが、envファイルに上記の定義を追記して、プロセス再起動をしてもうまく機能しなかったため、initializersの下にファイルを作成しました。
恒久対応
ブログの投稿ボタンを押したタイミングで今まで作成していたタグをフロントからバックエンドに送信する仕組みの見直しました。投稿ボタンを押したタイミングでバックエンド側でタグの新規作成、削除、編集、記事とタグの紐付け処理などを一度に行なっておりました。上記のような実装にしていると以下のような問題がありました。
- コードが複雑になってしまい、可読性が低く、バグが発生しやすい。(過去にもこの処理がバグっており、ブログが投稿できないとお客様から問い合わせがありました、、)
- タグを作成や編集、削除などの処理をした後にブラウザをリロードすると、更新処理が全て消えてしまいます。投稿ボタンを押さない限り、タグの変更が反映できない仕組みなっていたため。
上記の問題を解決するため、タグを作成、削除、編集などを行なったら、それぞれの処理をバックエンド側で即座に処理するようにしました。Rails側でタグを作成、削除、編集などを行うためのAPIをそれぞれ実装しました。また、Vue.js側でもタグの更新処理を行なったら、即座にAPIを呼び出すようにしました。
今回の対応でお客様が意外にもタグを900件以上作成していたことも知れたため、Rails側のパフォーマンスにも気をつけて実装しました。具体的には以下です。
- N + 1が起きないように実装。DBからのデータ取得の際にpreloadを使用して、事前に読み込みを行いました。
- ブログ記事とタグの紐付け件数をフロントエンドで使用していたため、countメソッドではなく、sizeメソッドを使用。
- タグの更新処理を行なったら、タグ全量をフロントエンドに返却するのではなく、差分だけを返却するようにしました。(新規作成したタグのみ、編集したタグのみをフロントに返却。)
学び
今回は技術的に学びが多い障害対応でした。フロントエンドからバックエンドへのパラメータ数に制限があることは今まで知りませんでした。Rackというのがパラメータ数のチェックをしていることも今回初めて知りました。まだRackに関する深いところまでは理解できていないたため、今後学習していこうと思いました。
また、設計が本当に大切だと身にしみて感じました。設計が良くないと可読性だけでなく、品質面でもパフォーマンス面でも良くないコードになってしまうことを学びました。今回修正したコードは私が実装した箇所ではなかったものの、アンチパターンを知ることができてとても勉強になりました。