この記事は Tech KAYAC Advent Calendar 2020 の6日目の記事・AWS & Game Advent Calendar 2020の11日目の記事です。
こんにちは、バックエンドエンジニアの @commojun です。
今回は、最近業務でがんばったことを書きたいと思います。ちなみに、去年のアドベントカレンダーでは、死んだ猫を蘇らせようとしたりしていました。ちなみに蘇った猫ちゃんは現在このような姿になりました。
記事の概要
- 6年続くサービスのPerlのバージョンを5.16から5.30へバージョンアップさせようとしています
- 文法が変わってコンパイルエラーが起きたり、テストが運で落ちたり通ったりするようになりました
- 問題を解決するためにOSSにPullRequestを書く経験をしました
- 我々の作るサービスは、モジュールを組み合わせ、うまく調整した上で成り立ってるんだなあと実感しました
背景
弊社のサービスは、クラウドプラットフォームとして主にAWSを利用しており、比較的長く運用が続いているサービスは仮想サーバーであるAmazon EC2上に構築されていますが、比較的最近立ち上げられたプロジェクトでは、コンテナベースでのシステム構築の潮流にのっとり、Amazon ECSを利用してコンテナベースのシステム構築を取り入れています。
私が従事している「ぼくらの甲子園!ポケット」は、2014年9月からサービスを開始し、おかげさまで6周年を迎えることができました。 当時はAmazon ECSというサービスはありませんでしたので、Amazon EC2上にAmazon LinuxというOSをインストールし、その上にPerl 5.16を使ってシステムを構築していました。そのシステム構成は大きく変わらず、OSと言語のバージョンはそのままに、調整を繰り返しながら6年にわたりサービスを継続させていました。
Amazon Linuxのサポート終了
Amazon Linuxは、2020年12月末でサポート期間が終了することを発表しています。我々のサービスも、Amazon Linuxに頼った構成を変更する必要性が出てきました。
ここで、AWSが推奨している後続OSであるAmazon Linux 2に乗り換え、引き続きAmazon EC2でのサービス運用を継続することも選択肢にありましたが、他プロジェクトでの実績や、コンテナベースのシステムのメリット等を鑑みた結果、Amazon EC2をベースとした構成からAmazon ECSをベースとした構成に乗り換えるという決断をしました。(テーマとしてはこちらの方が重大な話題なのですが、この話はまた別の機会にまとめたいと思います)
ついでにPerlのバージョンも上げよう
システム構成を大きく見直す機会は、他の部分にも大きな見直しを入れるチャンスでもあります。そこで、ついでにアプリケーションの実装言語であるPerlのバージョンを新しくしよう、ということで、サービス開始当時からそのままだったバージョン5.16から、5.30に一気にアップデートさせることにしました。Perl 5.16のリリースは2012年5月、Perl 5.30のリリースは2019年5月ですので、実に7年分もの更新を一気に行うこととなります。
Amazon ECSへの乗り換えは、まだリリースまでこぎつけていませんが、Perlのバージョンを上げる作業についてはある程度目処がついており、これが「ついでに」というにはなかなか大変な作業だったため、本記事では、このアップデート作業の過程で立ちはだかった出来事を紹介します。
プロジェクトの規模
先に、プロジェクトの規模感(記事執筆時点)を簡単に説明します。
項目 | |
---|---|
パッケージ数(lib/ 以下の.pmファイルの数) |
3,751 |
テストコード数(t/ 以下の.tファイルの数) |
2,814 |
Githubのコミット数 | 139,108 |
GithubのPullRequest数(Open・Close合算) | 41,972 |
Githubのcontributor数 | 84 |
多くの人が携わって、歴史も長い大きなプロジェクトであることがわかります。一人の人間が全てを把握するのはなかなか難しそうです。
Perl5.30で動くコンテナの作成
Amazon ECSを使ったコンテナベースの構成に乗り換えることと、Amazon Linuxから乗り換えることが決まっていたため、Perlのバージョンアップ作業は、「Perl 5.30でアプリケーションが動作するコンテナを作ること」をゴールに据えて作業を行いました。具体的には、Docker Hubで公開されているperl5.30-busterのコンテナイメージをベースとして、Dockerfileを1から作り上げるという作業です。
必要な要件を一通り揃え、ローカル開発環境でコンテナを実行してみたところ、perlのバージョンが5.16から5.30に上がったことによる問題がいくつか発生しました。
コンパイルエラーが発生した
そもそもコンパイルが通らず、アプリケーションのプロセスが立ち上がらないという箇所がいくつかありました。
これは主に、Perl5.24の変更点である「The autoderef feature has been removed」というものが原因で、keysやvaluesのような関数にハッシュリファレンスを与えた時、自動的にデリファレンスする機能が終了し、その機能に頼っていた箇所があったため、コンパイルエラーとなっていました。この問題をかかえたパッケージは50ほどあり、個々に正しい記法に書き直すことで対応しました。
言語側が空気を読んで融通を効かせてくれていた機能が、バージョンが上がることによって終了してしまったという事例です。
テストが運で落ちたり通ったりする
Perl5.30環境でCIを実行したところ、試行によって通るテスト、落ちるテストがランダムで変わるという謎な現象が発生しました。
これは、Perl5.18の変更点である「ハッシュのランダム化」が原因でした。
例えば、以下のようなコードを実行した場合
my %hoge = (a => 1, b => 2, c => 3, d => 4); my %hoge2 = %hoge; my %hoge3 = %hoge; print keys %hoge; print keys %hoge2; print keys %hoge3;
Perl5.18以前では、必ず同じ結果が得られました。
(例)
cabd cabd cabd
一方、Perl5.18以降では、試行によって毎回違う結果が得られるようになります。
(例)
cdba dabc cbad
プロジェクト内に、ハッシュの内部の順番が同じであることに頼った実装がいくつかあり、試行ごとにテストが通るケース、通らないケースがランダムで発生するという状況になっていました。こちらも地道に該当箇所を探し、問題を潰していきました。
利用しているモジュールのバージョンを上げる
Perl5.16のまま6年以上運用を続けていると、止む終えない理由で古いバージョンのまま固定しなければならないCPANモジュールが多く発生します。その結果、プロジェクトのモジュールはさながらタイムカプセルのようになってしまっていました。
Perl5.30へのバージョンアップに合わせて、各種モジュールも可能な限り新しいものへ更新しました。バージョンの数字を上げただけで案外動くぞ!というものも多かったのですが、一度問題にぶつかると、原因が根深いところにあるため、解決がなかなか難しかったです。特に苦労した例を1つ紹介します。
日本語を含んだJSONのリクエストを正しく受け付けられない問題
モジュールのバージョンを新しくすることによって、日本語を含んだJSONのリクエストを正しく受け付けられない問題は発生しました。この問題は、プロジェクト内のコードの問題というより、むしろCPANモジュール側が我々のような使い方を想定していなかったことにより発生した不具合でした。こちらは2つの問題に分けられ、それぞれ解決する必要がありました。
一つは、ネストされたJSONリクエストの中に日本語が含まれていた場合、正しくエンコードされないという問題でした。こちらは、HTTP-Entity-ParserというCPANモジュールに対してPullRequestを送ることによって解決をしました。
もう一つは、ネストされたJSONリクエストが正しくデコードされないという問題でした。こちらも、Plack-Request-WithEncodingというCPANモジュールに対してPullRequestを送ることによって解決しました。
PullRequestを確認し、対応してくださった@kazeburoさん、@moznionさん、ありがとうございました。
感想
Perlのバージョンを上げるという作業をおこなってみて、言語の歴史の変遷に少し触れることができ、また、我々の作るプロジェクトが多くの人のモジュールによって支えられているんだな、ということを認識できました。地道な作業もかなり多かったのですが、少しずつ古い汚れをきれいにしていく作業に思えて自分的にはあまり苦ではありませんでした。といいつつ、まだ本番リリースはこれからですので、リリースに向けて粛々と作業していきます。