処理は速い方がいいし、計測はこまめにやった方がいい。

2023/08/17に公開

はじめに

この記事では まさき。(kuroneko913) がNE株式会社でのネクストエンジンに関する業務中の気づきを書いてみたいと思います。

処理は速い方がいいよね?

エンジニアは誰かの課題を技術も使って解決するのが仕事であり、そこがやりがいであると個人的に感じています。そして、課題解決をしていくと必ずと言っても良いくらいぶつかる壁が「もっと速く処理をしたい」、というパフォーマンスの壁だと思っています。

インフラ面で見ても処理時間が長すぎるとAWSのインスタンスの使用料金の増加に関わってきますし、当然ユーザーからしても処理は速いに越したことはありません。

ネクストエンジンでの処理高速化の一例

弊社のネクストエンジンはさまざまなネットショップからの注文をいちいちモール・カートの管理画面に入らずとも管理できるようにするというコンセプトのプロダクトです。
各モールやカートからAPIなどで取り込んだ注文を、その注文に必要な作業(例えば、住所と郵便番号の組み合わせが異なるから注文者に追加で確認する必要がある、など)ごとにステータスに分けて管理しています。
受注ステータス管理
受注ステータスによって必要なことをやるのがイメージ

ユーザーからの要望

実際にネクストエンジンをご利用中のお客様からのお問い合わせやご要望で一時期多かったのが、上記ステータスの「印刷待ち」への遷移が遅いことでした。印刷待ちは出荷するために納品書(よく箱に入っている購入した商品と金額が書いてある紙)を印刷できる準備が整ったよ、出荷準備ができるよ、というステータスです。
他のほとんどのステータスは画面からの操作と同時に遷移するのですが、ここだけは5分に1回行われるバッチ処理の中でのみ遷移するようになっていました。

その5分というのも一刻も早くお客様に商品を発送したいユーザーや倉庫等へ出荷依頼する時間が迫っているユーザーさんにはクリティカルなものでした。
実際、NE株式会社のネクストエンジンを使用して自治体のふるさと納税を支援する事業、ロカルコからも同じような声が上がっていて改善に踏み切るには十分すぎる状況でした。

対応

実際にやったこととしては、バッチでやっていた処理を見直しました。
そもそもその処理には必要十分なテストケースがありませんでした。例えば、ステータス遷移してはいけない注文が遷移しないこと、ステータス遷移が可能な伝票のパターンを網羅したテストがありませんでした。バッチ処理ということで多くのユーザーに関わる処理であるのでバグると大変なのでまず、テストコード書いて仕様を把握するところから始めました。
そこでまず機械的に想定されうる伝票パターンを生成し(41472パターン)、そこから実際に存在しうる伝票だけを洗い出すという作業を目視で行い、最終的に6272件の注文パターンをFixtureに用意しテストを追加しました。


実在しうる注文データを洗い出してFixtureとして切り出す

それから処理対象の注文を取得するクエリの最適化を行い、もともと1件ずつ処理していたものだったものを一括で処理できるように修正しました。これによりバッチの実行時間も少し速くなりました。
さらにこれをモジュールに切り出すことで、取り込まれた注文を確認する受注伝票画面やネクストエンジンが外部へ公開しているAPIの注文処理への反映、など即時でステータス遷移して欲しい箇所に簡単に処理を埋め込むことができるようになりました。


モジュール化して切り出すイメージ

この改善はお気づきの通り、処理そのものの高速化(クエリを見直したり、一括処理するようになったので副次的に効果はあったが)というよりは、バッチでやっていた処理を画面からもできるようにするという、構造を変更することによる高速化という側面が大きかったです。「速い」の提供方法にもいろいろあるんだな、と気付かされた案件でした。

よかったこと

次の顧客満足度調査で速くなった、改善してくれてありがとうと言った声をいただけ、大変嬉しかったのを覚えています。

また、意外だったのがネクストエンジンの導入を支援するチームも困っていたことです。ステータス遷移を実演する際に「確認待ち」から「印刷待ち」に遷移するところだけちょっと待っててと言わないといけなかったのが、その場で確認チェックを入れるだけで遷移を実演できるようになったという声もいただいています。

実装後に検証環境にデプロイして動作検証をします。この時にステータス遷移をさせないとテストできない機能もあるため、それが画面からサクッとできるようになって開発者検証や検証者による検証も楽になったと個人的に感じています。

このように、インフラにとっても、ユーザーにとっても、場合によってはサポートや開発者にとっても「速い処理」というのは求められるものです。

DBへの保存を高速化した

現在関わっているPJでも実は高速化が必要になりました。今まであった注文からとあるオブジェクトを作って保存するというバッチ処理での話です。
ネクストエンジンに登録されている今までの注文を全件処理する必要があり、単純にDBに保存するオブジェクト数が多いので1件ずつ処理していくと単純に時間がかかってしまうので一括で処理しようということになりました。

もちろん、「推測するな、計測せよ」って言葉がある通り、このようなパフォーマンス改善は、実際の実行時間を計測し、ボトルネックとなる処理を特定し、改修するというステップを踏みます。この時もログを仕込んだり、New RelicのAPMを確認して確かにDBへの保存処理がボトルネックになっていることを確認してから改善に着手しました。

様々な課題を乗り越えて一括でDBにオブジェクトを保存することができるようになった時にいざ実行時間を計測してみると、なんと、目論見通りDBに保存する処理の分は確かに高速化が果たされていました。が、全体で見ると10%程度の短縮に過ぎませんでした。

新たにボトルネックになっている処理はなんと、この対応と並行して実装されていた、同一視できるオブジェクトの統合処理で、実行時間の約90%がこの処理で占めていました。
このように実際に計測しながらやっていても、コードそのものが変わることがありうるので、コードが変わったらその都度計測するのが必要なのだと思います。


改修前後での実行時間の変化の内訳

この例によらず、常に課題は変化するものなので変化に追従していけたらいいなと思いました。

まとめ

処理を高速化することの必要性をネクストエンジンでの例をあげて紹介しました。
また、現在関わっているPJでDBへの保存処理を高速化する対応をしていたら、いつの間にかそのほかの違う箇所の課題に置き換わっていたという例を紹介しました。
この課題に関しても計測することで気づけたのもあり、やはり「推測するな、計測せよ」って言葉は正しかったんだ、と改めて思いました。

NE株式会社の開発ブログ

Discussion