ソフトウェアプログラムの処理性能において速さは絶対善です。速さは最重要指標と言ってもいいでしょう。簡単で小さなデータを扱うプログラムであれば、昨今のCPUや潤沢なRAMを搭載したマシンなら、処理速度に辟易することは少ないかもしれません。しかし巨大なデータを扱うプログラムの場合、そのアーキテクチャ(基本設計や設計思想)によって歴然とした処理時間の差が出ます。
経験の浅いソフトウェアエンジニアの場合、仕様要件を満たすことで頭がいっぱいになってしまい、処理速度の考慮が二の次になってしまうことがあります。今回は、私が入社半年後に、初めて一人で任されたサブシステムの開発での経験です。そこで私が突き当たった壁(処理時間の改善)をどうやって乗り切ったのか。拙い経験ではありますが、参考になればと思いお話します。
開発テーマはデータ移行ツール
私が任されたサブシステムは、ある旧システムからシステム刷新のため、新システムへのデータ移行を行うものでした。今ではあまりやらないことですが、SQLを組み合わせたプロシージャ(複数の処理位を一つにまとめたプログラム)を作成し、順番にバッチファイルで起動し、データベース間で、データ変換、移行処理を行うものです。
当初、自分的には簡単なプログラムだと考えていましたが(新人にありがちなやつですね・・・)、ただ単にデータを移行するということではなく、運用保守の面、ユーザビリティを考慮すると、処理時間が大きなネックになることに気づきました。
着手時は、本当に単純なルールで変換することで実現できるだろうと安易に考えていました。しかし、作業を進めるほど、そう間単にはいかないということが分かり、プログラム処理時間の長さという壁にぶつかりました。処理時間が長いということは、ユーザビリティが悪い(ユーザーストレス)のはもちろん、デバッグ中にも言えることですが、何か問題があったときにやり直しが簡単にできないという問題があります。
処理時間が長くなる原因
処理時間が長くなる理由には、大きく下記の2点がありました。
(1)旧システムのデータ仕様が不規則な上にパターンが複数ある
旧システムでは、ユーザが自在にフォーマット編集やルール決めができるようになっており、任意なデータを作成することが可能。したがってそこから全てのユーザのデータを新システムに移行可能にできるように、全てのデータベーステーブルに様々な変換処理プログラムを埋め込みました。
そのため実際走らせてみると、条件分岐が多重に発生し、その処理時間が予想以上にかかっていたのです。また変換処理時間だけでなくその前の、旧システムのデータ仕様のインポートにも時間はかかっていました。
(2)移行対象データが膨大
レコード数が数百万になるテーブルで、かつテーブル数も100以上ありました。旧システムでの運用レコード数が大きいため、単純に条件を組んで処理を行おうとすると、処理時間がレコード数に比例して長くなります。デバッグ時には、サンプルレコードで数を絞って行っていたので、さほど気に留めていませんでしたが、初めての結合テストでは、実に半日以上かかり唖然としてしまいました。
このことにより、新システム側のデータベース設計の見直しで挽回できないかと考えたのですが、データ変換仕様という面では既にコミットしていたため、テーブルインデックスなどの見直しに留まり速度改善には至らず、1年目のエンジニアとしては、決定的な次の手を思いつくことがすぐにはできませんでした。
発想を逆転してみる
その時は、もうこの現状はどうしようもできないと思っていましたが、そのうち、どうしようもないのであれば発想を変えて、なんでも試してみようと思い、テーブル分割およびテーブルを追加、つまり処理の分散ということに挑戦してみました。
普通に考えると、処理を分けたり増やしたりすれば、それだけ手間が増えるわけですから、むしろ時間がかかってしまうように思えます。なので当時の自分も、処理の分散は可能だが、プログラムは増えるし、分散後の結合もあり、テーブル分割処理や、テーブル追加では効果が出ないだろうと考えていました。しかし実際試してみたところ、処理時間を約10%減ぐらいまで追い込むことができました。
冷静になってよく考えてみれば、旧システムのデータが、数百万レコードあるので、テーブルアクセスするだけでも処理時間がかかります。なので事前にテーブルを分割し、アクセス処理を分散すれば、それだけで処理時間が明確に変わることに気付きました。
それに気づいてからは、改善のスピードは速くなり、全テーブルの見直しなどを進めることができました。さらに作業を進めると、もう一つ改善ポイントが見えてきました。
それは、SQLでは、複数のテーブルを結合し、抽出することができることです。さらにそれを複数段階で結合することが可能です。初めは、データ変換、移行のルールに則り、単純にSQLを書いていましたので、テーブル結合のオンパレードとなっているプロシージャを作成していました。
そうすると、そのプロシージャは、データベースのテーブルにアクセスしているように見えますが、実は、SQLの処理結果(クエリ)にアクセスしており、いわば仮想テーブルのようなもので、実テーブルではありません。そのため、単純なSQLで書いたプロシージャと同じレコード数で比較しても、処理時間が大幅に増えていました。私はこの点にも着目し、テーブル分割、中間テーブルの設置を行うことで、最終的には、処理時間を約30%減まで達成することができ、なんとか仕事を終えることができました。
答えはだいたい「反対側」
新人のときには、仕様通りに作成することにだけに着目(仕様通りなので見直し必要がない)し、処理時間まで頭に入っていませんでしたが、この経験で、プログラムの組み方には、あらゆる方向からの目線での検討が必要ということを痛感しました。
プログラム追加=処理時間が増える、と考えがちですが、処理分散などの着目で劇的に変わるかもしれません。これはSQLだけでなく、他のプログラム言語でも同様だと思います。開発で壁にぶちあたったときにこそ、一旦リセットし、発想の逆転など、冷静に第三者的に見ることが有効です。
煮詰まってる時は、目線が一方向に向いてしまっており、自分にその自覚がなかったりします。そんな時の答えはだいたい「反対側」に落ちていたりしますが、一人だとなかなかそれに気づかない。気づくとしてもそれなりの時間が必要だったりします。なので開発は一人ではなく、実装前の設計・レビューから組織(チーム)で進めるべきと思います。気づきが早いということは、開発の手戻りも少ない(=開発のスピードアップ)ということですので。ぜひ若手開発者のみなさんには、このことを心がけておいて欲しいなと思います。


