リクルートのプロダクト開発チームが明かす、大規模システム改修・新規サービス開発の舞台裏──RECRUIT TECH MEETUP #1
アーカイブ動画
■登壇者プロフィール
株式会社リクルート プロダクト統括本部
飲食プロダクト開発G 早川 浩平氏
2016年にリクルートに新卒入社し、海外向け新規サービスのiOSアプリ開発を担当。その後『Airレジ ハンディ』のサーバサイドとフロントエンドの開発を経て、2019年から『Airレジ ハンディ セルフオーダー』の開発の立ち上げと推進に従事。
株式会社リクルート プロダクト統括本部
飲食プロダクト開発G 松尾 裕幸氏
2019年リクルートに新卒入社。『Airレジ ハンディ』の開発・運用・保守に従事し、インフラ構築やサーバサイド・Webフロントエンドの開発など幅広く担当。
株式会社リクルート プロダクト統括本部
美容プロダクト開発G 奥冨 匠氏
新卒でゲーム会社に入社し、オンラインゲームのバックエンド開発運用に関わり、2018年にリクルートに入社。入社後はSpringを拡張した横断ライブラリの開発運用に従事。現在はホットペッパーの美容クリニックプロジェクトにおいてバックエンド開発及びフレームワークの保守を行っている。
株式会社リクルート プロダクト統括本部
旅行プロダクト開発G 秋田 諒氏
2017年リクルートに新卒入社。1年間データエンジニアとしてデータ基盤の構築やデータ活用のプロジェクトマネジメントに従事。その後2年間新規サービス開発に携わり、バックエンドエンジニアとして全体設計や開発・品質管理などを担当。現在は『じゃらんnet』の開発を行なっており、プロジェクトマネジメントや開発、運用保守を担当。
失敗事例で学ぶ ─『Airレジ ハンディ』障害対応と大規模アップデート
最初に登壇したのは、2019年にリクルートに新卒入社して以来、POSレジアプリ『Airレジ』関連のシステム開発・運用・保守を幅広く担当している松尾裕幸氏。 今回のセッションでは「しくじり先生 ~開発環境インフラ構成編~」と題し、飲食店向けのSaaSプロダクト『Airレジ ハンディ』の開発環境やインフラ構成、その構成が原因で発生したと思われる3つの障害について紹介された。
【事例1】ツールの老朽化でバグ検知が無効化
<前提・障害内容>
『Airレジ ハンディ』の開発チームでは、CIによる自動テストやソースコードの静的解析を実施している。CI上でテストに失敗したり、問題のあるコードが検出されたりすると、プルリクエストに自動連携され、対処ができるように整備されている。
あるとき、サーバーから「Too many open files」というエラーログが出力される。lsofコマンドでJavaプロセスが開いているファイルを調査してみると、ファイルが大量に開いたままになっており、ファイルディスクリプタが枯渇していた。原因はI/Oの閉じ忘れだったが、本来であれば、静的解析で検出できるはずなのに、スルーされていた。
検出できなかった原因は、ツールのアップデートを怠っていたためである。静的解析ツールのバージョンが古くなり、新しいAPIの検出に対応できていなかったのだ。
<再発防止に向けて>
ヒューマンエラーは起こりうるもの。それをチェックするツールの導入は有用だが、古いツールを利用し続けるのはかえって危険なので、ツールのアップデートは徹底させる。
【事例2】ゾンビプロセス発生によるプロセスID枯渇
<前提・障害内容>
アプリケーションはコンテナ化されているが、コンテナ内でさらに子・孫プロセスを実行している箇所がある。例えばホールで受けた注文をキッチンに連携するために、その注文内容をキッチンのプリンターから印刷するためのデータを生成する処理などだ。
だが、印刷データ生成の処理に失敗する障害が起きてしまう。生成処理の直後に、psコマンドを実行すると、ChromiumプロセスがSTAT:Zとして残り続けている。これはゾンビプロセスと呼ばれており、放置するとプロセス立ち上げに失敗するようになる。
今回発生したゾンビプロセスは、親プロセスが子プロセスの終了を待たずに先に終了してしまい、子プロセスがプロセスIDを占有し続けてしまうというもの。通常は問題化しないのだが、コンテナ内にはinitプロセスが存在しないため、問題が表面化した。
<再発防止に向けて>
1コンテナ1プロセスの原則を守ること。今回は応急処置として、Chromiumの動作モードを変更することでゾンビプロセスの発生を解消したが、根本解決に向けてこれらの処理をコンテナ内から切り出し、子プロセスの実行箇所を除却することを検討。さらにinitプロセスを起動してくれるオプションの指定や、耐久テストの実施を行うことが挙げられる。
【事例3】デフォルト設定値の意図せぬ書き換え
<前提・障害内容>
インフラの構成は責務を小さく分割してコンテナ化し、相互に通信するマイクロサービス構成。GatewayはZuul(※)を使って実装。外部からリクエストが来た際は、まずGatewayコンテナが受け取り、そのリクエストパスで適切なサービスにルーティングする。
(※現在はSpring Cloud Gatewayを利用)
このクライアントの再接続数やタイムアウト時間は、デフォルト値をそのまま使っていた。あるとき、モニタリング強化のため分散トレーシングをすることになり、クライアントモジュールを入れ替える必要性が生じた。Zuulが内部で利用していたHttpClientをX-Rayが提供するデフォルト実装に入れ替えたのである。
リリースから数日後、システムが高負荷になった際に、徐々にリクエストが遅延滞留するようになる。最終的にGatewayサーバーが応答できなくなり、アプリケーションの全機能が利用不可能という大規模な障害となった。HttpClientを入れ替えた際に、デフォルト値が意図せず置き換わったことが原因だった。
デフォルト値の参照は、Conditional Dependency Injectionという仕組みが使われており、事前に影響範囲を厳密に予測することは難しい。そのため開発チームは反省点として、可用性に関わる設定値を明示化していなかったこと、パフォーマンスの監視が不十分だったことを挙げている。
<再発防止に向けて>
可用性に関わる部分の重要な設定値は、デフォルト設定に任せず設定ファイルに書き出し、明示的に指定する。それによって設定値に意味があることを明示。設定値に関するテストコードも記述することを決めた。
さらに、パフォーマンス監視およびその異常検知の仕組みも整備。パフォーマンス劣化の兆候をアラートで発行したり、定期的にパフォーマンスの状況を確認する習慣付けも行うこととした。
『ホットペッパービューティー』Swaggerによる生産性向上の取り組み
続いて登壇したのは、奥冨匠氏。『ホットペッパービューティー』の美容クリニックプロジェクトで、主にバックエンド開発やSpring有識者としてSpringバージョンアップやチーム内ライブラリに開発を担当している。
今回は『ホットペッパービューティー』に新ジャンルとしてリリースした美容クリニック予約サービス開発の際に、フロント開発チームとAPI開発チーム間のコミュニケーションコストをSwaggerを用いて削減した事例について語られた。開発メンバーだけで数十名という大規模プロジェクトである。大規模開発においてコミュニケーションが複雑化した際、どのような施策を実施したのか、その効果についても語られた。
アーキテクチャはフロントとAPIを切り分けたマイクロサービスとして実装しており、全てのコンポーネントがAWS上で完結。フレームワークはフロント、APIともにSpringBootを採用している。
Swaggerとは、OpenAPI SpecificationをベースにしたAPI開発のためのツール。Swagger Specと呼ばれる仕様を表現するファイルはJsonやyamlなどで書かれている。
Swaggerの導入事例として紹介されたのは、APIの定義からAPIに関する仕様定義書およびAPIの呼び出しに必要なクライアントモジュールの自動生成。API実装をする際にコントローラに対してSwaggerが提供するアノテーションを記載し、API仕様を定義することで、Swaggerスペックと呼ばれるAPI仕様定義ファイルを自動生成することができる。
そのファイルを使って、同じくSwaggerツール群であるコードジェネレーターで、APIを簡単に叩くことができるJavaのAPIクライアントモジュールを自動生成するという流れになる。
自動生成されたクライアントモジュール、Javaのクラスはそのクラスのメソッド1個1個がAPIに対応しており、フロントではそのメソッドを実行するだけで、APIを叩いて結果を受け取るまでの動作を1行で完結できる。
プロダクトコードからAPIクライアントモジュール、そしてAPIドキュメントの生成までの大まかな流れはこのようになる。
Swaggerのアノテーションが付与されたAPIプロダクトコードからコンパイルし、プラグインを使ってSwagger SpecであるJSONを生成。生成されたJSONからAPIドキュメントやAPIクライアントモジュールと呼ばれるJavaクラスを生成してコンパイル後に配布する。
CIのフローは以下のように、GitHubにプッシュしたときにJenkinsがAWS CodeBuildを実行し、ユニットテストを行う。その過程でAPIドキュメントとAPIクライアントモジュールの生成が行われる。
本プロダクトではMavenとそのプラグインである、Swagger Maven Pluginを採用している。その理由は、SpringFoxはランタイムでの生成であったため、Swagger Specの生成にSpringの起動が必要だった。CIに組み込むことを考えると、プラグインの方が利便性が高くそちらを採用した。またSwagger SpecからAPIクライアントモジュール及び、API仕様定義を生成するために別のMavenプラグインであるSwagger CodegenをCIに組み込んでいる。
導入して良かった点は、API呼び出しを完全にブラックボックス化したことで、実装やテストを行う必要がなくなったこと。CIにおいてインターフェイスの変更検知が出来るようになり、例えばリクエストのフィールドが追加された場合には、コンパイル時にエラーとして認識できるようになった。また、APIのドキュメントやAPIクライアントのJavadocにどうエラーレスポンスを返すのかが記載されているので、仕様確認が楽になった。
苦労した点は、当時プラグインがOpen API ver.3に対応されておらず、ver.2を採用したことによって、Enumなどの表現力が弱かったこと。立ち上げ初期はプラグイン設定のカスタマイズに追われたことなどが挙げられた。
インターフェース変更検知ができるようになったことで起きたコミュニケーションの問題もあった。APIチームがAPIリクエストの定義をを変更すると、CIがエラー落ちすることがあったため、変更する際はJIRAチケットを切るなど、共有を徹底することで改善したという。
APIチームにドメインに詳しい人が多かったことや、ドメインが複雑だったこと、曖昧性と アジリティのトレードオフなど、今回のプロジェクトの特性を踏まえ、奥冨氏は以下のようにセッションをまとめた。
長年の増改築で肥大化した『じゃらんnet』が抱えていた問題とは
続いて登壇したのは、『じゃらんnet』のシステム開発・運用やプロジェクトマネジメントを担当する秋田諒氏。長年の増改築で肥大化したシステム問題に対し、どのように取り組んだのか紹介された。
まず紹介されたのは、『じゃらんnet』の規模感。20年の増改築の結果、増大なシステム規模に膨れ上がっていた。
『じゃらんnet』は定期的にクーポンを発行しており、ユーザーにも好評を得ている。クーポンは先着順、かつ配布日時は事前に告知しているため、クーポンの配布開始時には大量のアクセスが発生する。サーバーのレスポンスが遅くなり、サイトにつながりにくくなることもしばしばあったという。
システム負荷が大きい上に、増改築を重ねた巨大システムがゆえに全体感を把握している人が誰もいないといった状態だったため、再構築の検討依頼が秋田氏にきたのである。
再構築の検討を依頼された秋田氏がまず思ったのは、「再構築は魅力的だけど、時間がかかりそう」「再構築が終わるまで、毎回クーポン配布の10時を恐怖で迎えるっていうのは嫌だ」ということ。メンタル的にも良くないので、早く手を打ちたいと考えた。
さらに、システム化と複雑性は分けることを検討した結果、システム負荷から攻めることにした。セール時にユーザーが通る動線を軽く調査したところ、無駄な処理がありそうなことがわかったからだ。
勝ち筋が見えてきたことから、「開発者がビクビクせずにセールの10時を迎える」ことを第一目標に進めることを決めた。いずれ「複雑性の改善」もやることになるので、その仕込みもしておくことにした。
続いて、短期と中長期の二階建てで戦略を立てた。短期では一旦セール時の負荷を軽減して信頼を得る。仕様を理解していることを武器にして改修し、スピードと質が高い機能開発をするための戦略を練っていった。
まずは中長期を見越し、できるだけ幅広く情報を集めることにした。開発軸ではクーポン機能やリソース使用率・画面遷移図の整理など。ユースケース軸では社内でも用語の表記違いを整理したり、クーポンの使われ方などを整理した。
ドキュメントを読んでもわからないことが多かったため、ヒアリングと称し、企画や営業、バックオフィス側の担当者から話を聞いた。これが対外的な印象付けにつながり、クーポンを使った新たな取り組みが始まる際は、必ず声がかかるようになったという。
対策検討フェーズからは、負荷改善の検討を開始した。負荷対策検討のフェーズに関しては、機能一覧や画面遷移図を地図として、セール時にアクセスが多い機能を特定。ボトルネックとなるリソースの使用状況を調査し、改善案を検討、対策を打っていった。
また、セール導線にあった使われていないSQLを削除し、大量のマスターデータを取得するSQLはキャッシュ化。クーポンを発行するときに発行枚数をカウントする行ロックに関しては仕様変更した。情報収集フェーズで、ある程度ソースコードリングを行い処理化も進んでいたこともあり、実装自体は順調に進んだという。
その結果、クーポンのセール動線のスループットが10倍以上となり、DB負荷も余裕がある状態にまで改善。短期目標を達成した。
秋田氏は大規模プロジェクトならでは苦労も紹介している。一つは開発外の体制や責務を理解するのに苦労したこと。施策を考えるマーケティングチームや新しい機能を追加する開発チームなど、それぞれ仕様や要件に対して強い思いを持っているために調整が難航したり、たらい回しをくらうことも。これに関しては、愚直にドキュメントや手順書を読んだり、ヒアリングの場数をこなすことで、解決していった。
ソースコード理解にも、大規模プロジェクトならでは苦労があった。クラス数が多く、トランザクションスクリプトで記載されてるので重複があり、関連システムも多い。一人でコードを読むのは大変なので、ペアソースコードリングなどで乗り切った。
だが次第に、クーポン系の機能の話が出ると最初に声がかかるようになり、チーム内での存在を示せるようになったと語る秋田氏。うまくいかなかったことも含めて形式知をまとめ、中長期の目標実現に臨むべく意欲を見せている。
『Airレジハンディ セルフオーダー』製品版に向けて大切にしてきたこと
最後に登壇したのは、『Airレジハンディ セルフオーダー』のプロダクト開発リーダーを務める早川浩平氏。『Airレジハンディ セルフオーダー』の新規立ち上げからエンジニアとして、フロントエンドとサーバーサイド、インフラまで関わっている。
今回はプロダクトのライフサイクルにおいて、プロトタイプ・MVP(Minimum Viable Product/必要最小限の製品)・製品版という3つのフェーズにおいてエンジニアとしてどう関わり、大切にしてきたかをテーマに語った。
『Airレジハンディ セルフオーダー』は「料理の注文、スマホで完了」というキャッチコピー通り、飲食店でQRコードを読み取ると注文できるWebアプリ。忙しい店員を待つことなく、好きなタイミングで注文できる。店側も業務負荷が削減され、客単価が向上する。コロナ感染症対策としてニーズが高まっている。
製品版リリースまでのフェーズは4つあり、1つ目が課題、価値定義、検証設計。2つ目がプロトタイプの開発・検証、3つ目がMVPの開発・検証、4つ目が製品版の開発・リリース。今回は特に2、3、4についてフォーカスされた。
プロトタイプ開発については、今ある資産をフル活用する、なるべく新しいことを始めない、時には人力に頼る、作ったものは捨てる前提などが挙げられた。技術はReactやAWSなど、メンバーのスキルセットや社内実績から選定。フロントエンドだけ作って、サーバーサイドは作らずにスプレッドシートに連携して注文を飛ばすなど、最低限の機能で進めた。
MVPの開発は、製品版を見据えた技術選定で素早い改善と機能追加、テストはきちんと書く、プロトタイプよりも品質を高めることを意識したという。
KotlinやTypeScriptといった安全な言語を採用しつつ、Kubernetesを採用したasCodeなど、インフラの再編成を高める取り組みにもチャレンジ。アーキテクチャはモノリス、モニタリングには毎朝デイリースクラムで確認するなど、失敗を最小化して早く改善することに力を入れた。
製品版はさらに品質にふり切り、長く安定稼働させるための技術にシフト。具体的にはマイクロサービス化を進め、Gatewayサーバーを構築したり、Spring Cloud Gatewayといったモダン技術で開発している。Terraformでインフラ構成管理の導入、prop drillingはやめる、Redux Toolkitに移行するといった取り組みも紹介された。1枚でまとめると以下のようになる。
最後に早川氏は、「プロダクトのフェーズに合わせて、技術もアーキテクチャも最適なものを選択するように意識し、一貫して新しい技術への挑戦を行い、進化させていくことを大事にしていく」と強調し、セッションを締めくくった。
【Q&A】参加者から寄せられた質問に回答
参加者から寄せられた質問に登壇者が答える時間も設けられた。いくつか紹介する。
Q.複雑性の解消をしたいが、ビジネスサイドの要望によって新規開発の優先度が高くなっている。優先順位付けを建設的に決めるにはどうしたらいいか?
秋田:やはりビジネスサイドと会話すること。どういう戦略を描いてるのかヒアリングし、事前の打ち手を一緒に考えながらビジネスサイドの戦略を洗い出していく。例えばバグを改修したいのであれば、ユーザーやビジネスサイドから見える影響を説明し、対応策を握るといったことが考えられます。
奥冨:私たちは立ち上げから介入できたので、既存改修工数から保守工数まで、これをしないとこういったデメリットがありますよという説明をした上で、ビジネスサイドと認識を握っています。カットオーバーをする前から決めておくのも一つの手ですね。
Q.しくじりのプレゼンなど、チーム横断でシェアされる場や準備はどうしているのか?
松尾:社内で数ヶ月に一度、定期的にエンジニア向けイベントがあるので、発表の場はあります。失敗事例だけではなく、また技術的な話題以外も含め、さまざまなナレッジや知見についても共有するようにしています。その他にも各種分科会が自主的に開催されており、チーム内外での知見や技術的な話題について相談し合う時間が設けられています。
Q.リモートワーク下でコミュニケーションで苦労したこと、工夫したことを教えてほしい
早川:ペアプログラムやモブプログラミングに関しては、リモートになる前からもかなりやってたいたのですが、よりコミュニケーションを意識するようになりました。夕方にチームでオンラインを繋ぎっぱなしにするチーム会を開いて、雑談や困りごとなど、何かしらを話すという取り組みもしていました。
Q.Swaggerの選定から導入まで、どのくらい時間がかかったのか
奥冨:Swaggerに関しては既に導入しているプロダクトがあり、効果が見えていたこともあったので、それほど時間はかかりませんでした。検証とたたきのテンプレート実装、そしてCI装着で、、2週間から1カ月ぐらいでした。後はバグ修正対応がちょっとあったぐらいです。
Q.AWS、ECSを選択した理由は?
奥冨:LambdaはコードをアップロードするだけでAPIを実装できるのですが、言語や実行環境に関する制限がいくつか存在しています。一方、ECSはコンテナの実行環境を提供してくれており、dockerイメージを用意すれば言語や実行基盤に関する制限がLambdaよりも少ない状態でAPIを起動することができます。美容クリニックの場合、APIの処理や実行環境に関してある程度柔軟性をもたせたかったのと、どこまでサーバレスに寄せてサーバレスのメリットを享受するのかを検討した結果、ECSを採用しています。