こんにちは。社内のJava利用率を少しでもあげたいと常日頃から考えている、Javaラーのカンノです。プラットフォーム開発グループに所属しています。
Javaと聞いて、ぐるなびの開発は「PHPメインでしょ?」と思われる方が多いようですが、実は社内でもJavaで開発されたシステムは多々あります。個人的にはJava推しで、実際にPHPのシステムをJavaでリプレイスしたら性能が劇的に良くなったので、今回紹介するのはその話です。
ぐるなびエンジニア内のJavaの扱い
ぐるなびの開発エンジニアの中ですら「社内でJavaを使っているところがあるの??」と言われるほど、ぐるなびではPHPが主流となっています。たまにシステムのリプレイスや新規開発でGo、Rubyを使うと社内で話題に上がったりしますが、いざJavaで開発するとなっても、そこまで話題にすら上がりません……何故なのか……Javaラーとしては日陰者の気分です……。
※ あくまで個人的な意見です!社内のJavaエンジニアが冷遇されていることは一切ないです。
実はJavaのシステムは沢山ある
弊社では8〜9割のエンジニアがPHPメインで開発していますが、私が所属するプラットフォーム開発ではむしろ大半の人がJavaで開発しています。我々プラットフォーム開発者は、様々なコンテンツが共通で利用するシステム(ユーザーの個人情報管理、FAX、決済、メール、ポイントetc.)などを開発していますが、実は多くのシステムはJavaが開発言語に選ばれています。
ぐるなびプラットフォームではJavaを利用している場合が多い
ぐるなびでは個人情報管理、決済、ポイントなどの高セキュリティが求められるシステムは、全てJavaで作られています。また、プラットフォームが提供する共通システムは、様々なコンテンツから利用されるシステムでもあるため、大量のアクセスや、シビアな処理速度が求められるものが多いです。そのため各システムには少しでも多くのアクセスを捌くための工夫や、レスポンス速度を少しでも早くする様々な工夫がされており、それらの開発にマッチする言語がJavaでした。
Javaの特徴と選定理由
Javaのよいところの1つに、メモリ管理が楽なことがあげられます。JavaではJava仮想マシン(JVM)にどのくらいのメモリを割り当てるかを意識するのに対し、CやC++などの言語ではメモリ管理は自分でおこなう必要があり、必要に応じてシステムに問い合わせてメモリの確保や解放を行わなければなりません。また自分でメモリ解放をする必要がある言語では、慣れない人がコーディングをするとメモリリークが発生し、発狂したくなることもあります。そのほかにもJavaではプログラムを最大いくつ並行で実行できるかを決めるスレッド数の設定が行えたり、そのスレッド数を多く生成するためにスタックサイズの調整なども行えるのが特徴です。
FAXシステムをJavaでリプレイスしてみた
この時代に意外に感じるかもしれませんが、ぐるなびではFAXは必要不可欠な存在なのです。馴染みがない方が多いかもしれませんが、飲食店業界などの現場では「本日の予約状況を紙でみたい!」、「予約は電話かFAXでもらえると助かる!」などの声がまだまだあるため、FAXは当分なくなりそうにない存在です。
リプレイス前のぐるなびのFAXシステムの状態
ぐるなびのFAXシステムは2009年に作られたものであり、言語はPHP、フレームワークは当時人気のあった Zend Frameworkを使って実装されていました。そして度重なる無理な仕様追加、機能拡張などによる量産されたレガシーコード、解読不能なバッチ、システム構成自体がボトルネックとなり性能が上がらなくなっているなど、リプレイスをする理由は沢山ある状態でした。
問題箇所の洗い出し
大きな問題点は以下3点です。小さな問題点にBatchがカオス、管理画面が古い、仕様が複雑などがありましたが、それらは段階的にリリースしていく方針にしました。
1:通信プロトコル
APIのプロトコルにSOAP通信が利用されており、IFの拡張がある度にXMLの更新を行い、FAX利用コンテンツにキャッシュされているサーバのXMLを削除するなど、運用コストが中々に高い状態でした(そもそも今の時代にSOAP通信……)。そのため、サービス側からのリクエスト受付をREST APIへ変更します。
2:PDF送信を行えるようにする
送りたいFAXのデータは、パーツ単位(タイトル、見出し、本文、フッターetc.)でパラメータをもらい、決められたレイアウトの場所にテキストをマッピングしていました。そのため、FAXレイアウトの追加や変更があるたびに新規のIFの口を作成・レイアウトマッピングの作成などが発生していました。 そもそもFAXでレイアウトが固定される意味もないので、サービス側が好きにFAXのレイアウトを決めれるよう、PDFを受け付けれるように変更します。
3:FAX送信のcron管理をやめる
送信用サーバがcronで1分間隔で溜まっているFAXをまとめて送信する仕組みでした。そもそもcronに頼る必要がないため、APIリクエストを受け取ったら即時送信するように変更します。即時でFAX送りたくない場合も想定し、パラメータで配信予約ができる機能も導入します。
作ってみたらシンプルになった
まずは出来上がったシステム構成をご覧ください。
格段に構成や処理がシンプルになりました。JavaでリプレイスしたFAXシステムでは主に「入出力」、「データ管理」、「通信処理」の3層に分け、それぞれ役割に応じて並列処理させるようにしていきました。
並列処理にAkkaを選択
今回、並列で実行される処理はどれもシンプルで軽量であること、処理完了時の結果などを場合によっては管理する、トランザクションの実行とロールバック、実行元からパラメータを簡単に受け取りたい、などの理由から並列処理の実装としてAkkaを採用しました。
Akkaは以下が分かりやすく、実装しやすいです。
- アクターモデルによるプログラミングのためのAPI
- アクターを実行するためのメッセージ送信の
tell
- メッセージ送信し結果を受け取る
ask
などのコマンド
またAkkaには障害耐性があり、以下が容易におこなえます。
- アクターの処理中に例外が発生した際に再実行の設定
- エラーのハンドリング
また、異常終了した場合の挙動などが柔軟に設定できるのも特徴です。
性能比較
APIのレスポンス返却時間
最小値(ms) | 最大値(ms) | 平均(ms) | 中央値(ms) | |
リプレイス前 | 151 | 5012 | 480 | 422 |
リプレイス後 | 2 | 100 | 30 | 29 |
FAX送信APIのリクエストを投げてから、レスポンスが返ってくるまでの時間は圧倒的にリプレイス後のほうがよくなり、最小値で比べると約70倍近く性能差が出ました。この結果は単にAPIの入り口で遅延することがなくなり、より多くのリクエストを受け付けられるようになっただけでなく、今後FAXシステムの利用者数が増えた場合でも1台のサーバで捌けるリクエストが増えたため無駄なサーバ増設をしなくてもすみ、結果的にサーバ費用の削減にも繋がります。
FAXが送信されるまでの時間
APIのリクエストを受け取ってから、実際のFAXが送信されるまでの時間比較です。
- リプレイス前……約1分(cronで1分単位で実行していたため)
- リプレイス後……約 50〜120ミリ秒
これは比べるのも気が引けますが、JavaでリプレイスしたシステムではAPIのリクエストを受け取ってからFAX送信までを、ほぼリアルタイムで処理されるようになりました。
APIの捌ける数
PHPで作られている旧システムの許容量の測定は「API受付け」、「送信サーバ」とで分かれているので厳密な測定は行えませんが、JavaでリプレイスしたFAXシステムではPHP時代のシステムが繁忙期の時期に捌いていた1日のFAXの通数を、わずか3分間(1台のサーバ)で捌き終えるポテンシャルを持っています。これはある意味オーバースペックになってしまいましたが、今後機能が増えても問題ないくらいのシステム基盤が出来上がりました。
Javaでリプレイスしてみて
正直、今回のJavaでのリプレイスやシステム構成の変更など、だいぶ好き勝手にさせて頂きどうなるか不安でしたが、結果的にみればシステムがシンプルになり、更に性能面でも格段に良くなったのでホッとしています。まだまだぐるなび社内ではJavaエンジニアは少数ですが、これから頑張ってJavaラーの同志を作っていきたいと思っています!
個人的にJavaエンジニアの増加を願う
私に人事採用権があるわけではないので、「Javaエンジニアを募集しています!」と言うわけにはいきませんが……社内でJavaエンジニアの数はまだまだ少ないので、これからのぐるなびを支えていくためのJavaエンジニアが増えていくことを願っています。
Javaが好きな方、Javaに興味がある方、Javaに熱い想いを持っている方などがもっと増え、熱いディスカッションを重ねながら楽しい開発を一緒にできることを夢見ています。
お知らせ
ぐるなびでは一緒に働く、物作りに対して熱いハートを持った仲間を募集しています。