こんにちは、プロダクトエンジニアリング部の山﨑です。
私は LIFULL HOME'S 賃貸マーケットのプロダクト開発を行うグループに属しており、その中でも技術負債解消に取り組むエンジニアリングチームのリーダーをしています。
本記事では、LIFULL HOME'S の賃貸マーケットにおける技術負債の現状と、本チームの具体的な取り組み内容を紹介します。 LIFULL HOME'S の技術負債解消への取り組みを知っていただくことで、LIFULL HOME'S でのエンジニアリングに興味を持っていただけたら幸いです。
LIFULL HOME'S の賃貸マーケットにおける技術負債の現状
さっそくですが、現状、賃貸マーケットの基盤刷新後のアプリケーションは刷新前のアプリケーションへリクエストを送ることで一部機能の実行を担保しているという課題があります。
以前別の記事でも紹介させていただいた通り、賃貸マーケットでは現在アプリケーション基盤の刷新を行っており、物件詳細ページなどページ単位で機能の移植を進めていました。
着々と機能移植が進んでいった一方で、移植することが工数的に難しい機能もあり、そういった機能は苦渋の決断で刷新前のアプリケーションへリクエストを送り実行を担保しているということです。
技術負債解消に取り組む理由
このような状況において、技術負債解消に取り組む主な理由としては、技術負債をそのままにしておくと以下のような問題が発生し続けるためです。
- 刷新前後のアプリケーション間で不要なリクエストが走ってしまうことで、無駄なリソースを消費してしまっている
- 移植されていない機能は刷新前のアプリケーションを開発して、移植済みの機能は刷新後のアプリケーションを開発するという複雑な開発体制が続いている
- 一部使われている機能があることで、刷新前のコードを削除しにくい
これらの問題は放置し続けられるものではないため、技術負債の解消を優先度高く取り組んでいく必要があるという状況です。
技術負債解消の具体的な取り組みについて
「移植することが工数的に難しかった機能」について実際に私たちのチームで機能の移植を進めました。
今回は、いくつかの機能を移植した中でも特に ユーザーの行動ログを送信する機能 と ユーザーが最後に検索した条件を保存する機能 について、移植のポイントや取り組み内容について紹介していきます。
ユーザーの行動ログを送信する機能
1つ目に移行した機能は、ユーザーの行動ログを送信する機能です。 LIFULL HOME'S ではより良いサービスを提供するために、いくつかのタイミングでユーザーの行動ログを収集しています。 イメージとしては、ユーザーが物件詳細ページを閲覧した際にその物件の情報などいくつかの情報を送信します。 送信された情報はバケットにたまっていき、その後定期的に分析レポートを作成するためのバッチで利用されるといった形ですね。
できあがったレポートは掲載されている物件の状況把握のために活用されていたりと重要な役割を持っています。
この機能を移植するにあたってのポイント
この機能に関しては約 10 年前に開発されて以降はあまり手を加えられていない状態だったので社内の知見者もほぼおらず、ドキュメントも存在していない、まずソースコードがどこで管理されているのかすらわからないという状況からのスタートでした。 そのため、まずはインスタンスで動いているコードを確認してソースコードの管理されている場所を突き止めた後、コードからしくみを把握していく。さっと全体感を理解したうえで、どこをどういった手順で移植していくかを検討していくところから動き始めました。
モブプロでのコードリーディング
全体感を理解していく初回の調査で、コードリーディングの場面に限ってはモブプロで作業を行いました。 「ここはこういった処理をしているね」「ここの処理は不要そう」「本質から逸れているからここの部分は飛ばそう」など認識のレベルを適宜擦り合わせつつ、各々の視点でこの調査が本質から逸れていないかなど視野を広げながら進めていったことで、滞りなくかつその後も前提知識がある程度そろった状態で話を進めることができたというのは一つの良い知見になりました。 コードリーディングをモブプロで行ったことはなかったのですが、置いてけぼりになる人が出てこず、しっかりと議論をしながら進められました。今後も認識をそろえながら進めたい場面では、状況をみて活用していこうと思えた進め方でした。
調査後に判明した機能の構成
さて、一通り調査をして判明した機能の構成は以下図の通りでした。
この機能は主にユーザーの行動ログを処理(バリデーションやネストしたデータをフラットにする整形)するための Fluentd と収集したデータを元にバッチ処理を行う EMR から構成されていました。 ですので、アプリケーション側から移行作業としてやることはシンプルで必要なデータを Fluentd に送信するという処理があれば良いということが分かりました。
やること自体はシンプルなのですが、実際にいざ作業を始めてみると開発の活発だったころからは月日が経ってしまっていて、すでに使われていないであろうデータも送信されている状況でした。 不要な値は削除してしまおうということで、まずは不要であることを確認する調査から始めるのですが、不要なデータを削除するということは、どのデータがどのような処理を経ているのかを理解する必要があります。 そのためには、Fluentd のファイルや EMR のジョブのファイルなど複数のシステム跨いでデータの流れを読み解く必要がありました。
このように不要であることの証明は工数のかかることが多いので、ある程度調査はしつつ残りはテストで担保するか、ほかの部署に確認を行ったりしながらなるべく工数をかけすぎないように削除を進めていきました。 結局、多くの不要データを削除することはできたものの、すべての(不要そうな)データを削除することはやはり難しかったです。 ただ、移植するだけではなくリファクタリングも行うという意識は、このプロジェクトを進めるうえで良い意識だったと思っています。
テストのやり方
テストに関しては、新旧のアプリケーションで同じデータを送信して、Fluentd が吐き出したそれぞれのデータを比較するという方法を取りました。 システムの構成としては Fluentd の送信したデータを元に EMR がバッチ処理を行います。 つまり、Fluentd が吐き出すデータ(= EMR にインプットされるデータ)を移植前後で変わっていないかを比較すると最終的なアウトプットの差分もないということで、この方法を取っています。
テストの構成イメージとしては上記のような形です。 基本的には Fluentd のタグでデータを区別して送信しているので、該当するタグの場合は比較用のバケットへデータを送信する様にしている形です。
なお、テスト用バケットにたまった新旧データを比較するためには、新しいアプリケーションからのデータと同タイミングで作成された旧アプリケーションのデータを紐付ける何かが必要です。 紐づけるものがないと比較すること自体ができないので、紐付けをどうするかということはテストの重要なポイントとなります。 今回のテストではリクエストヘッダに載っている TraceId という値を使って紐付けを行いました。この値は LIFULL の共通実行基盤である KEEL 上で動いているアプリケーションが利用できる値で、アプリケーションを跨ぐ一つのリクエストを紐づけるための値として使われています。
この値をデータに含ませておくことで同一リクエストのデータを比較できるので、テストスクリプト実行時はこの値を見て新旧データを比較することを実現しています。
ユーザーが最後に検索した条件を保存する機能
二つ目に移行した機能は、ユーザーが最後に検索した条件を保存するという機能です。 機能自体はシンプルで、特定のタイミングでユーザーが検索した条件を memcached に保存しておくというものでした。
この機能を移植するにあたってのポイント
今回保存する検索条件は様々なアプリケーションで活用される値なので、基本的に書き込みおよび読み込みの処理は共通の API から行っています。 しかし、この共通の API (以降 V1 API)というのがすでに開発が終了したもので、新規利用が非推奨となっているため刷新後のアプリケーションからは新共通 API (以降 V2 API)を利用する必要がありました。 ただし、V2 API には memcached に対して検索条件保存のためのエンドポイントが存在していないので新たに作成する必要があります。
ここが一つのポイントで、問題は V1 API と V2 API で利用している言語が異なるということです。V1 API は PHP で動いているのに対して V2 API は Ruby で動いていました。 それに加えて、利用している memcached のクライアントライブラリのデータのシリアライズと圧縮のアルゴリズムが異なっており、この部分の整合性を取るのが難しいという問題がありました。
保存するだけであれば問題ではないのですが、保存したものはもちろん取得がされます。 全ての取得したい箇所で V2 API を通して取得される様に統一されているのであればまだ問題にはなりません。V1 API 側に手を入れる必要がありませんからね。
しかし、現状はまだ V1 API から取得している箇所もあるというのと V1 API を通して取得している箇所を特定するのにそのしくみ上工数がかかるため、コストパフォーマンスを鑑みても全ての取得箇所を V2 API に置き換えるのは厳しそうでした。
そのため、V1 API から取得されても V2 API で保存されたものをデシリアライズと解凍出来る様にする必要があるということがこの機能を移植するにあたってのポイントとなっています。
データの圧縮と解凍をどうするか
まずは、なるべく開発を終了している V1 API 側に手を加えることはしたくなかったため、V2 API 側の保存の仕方を調整する形で実装が完結出来ないかと調査を始めることにしました。
V1 API 側では PHP の pecl-memcache ライブラリを利用しており、このライブラリは内部的に zlib の uncompress 関数を使って解凍したあと php_var_unselialize することで検索条件を取得していることがわかりました。 V2 API ではそのまま解凍出来る様にデータ圧縮を行いたかったので、Ruby に組み込まれている Zlib クラスの実装やいくつかの gem のコードを読み漁っていたのですが... 結局 V2 API 側だけでは調整できず、調査ばかりに時間もかけてはいられない状況であったので泣く泣く V1 API 側でも問題なく解凍出来る様に手を加えることで落ち着きました。
リリースに向けて
今回は書き込み機能であり、全社の様々なアプリケーションで活用される値ということで影響範囲も広いため、リリースまでにいくつかのリスクヘッジをしました。 一つは、V2 API を通してデータが保存されるユーザーの割合を徐々に増やすという方法です。 初回は 10 %、3 日ほど経って問題なければ 30 %といった感じですね。
もう一つは memcached に保存するデータの TTL を短くしておくという方法です。 これも万が一問題が起きた時に、ユーザーへの影響を最小限にするために極端に短くしていました。
次のステップ・展望
今回私のチームでは 3 つの機能移植を行うことで刷新前アプリケーションへの依存を取り除くことに注力しました。 加えて、今回の機能移植はデリバリに寄せて進めたところもあり、ベストな選択肢が取れなかった箇所も大いにあるのは心残りな部分でした。 やはり一度に大きな変更(たとえば今回みたいに大きな機能の移植をまとめて行うなど)を行うことはリスクが高かったりさまざまな制約から思う通りに進められないことが多かったです。 今後はメインのミッションを進めつつも、周辺のエンジニアを巻き込んで小さな技術負債解消を積み重ねていく取り組みを行っていきたいと考えています。
終わりに
ここまで LIFULL HOME'S の賃貸マーケットにおける技術負債解消チームの取り組み内容について紹介してきました。 LIFULL HOME'S のような長年稼働しているサービスでは技術負債が蓄積されがちです。だからこそ負債を解消したときのインパクトがあるし、解消するためには多くの知識やスキルを発揮できる環境だと思っています。
このように、LIFULL ではユーザーを始め社内の開発者など多様なステークホルダーの体験を考えたものづくりを行い、ともに成長していける仲間を募集しています。 興味を持っていただけた方は、ぜひ以下のページもご覧ください。