レガシーシステム移行プロジェクトから学んだ教訓
はじめに
私のキャリアでは今まで新規開発のITプロジェクトに関わることが多かったです。そのため、いわゆるレガシーシステムと呼ばれる古いシステムに関わる機会は少なかったのですが、去年からレガシーシステムの代替を担うプロジェクトに関わる機会を得ました。
レガシーシステムに実際に関わる前は、「今はAIの補助もあるし、古い技術の使い方に適応すれば問題ないだろう」という風に正直に言うと甘く見ていたところはありました。ただ、実際に関わってみると、古い技術の単なる適応だけでなく、ドキュメント不足や仕様の不明確さなど、予想外の課題が多く存在することに気づかされました。
良い機会なので、レガシーシステムと実際に向き合って学んだことを綴っておきます。
一言で言うと、考古学の発掘のような新鮮な体験でした。
今回関わったレガシーシステム
まず、今回関わったレガシーシステムはCOBOLやメインフレームといった"真性の"レガシーシステムではありません。
それでも、10年以上に渡って使われてきたシステムであり、最近ではあまり使われない技術を含む点で、広義のレガシーシステムと言えると思います。
今回携わったプロジェクトは、そのようなレガシーシステムを新しいシステムへ置き換えるための窓口となる API の開発です。数万ユーザーを擁するサービスではあったので、並行稼働による移行期間を担うような設計の、いわゆるゲートウェイと呼ばれるタイプの代物です。
移行先のシステムは、最先端というわけではなかったのですが、それでも AWS というクラウドサービス上で動作する REST API という、比較的新しいサービスでした。
対して、移行元となるレガシーシステムはオンプレミスのサーバー上で動作するシステムで、TCP Socket 通信で固定長のデータをやり取りするという、最近ではあまり見ない代物でした。
何が起きたか
さて、私の記憶があやふやになる前に、実際にどのような問題に直面したのかを綴っておきましょう。
端的に言えば、TCP Socket 通信などの最近は触ることのない技術をどうにか扱えば、このプロジェクトは問題なく進むと甘く考えていました。分からないことがあってもレスポンスやログ出力から調査可能だろうし、最近は AI に頼ることもできると考えていました。
ただ、実際には、得られる情報があまりにも少なく、泥臭い作業で情報をかき集める必要がありました。
仕様書が無い・仕様を分かる人間がいない
予想していたことではありますが、レガシーシステムの仕様の把握が困難でした。完全なブラックボックス状態で、仕様を分かる人間がいない上に、データ送受信のまともな仕様書が存在しませんでした。
そのため、現行でこのシステムと通信しているシステムのソースコードを提供してもらい、どのようなインターフェースでデータのやり取りをしているのかを調査する必要がありました。
ソースコードが仕様書という状況は褒められたものではないですが、10年以上メンテナンスされていないシステムだったので想定の範囲内ではありました。
疎通確認が行えない
さて、通信するためのコードが完成したので検証環境で疎通確認を行おうという話になったのですが、次なるトラブルがありました。
10年以上に渡って使われてきたシステムで、その期間に他のシステムと新規に接続する機会が少ないシステムだったおかげで、簡単に接続ができませんでした。
オンプレミスのサーバーとGoogle Cloudの検証環境の接続にCloud Interconnectを使っていたのですが、既存のネットワーク設定が複雑で、既存のインフラやネットワークの調査に多くの時間と労力が必要になりました。結局、接続できるまで数ヶ月かかりました。
原因不明のコネクションタイムアウト
ようやく通信ができたと思ったら、今度は原因不明のコネクションタイムアウトに悩まされました。TCP Socket 通信ということもあり、サーバー側で何か起きていてもクライアント側にエラー内容が伝わってこないという問題もありました。
レガシーシステム側のログを提供してもらったものの有益なログは存在せず、仕様について把握している人もおらずで、タイムアウトの原因はしばらく分からずじまいでした。結局、既に通信に成功している既存システムとの比較を延々と繰り返す泥臭い作業が必要になりました。
パケットキャプチャなどでデータを比較する中で、まず最初に気付いたのは TCP ヘッダーのサイズの違いでした。なんと Windows 由来の TCP ヘッダーとLinux 由来の TCP ヘッダーはサイズが違います。新しく開発したシステムは Linux マシンのイメージを K8S で動作させていたのですが、既存のシステムは Windows マシンで動作していました。これが原因かと思い、Windows サーバーから通信を行ったり、TCP ヘッダーのサイズを調整可能にしたりしたのですが、結局これは原因ではありませんでした。
結論を言うと、原因は送信データのエンディアン・バイトオーダーの違いでした。既存のシステムからのパケットと新システムからのパケットを比較していたところ、細かいデータの差異があることに気づき、リトルエンディアンを使っていた箇所をビッグエンディアンを使うようにしたことで解決しました。正直、エンディアン・バイトオーダーという単語を2020年代のモダンな開発現場で聞くことがあるとは思いませんでした。
AI は役に立ったのか
今回の開発プロジェクトはAI使用に関する制約が無かったので、色々とAIを活用させていただき知見を蓄えることができたのですが、もちろんレガシーシステム由来のトラブル解決にも活用していました。
結論から言うと、今回のケースではAIは部分的には役に立ちましたが、根本的な問題解消の助けにはなりませんでした。
「エンディアン・バイトオーダー」などの知識面は助けになりました。ただ、コネクションタイムアウトの原因解明という根本解決については、色々と提案をしてもらったものの解決には結び付かなかったです。
例えば、「エンディアン・バイトオーダーの違い」という原因の解明では、AIと協働して新規と既存のコードレベルでの比較分析を行いましたが原因を見つけられず、実際の通信データの挙動を観察する泥臭い作業なしには根本的な原因を特定できませんでした。
ドキュメントや仕様の詳細、十分なログデータなど、AIに提供できる情報(コンテキスト)が限られていたことが決定的な要因だったと考えています。
それでも、テストコードの生成など開発に関わる生産性の改善には大きく役立っています。情報が少ないレガシーシステム側の解析には少し難があったようです。
教訓
結局のところ、レガシーシステム側のドキュメント整理やエラー時のログ出力が不十分だったという点に尽きるとは思います。そして、そのようなドキュメントの不備などが後にそのシステムを使う人にとって大きな課題になるということを身を持って学べました。
個人的に、普段調べることのない「TCP ヘッダーのサイズ」・「エンディアン・バイトオーダー」などに触れる良い機会ではあったのですが、このようにして生産性は下がってしまうんだなという方が実感として大きかったです。
最近は OpenAPI などに API 仕様書を依存している状況も多いですが、文章としてのドキュメント整理やエラー原因を特定できるログ出力など保守性を考慮しないといけないと改めて思いました。
今後気をつけるべき点
- ドキュメントは書こう
- コードから自動生成できる部分(OpenAPIによるAPI仕様書)と人間が記述すべき部分(設計思想やトラブルシューティングガイド)を区別し、両方をバランスよく整備する。
- 標準化されたプロトコルを使おう
- HTTP や GraphQL などの広く知られたプロトコルを使う。
- どうしても低レベルな通信が必要な場合は、gRPCなどの標準化されたフレームワークの使用を検討する。
- TCP Socket 通信による固定長データのやり取りなど標準化されていないプロトコルの使用は避ける。
- トラブルシューティングのためのログ出力は実装しよう
- エラーが発生した場合は、最低限エラーの内容をログ出力する。
- リクエストIDなどのコンテキスト情報をログに含めることで、関連するログをトレースしやすくすると助かる。