TECH PLAY

株式会社LIFULL

株式会社LIFULL の技術ブログ

660

こんにちは、テクノロジー本部コーポレートエンジニアリングユニットの籔田綾一です。今回は、情シス部門におけるKGI・KPIマネジメントの導入と実践について、その経緯と成果を共有します。私たちの試みが、同じ課題を抱える皆さんの参考になれば幸いです。 はじめに:情シス部門の成果を定量的に計測するには 「情報システム部門で、成果を定量的に示すことができるのだろうか?」 これは、多くの情シス部門が抱える課題ではないでしょうか。日々のインフラ運用、ヘルプデスク対応、セキュリティ対策、そして数々のプロジェクト推進など、情シス業務は多岐にわたります。その貢献を経営層や他部門に示す際、定性的な説明に終始してしまうケースも少なくありません。売上のような明確な指標がないため、部門の価値をどのように証明すれば良いのかという悩みがあるかと思います。 このような経緯から、 「情シス全体で採用できる定量的指標を自分で作ってみよう」 と考えました。 はじめに:情シス部門の成果を定量的に計測するには 「情報システム部門で、成果を定量的に示すことができるのだろうか?」 最初のステップ:指標の「物差し」を統一するアイデア 戦略と連動させるための工夫:ウェイト付けの導入 とにかく1以上を目指そう!:具体的な行動変化 固定費削減への意識と行動の変化 開発タスク進捗向上への意識と行動の変化 振り返り:やってみて分かったこと、そして今後の課題 まとめ:情シス部門の成果は定量的に計測できる まず、数十社もの大手有名企業の情シス責任者の方々にヒアリングを実施しました。その結果、多くの企業で 「部門全体の成果を定量的に測る指標があれば良いとは思うものの、実際には作れていない」という共通認識 があることが分かりました。個別の指標は存在するものの、それらを統合して部門全体の成果として示す仕組みは確立されておらず、正直なところ、私自身も半ば諦めかけていました。 しかし、それでも何か方法はないかと1年ほど模索する中で、ふと解決の糸口を思いつきました。 最初のステップ:指標の「物差し」を統一するアイデア 私が最初に直面したのは、「異なる性質の指標をどう統合し、定量的に評価するか?」という壁でした。固定費、従業員満足度、プロジェクト進捗率、問い合わせ対応時間。これらは単位も性質も全く異なります。 そこで考えたのは、 「物差しが違うなら、統一してしまえ」 という思い付きです。つまり、単位が異なる指標であっても、基準となる数値を設けて比較できるようにすれば良い、という発想です。 例えば、固定費削減であれば、年間予算ならば「1」を基準値とし、10%削減なら「1.1」、逆に10%オーバーなら「0.9」といった具合に表現します。開発進捗率も同様に、完了すべきタスク数(目標値)を分母、完了済みのタスク数を分子とし、「1」以上であれば目標達成とみなしました。これらを個々のKPIとみなし、その平均を部門全体のKGIとして捉えてみました。 戦略と連動させるための工夫:ウェイト付けの導入 設定したKPIが常に全社戦略と整合しているとは限りません。そこで、各KPIにウェイト設定を取り入れてみました。全社戦略における各項目の重要度に応じて、個々のKPIに重み付けを行うイメージです。 部門全体の目標達成度合い(仮に「戦略スコア」とします)をKGIとして、個々のKPIスコアにそれぞれのウェイトを掛けて算出されるようなイメージを表形式で示します。 KPI の種類 KPI名 予算/実績 KPI 重要度 ウェイト KGI コスト関連 固定費削減率 1億/9000万 1.1 高 0.4 0.44 開発進捗 開発タスク消化率 100件/90件 0.9 中 0.3 0.27 業務効率化 工数削減時間 110時間/100時間 1.1 中 0.2 0.22 その他(必要に応じて) システム安定稼働率 100%/100% 1 低 0.1 0.1 KGI(戦略スコア) 1.03 このように、各KPIに戦略上の重要度に応じたウェイトを設定し、それぞれの達成度を考慮することで、部門全体の戦略的な貢献度を評価することができます。そして、このウェイト付けにより、情シス部門の活動が、単なる日々の業務遂行ではなく、企業の戦略目標達成にどのていど貢献できているか定量的に示せます。 とにかく1以上を目指そう!:具体的な行動変化 KPIを評価にも組み込み、KPIマネジメントを半年間運用してみたところ、メンバーの行動に明らかな変化が現れました。 固定費削減への意識と行動の変化 これまで、具体的な数値目標として意識されていなかったコストに対して、「自分たちのKPI」という意識が芽生え、コスト削減に向けた具体的な行動が自発的に生まれるようになりました。 不要アカウントの削減: 無駄なコストを減らすため、各メンバーが主体的に利用状況を調査し、不要なアカウントの削除を提案・実行。 通信料の見直し: モバイル通信料の利用状況について、高額な利用をしているユーザーに対して利用状況の確認や改善提案を行う。 不要なサービスの見直し: 契約しているサービスの利用状況を調査し、利用頻度の低いサービスや重複しているサービスがないか積極的に洗い出し、解約に向けた検討を始める。 PC調達方法の再検討: PCのライフサイクルコストを意識するようになり、リースという選択肢を検討し始めました。コスト削減への意識がなければ、そもそもリースを検討することはなかった。 開発タスク進捗向上への意識と行動の変化 開発タスク消化率をKPIに設定したことで、「期日までにタスクを完了させる」という意識がより向上し、チーム内の協力体制が強化されました。 タスク管理の意識向上: 各メンバーが自身のタスクの進捗状況をより意識的に管理 積極的な協力体制: チーム内で互いに助け合い、タスクの遅延を防ぐための協力体制が生まれる あと一歩を頑張る: あと0.1で達成といった状況が見えるため、リスケよりもゴールしようと頑張る 振り返り:やってみて分かったこと、そして今後の課題 今回のKPIマネジメント導入を通じて、異なる指標を統一化し定量的に評価する難しさ、適切なウェイト付けの重要性、そして何よりも、目標を「見える化」することによる組織の変化の大きさを実感しました。 一方で、KPIの設定やウェイトのつけ方、目標値の妥当性、そして部門全体への浸透には、まだ改善の余地があると感じます。今後も定期的にKPIを見直し、より実効性の高いマネジメントサイクルを確立していく必要があると感じました。 まとめ:情シス部門の成果は定量的に計測できる 情シス部門は、決して「成果が測れない部門」ではありません。 「情シス部門の成果を定量的に計測する」という課題に対し、KPIマネジメントという試みを通じて、数字の裏にあるメンバー一人ひとりの意識と行動のポジティブ変化こそが、KPIマネジメントの成果であると強く感じています。 最後に、LIFULL ではともに挑戦し成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co 籔田綾一 LIFULLテクノロジー本部 コーポレートエンジニアリングユニット ユニット長 2010年入社、技術マネージャーとして商品開発を多数手掛けたのち、 Salesforce(CommunityCloud)を用いたB向けポータルサイト立ち上げ、オンライン受注システム構築。 社内のSaleforce組織を統合、機関システムとSalesforceを連携しマーケティング、CRM、販売管理と一気通貫のシステム構築。 現在は情報システム部門の責任者として社内システム刷新に取り組んでいます。
こんにちは、テクノロジー本部コーポレートエンジニアリングユニットの籔田綾一です。 LIFULLのエンジニアマネージャーの取り組みの一つ「 エンジニアキャリアクリニック 」について紹介します。 組織マネジメントの一環として、参考になれば幸いです。 「最高のチームをつくる」ために エンジニアキャリアクリニックの流れ リピーターも増加中! 参加者の声 まとめ 「最高のチームをつくる」ために それにはメンバー同士の相互理解と、一人ひとりの成長をサポートする環境が不可欠です。 そこで私たちは、メンバーがマネージャーの人となりを知り、気軽に相談できる場として「エンジニアキャリアクリニック」を開設しました。 この「クリニック」では、エンジニアマネージャーが「先生」となり、キャリアパス、技術的な課題、チームでの悩みなど、様々な相談に親身に対応します。 単なる自己紹介ページでは堅苦しく、話しづらい雰囲気になってしまうため、「クリニック」という形式を採用することで、より親しみやすく、相談しやすい環境を目指しました。 最初は数名の有志で始めました。今では、様々な経験を持つ全マネージャーが「先生」として登録し、活発に利用されています。もちろん、弊社のCTOも「先生」として参加し、メンバーの相談に乗っています。 「エンジニアキャリアクリニック」では、キャリアパスの相談だけでなく、日々の業務で直面する技術的な課題や、チームでのコミュニケーションに関する悩みなど、幅広い相談を受け付けています。 オンラインで簡単に予約ができ、それぞれのマネージャーの専門分野や経験に応じて、自分に合った「先生」を選ぶことができます。相談後には、必要に応じて具体的なアクションプランを検討し、フォローアップも行っています。 実際に、「クリニック」を利用したメンバーからは、「CTOと直接話せて刺激になった」「普段聞けない話が聞けて有益だった」「先生の経験談が参考になりモチベーションが上がった」といった声が寄せられています。 エンジニアキャリアクリニックの流れ 「先生」の顔写真と得意分野や相談にのれそうなこと(業務外も含め)を紹介するページを用意 メンバーは「先生」を選んで「予約」 当日中に返信、スケジュール調整 オンラインもしくは対面で30〜1時間ほど1on1を実施 事後に簡単なアンケート実施 予約が入ったら断らない、というスタンスで運営しており、業務であまり関わらないマネージャーにも相談しやすくなっています。 飲みながら、、といった予約も可能です! リピーターも増加中! 定期的な案内の際、はじめは「悩んでる人は相談しよう」のようなニュアンスで促していました。しかし、メンバーからは「いろんな人の考えや経験を聞くことで、キャリア形成のヒントが得られる」「人脈を広げることができる」といったポジティブな意見が多く聞かれるようになりました。そこで、最近はこれらのポジティブなメッセージを強調して案内するようにしています。 その結果、利用者は徐々に増え、今では月に4名ほどが「クリニック」を利用しています。 また、実施後のアンケートも高評価で、毎回同じ「先生」ではなくいろんな先生に予約するクリニックのリピーターも多くなってきました。全利用者の半数以上が2回以上利用しています。 先日は「キャリアを語る会」として、マネージャーが自身の経歴や経験を語る会も開催しました。今後もQに一度開催することで、特に若いメンバーがより利用しやすい雰囲気づくりを目指していきます。 参加者の声 「CTOとサシで話ができていい刺激になった」という声や、 「普段聞けないお話を聞けて非常に有益でした。★5つ」 「先生の考えや経験談がとても参考になりモチベが上がりました!」 「予約したらすぐに調整連絡が来てビックリです。次回も利用しようと思います。」 といった声をいただいてます。 まとめ 「エンジニアキャリアクリニック」では、メンバーが活躍するためのキャリア形成における一助となることを目指しています。 みんなが自分らしく成長出来るように、といった気持ちで「先生」が話を聞いて、語ってもくれます。 そして、困ったときだけでなく日常的に「クリニック」として活用してもらいつつ「最高のチーム」へ近づければと思います。 LIFULL ではともに挑戦・成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co 籔田綾一 LIFULLテクノロジー本部 コーポレートエンジニアリングユニット ユニット長 2010年入社、技術マネージャーとして商品開発を多数手掛けたのち、 Salesforce(CommunityCloud)を用いたB向けポータルサイト立ち上げ、オンライン受注システム構築。 社内のSaleforce組織を統合、機関システムとSalesforceを連携しマーケティング、CRM、販売管理と一気通貫のシステム構築。 現在は情報システム部門の責任者として社内システム刷新に取り組んでいます。
こんにちは!クオリティアーキテクトグループ(以下、QAG)でQAエンジニアをしている片野です。 好きなテスト技法はデシジョンテーブルテストです。 QAGでは横断組織として自動テストやツール開発、プロセス改善などの仕組みの構築に取り組んでいます。 今回は、テストの情報を用いた課題発見の取り組みについて紹介します。 QA組織の紹介 背景 やったこと 具体的な効果 品質ダッシュボードの概要 概要: 品質ダッシュボードを使う理由 活用の仕方 システム構成 今後の展望 まとめ QA組織の紹介 LIFULLでは、設計・実装を行う開発エンジニアがテストも担当しています。 QAエンジニアはプロジェクトを横断してテストの支援をしています。 背景 横断QA組織の立場からは、以下の状況により社内全体の状況把握が難しく、プロジェクトへのプロアクティブな支援が困難になっています。 社内には、さまざまな開発や保守のプロジェクトが同時に動いており、QAGがすべてのプロジェクトへ均等な支援を行えない 各プロジェクト・組織の品質面での課題や改善点がQAGから見えにくい 各プロジェクトの課題は相談を受けてから気付くことが多く、QAGは受動的な対応になりがち やったこと QAGでは、品質課題の解決に向け、品質ダッシュボードを作成しました。 このダッシュボードは、GoogleのLooker Studioを活用していて、様々なデータソースと連携してデータの可視化や分析ができます。 今回は特に、テストケースが格納されているQUBEのデータをLooker Studioに接続し、グラフや表を作成することで、テストの状況を可視化しました。 ※QUBEとはLIFULL独自のテスト設計書全社共通フォーマット。 www.lifull.blog QUBEのサンプル画像 具体的な効果 テストの状況を可視化することにより、冗長なテストケースの削減に効果がありました。 例えば、開発環境やステージング環境など、テストで見るべき観点が異なる環境でも、同じ内容のテストを実施しているケースが見受けられました(特に、テストケース数が多い(テストケース数が上位20%の)プロジェクトにおいて多く見られました)。 それらのテストケースの削減を実施した結果、最大で35%程度の削減をすることができました。 品質ダッシュボードの概要 概要: 品質ダッシュボードは、開発サイクルにおけるテスト関連アクティビティに関する指標を可視化します。 品質ダッシュボードを使う理由 データを可視化することにより、メトリクスベースのフィードバックループ(品質目標の設定→計測→改善)を運用できます。 フィードバックループを回すことで、品質目標(QCD)に近づけたり、品質目標が満たされている状態を保持できます。 活用の仕方 経験や勘ではなく、データに基づいた合理的な意思決定によって、以下のような活用を納得感を持って実現できます。 品質課題の特定 プロセス改善の効果測定 システム構成 今後の展望 今回はテストの効率化に焦点を当てていますが、効率化しすぎるとバグの見逃しにつながる可能性があります。 そのため、開発規模に対してテストケース数が適切かを判断するため、従来型(ウォーターフォール開発でよく使われるメトリクス)のテストケース密度やバグ密度の活用も検討しています。 また、開発生産性についてはアジャイルメトリクスを活用し、QCDの総合的な判断に役立てたいと考えています。 まとめ メトリクスの活用は始まったばかりですが、今回の取り組みを通して、新たな発見もありました。 それは、開発環境やステージング環境など、テストで見るべき観点が異なる環境でも、同じ量のテストを実施しているケースが見受けられたことです。 これは、テストが効率的に行われていないことを示唆しています。 このように、テストのメトリクスを分析することで、ソフトウェア開発の効率や品質を高められると考えています。 最後まで読んでいただきありがとうございました。 LIFULL ではともに挑戦・成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは、グループデータ本部データサイエンスグループの清田です。 昨年の DEIM 2024 に引き続き、「 第17回データ工学と情報マネジメントに関するフォーラム(通称DEIM 2025) 」に参加・登壇してきましたので、その様子を報告いたします。 www.lifull.blog 過去最大の開催規模 生成AI・大規模言語モデル研究の盛況 DEIMの参入障壁を下げる取り組み LIFULLのスポンサー活動とデータセット提供 今後の展開:新たなデータセット提供に向けて おわりに 過去最大の開催規模 DEIM 2025は、2025年2月27日(木)から3月4日(火)にかけて開催されました。昨年に引き続き「直列ハイブリッド」形式を採用し、2月27日(木)から3月1日(土)までがZoom Eventsを用いたオンライン開催、3月3日(月)と4日(火)は福岡国際会議場でのオンサイト開催となりました。 現地会場の福岡国際会議場(福岡市博多区) 今回のDEIMは過去最大規模となる840名もの参加者を集め、データ工学および情報マネジメントに関する研究コミュニティの活況を示しました。429件の一般発表が行われ、そのうち口頭発表とポスターによるインタラクティブ発表の両方が行われたものが386件(うちデモ発表46件)、口頭発表のみが43件という構成でした。 生成AI・大規模言語モデル研究の盛況 昨年に引き続き、今年のDEIMでも生成AI、特に大規模言語モデル(LLM)の利用に関する多数の研究発表が行われていました。5つのトラック(「自然言語処理・機械学習基礎」、「ビッグデータ基盤技術・データセキュリティ・プライバシ」、「情報検索・情報推薦・ソーシャルメディア」、「メディア処理・HCI・人間中心情報マネジメント」、「高度なデータ利活用・ドメイン応用」)すべてにおいて、生成AIの活用事例や研究成果が報告されていました。 特に印象的だったのは、従来の自然言語処理や情報検索の技術がLLMによってどのように変革されつつあるかについての議論です。単にLLMを使うだけでなく、その特性を理解した上で従来技術と組み合わせる研究アプローチが多く見られました。 DEIMの参入障壁を下げる取り組み 今回私が参加して特に印象に残ったのは、兵庫県立大学の大島裕明先生が企画された「DEIMの参入障壁を下げるBoF」です。BoF(Birds of a Feather)セッションとは、同じ興味や関心を持つ参加者が集まって自由に議論する場で、今回はDEIMをより多くの人にとって参加しやすい学会にするためのアイデアが活発に交換されました。特に初めての参加者が楽しく参加できる仕掛けについてのディスカッションが非常に参考になりました。例えば、「初心者向けのオリエンテーションセッション」など、具体的かつ実現可能なアイデアが多く提案されていました。 学会は研究発表の場であると同時に、研究者同士のネットワーキングの場でもあります。特に学生や若手研究者にとって、参入障壁を下げる取り組みは非常に重要であり、DEIMコミュニティの今後の発展につながる貴重な議論だったと感じました。 LIFULLのスポンサー活動とデータセット提供 LIFULLは今回も、前回に引き続きゴールドスポンサーとして出展しました。DEIM 2025には、プラチナスポンサー7件、ゴールドスポンサー15件、シルバースポンサー2件という多くの企業・団体からの支援があり、産学連携の場としても機能していました。 私たちLIFULLからは、 LIFULL HOME'Sデータセット の2015年11月の提供開始からの利用実績の分析結果を技術報告として発表しました。現在までに170件を超える研究成果が発表されており、学術研究コミュニティへの貢献が着実に実を結んでいることを報告できました。 また、新たなデータセットの提供を予定していることについても告知しました。AIや情報処理に関する研究は、多くの研究者がアクセスできる共有データ資源の存在に支えられて発展してきました。生成AI技術の隆盛により、「データを独占的に保有すること自体が巨大な利益につながる」という状況も生まれている中で、オープンなデータ資源の提供は今後も重要な役割を担っていくと考えています。 今後の展開:新たなデータセット提供に向けて 現在、私たちLIFULLでは新たなデータセットの提供開始に向けて準備を進めています。このデータセットは、これまでの不動産情報に加えて、より多様な分野の研究に活用できる内容となる予定です。 生成AIの発展や社会実装が急速に進む中、高品質なデータセットの価値はますます高まっています。LIFULLは今後も、研究コミュニティへの貢献を通じて、社会課題の解決に向けた技術開発を支援してまいります。 来年のDEIM 2026でも、より多くの研究者との交流を深め、データ工学や情報マネジメントの分野の発展に寄与していきたいと考えています。 おわりに DEIM 2025は、オンラインとオンサイト、それぞれの利点を活かした運営により、全国各地からの参加が可能となり、多様な立場の参加者による有意義な議論が展開されました。 LIFULLでは、今後も学会イベントのサポートを継続するとともに、データサイエンスやAI技術を活用した社会課題解決に取り組む仲間を募集しています。豊富な研究開発資源を活かしながら、多様な社会課題の解決に向けた研究開発やプロダクト創出に一緒に取り組んでみませんか? データサイエンスグループでは「活用価値のあるデータを創出」し「データを活用した新たな機能やサービスの研究開発」を加速してくださるデータサイエンティスト(R&D)を募集しています。 興味を持っていただけた方は、 カジュアル面談 も行っていますのでお気軽にご連絡ください。 hrmos.co
こんにちは、テクノロジー本部の布川です。 普段は社内のシステム基盤の運用を担当しています。 先日の記事にありましたように、社内でモノづくりイベント『創民祭』が開催されました。 www.lifull.blog 今回は参加してみて感じたことなどを共有させていただきます! 創民祭参加のきっかけ 創民祭参加につながるアプリ開発のモチベーションが高まったのは、入社2年目のSET研修がきっかけでした。 SET研修とは、新卒2年目のエンジニアを対象に、3人程度のチームでプロダクトをスクラッチ開発しながら、それぞれの技術課題を克服していく研修プログラムです。 業務で基盤運用に関するサービスの設計や実装に携わりつつも、基本的な設計思想やアプリを拡張性高く保って開発する方法などに関して知識と経験の不足を感じていたところ、SET研修で多くを学ぶことができました。 今年度のSET研修参加者からの記事もよろしければ読んでみてください。 www.lifull.blog その後は業務中や、同期の仲間がプライベートで制作しているアプリの開発を手伝ったりする中でSET研修で学んだことの有用性を感じたことが、創民祭に出展するアプリの開発に繋がりました。 アプリの着想と作品概要 今回創民祭で出展したアプリは、一日の満足感を高めることがコンセプトになっています。 生活の中でその日の気分の記録を取っていたところ、行った方が良いと感じていることを行い、行わない方が良いと感じていることを行わなかった時ほど、良い一日だったと感じる傾向が高いことが分かりました。 そのため、良い習慣を継続しつつ、良くない習慣(スマホの見過ぎ)を防ぐことをサポートするアプリを作ることにしました。 特定のスマホアプリの利用時間を目標以内に抑えつつ、良い習慣を一定以上実行できた日を「良い一日」としてカウントし、その達成状況と日々のムードログや日記を比較して効果を測定することを目的としています。 制作したアプリ 開発プロセス 業務外の時間で、半年ほどかけてアプリのコンセプト作りから機能要件決め、システムや画面の設計を少しずつ行いました。 画面の設計が終わった頃、試しにChatGPTに画面のスクリーンショットを渡してみたところ、想像以上に高いクオリティのSwiftコードが出力されたので、これは創民祭に間に合うかもとそこから一ヶ月弱でViewやロジックの実装を行いました。(この時はまだAIエージェントの存在を知らず、ChatGPTからエディタへコピペを繰り返すことに・・・) その後、完成を急ぐあまりシステムの設計が十分に反映されていなかったため、創民祭が終わってから数ヶ月かけて実装をクリーンな形に改善しました。 成長と普段の業務への影響 今回の開発を通して、最初の段階で拡張しやすい基礎となる設計を、時間を掛けて用意しておくことの大切さを改めて学びました。 昔の個人開発では行き当たりばったりの実装で行き詰まることが多く、それが原因で開発を諦めてしまう経験もありましたが、今はAIエージェントによる拡張を繰り返しても分かりやすさや変更のしやすさがほとんど失われず、その学びの成果が出ていると感じます。 普段の業務でも、設計段階で複雑さを排除しシンプルさを追求することで、その後の開発効率が向上することを実感しています。 また、創民祭を通して色々な方にフィードバックを頂けたおかげで、アプリのコンセプトに対する思索が深まったり、UI/UXに関する課題が明らかになり、とても良い機会となりました。 今後の目標 色々な方に頂いたFBをもとにアプリの形を整えた上で、使ってもらえるようにリリースすることを目指しています。 創民祭への参加は、業務外の技術習得に関してFBを受けられたり、社内でのコミュニケーションや新たな発見につながる取り組みです。LIFULLには、技術やものづくりに対する想いが強いメンバーが集まっているからこそ、こうした場が活発に機能していると感じます。 今回の参加でも色々な発表に触れることができ、業務の質の向上とより良いプロダクト開発につながる貴重な経験となりました。これからも職種を問わず社内の方々とさらに盛り上げていければと思います。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
KEELチーム の相原です。 前回のエントリは「小さい経路最適化ミドルウェアを実装してあらゆるAZ間通信を削減する」でした。 www.lifull.blog 今回は、MCPサーバを比較的安全に動かすために色々やってた話を書きたいと思います。 MCPについて MCPサーバのリスク なるべくローカルで動かさない ローカルではせめてDockerで動かす 無理やりHTTP Transportに対応する セッションの開始 コマンドの起動 ペイロードを受け取るためのエンドポイントの実装 まとめ MCPについて MCPはModel Context Protocolの略で、Anthropicが標準化を主導するLLMとその外部を繋ぐプロトコルです。 github.com これによりGitHubやPlaywrightといった外部のツールをLLMが自律的に利用できるようになり、OpenAIもサポートを表明したことから大きな注目を集めています。 要は標準化されたFunction Callingということになるでしょう。 我々KEELチームでは2年ほど前から無限にスケールする汎用AI(仮)を開発してきており、これは内部でFunction Callingするエージェントを複数協調させて動かすことであらゆるタスクを解決することを狙っています。 www.lifull.blog Function Calling(MCPサーバ)を横に大量に並べて使おうとするとトークン消費が無視できなくなるはずで、当時我々はそれを解決するためにマルチエージェントとして複数のエージェントを協調させることを選びました。 (最近の A2A の動きを見ていても、まだこの設計でやっていけそうだなと感じています) また、エージェントを簡単に開発できるフレームワークも提供することで、社内のContributorが自由に社内システムとの連携を実装できるようにもなっています。 ということもあり、我々目線ではMCPの登場は「コミュニティが成熟すればFunction Calling実装の手間が省けるな~」くらいの受け止め方だったわけですが、今となっては職種問わず多くの人がローカルでMCPサーバを動かそうとし、社内システムのMCPサーバも開発されるようになってきました。 そろそろMCPが無視できなくなってきており、(本業はマルチテナントなKubernetesクラスタの開発ではありつつ)プラットフォーマーとして動くことにしました。 MCPサーバのリスク 2025年5月現在のよくあるMCPサーバの実行方法は npx や docker run 経由のものだと理解しています。 Docker for Desktop有償化とかCPU Virtualizationが必要であることを考えると、職種問わず利用者が多いのは npx でしょうか。 多くの場合どちらも素朴に最新のMCPサーバの実装を取得してきて、実行ユーザの権限でstdio Transport経由でMCPホストが利用します。 そしてその場合、 悪意のあるMCPサーバを誤って実行してしまうと、踏み台として利用されたり npx 経由での実行では環境変数やファイルを奪われてしまうというリスクがあります。 最近ではMCPホストのUIからは見えない形でLLMに対して悪意のある指示をする攻撃手法が発見されるなど、MCPサーバを対象にした攻撃は巧妙化しつつあり注意が必要です。(それでも私たちはAuto-approveを使ってしまうわけですが) invariantlabs.ai とりあえずバージョンは固定したいですし、バージョンがプログラムの中身を一意に識別するものでないことを考えると、最低限 docker run ではDigestでの参照や、他もChecksumまでやれると理想でしょう。 ただそれだけではまだ不十分だと感じていて、欲を言えばWebAssemblyやDenoのようなサンドボックス上で明示的にCapabilityを与えた上で実行したいところです。 一方でこれらはMCPサーバの実装に依存する部分が多くあり、その中で比較的安全にMCPサーバを運用できないかと色々やってきました。 なるべくローカルで動かさない そもそも各々がローカルで大量のMCPサーバを実行することが会社的には少し厳しさがあると感じています。 もちろんPlaywrightのMCPサーバのように手元のデスクトップ環境を操作したい場合はローカルで動かす必要がありますが、社内システム用のMCPサーバやOrganizationレベルで権限を持ったGitHubなどのMCPサーバは、社内共通で一つ動かしておいてそれを各々が参照するだけでよい可能性があります。 MCPにはいくつかのTransport実装があり、よく使われる(?)stdio TransportはMCPホストが直接コマンドとしてMCPサーバを実行し、その名の通りstdioを介してMCPサーバとやり取りをします。 一方で、Server Sent Eventsを利用したTransport実装やそれを改良した Streamable HTTP Transport と呼ばれるHTTPに載ったものもあり、これらはリモートにあるMCPサーバを利用可能です。 そのため、LIFULLではこれらのTransportを実装したMCPサーバはまとめて前述のマルチテナントなKubernetesクラスタで運用して提供するようにしています。 apiVersion : apps/v1 kind : Deployment spec : template : spec : automountServiceAccountToken : false securityContext : fsGroup : 65532 seccompProfile : type : RuntimeDefault containers : - name : mcp securityContext : privileged : false allowPrivilegeEscalation : false capabilities : drop : - ALL readOnlyRootFilesystem : true runAsUser : 65532 runAsNonRoot : true seccompProfile : type : RuntimeDefault <snip> Kubernetesのベストプラクティス(Pod Security Standards)に沿って厳格な権限で動かしています。 明示的に許可しない限り、MCPサーバはrootfsへの書き込み権限すら与えられません。 また、LIFULLではすべてのコンテナにIstioのプロキシを入れることを義務付けており、MCPサーバにも同様にIstioのプロキシを入れて、許可した接続先以外にはリクエストできないようにしています。 apiVersion : networking.istio.io/v1beta1 kind : Sidecar spec : egress : - captureMode : DEFAULT hosts : - istio-system/istiod.istio-system.svc.cluster.local - istio-system/otel-agent.istio-system.svc.cluster.local outboundTrafficPolicy : mode : REGISTRY_ONLY <snip> www.lifull.blog これにより、ローカルで動かす必要がないMCPサーバをプラットフォームから提供することで、リソースの効率化とともに比較的安全に運用することができました。 ローカルではせめてDockerで動かす とはいえ全てのMCPサーバをリモートで動かせるわけではありません。 前述のようにデスクトップ環境に依存するようなMCPサーバはローカルで動かす必要がありますし、Personal Access Tokenを利用せざるを得ないものも同様です。 その際は、Kubernetesで実現していたものと似たような隔離空間をDocker Composeを使って実現することを推奨しています。 # docker-compose.yaml services : tcp-proxy : image : ghcr.io/lifull/keel/proxy:latest command : - https://example.com - --mode - tcp - --address - 0.0.0.0:443 networks : default : {} internal : ipv4_address : 172.16.255.1 my-mcp-server : image : ghcr.io/lifull/keel/my-mcp-server:latest cap_drop : - ALL environment : - PERSONAL_ACCESS_TOKEN=${PERSONAL_ACCESS_TOKEN} extra_hosts : - "example.com:172.16.255.1" networks : internal : {} networks : internal : internal : true ipam : driver : default config : - subnet : 172.16.0.0/16 default : driver : bridge Dockerはデフォルトでbridgeネットワーク上でコンテナを動かし、bridgeを介してホストのネットワークからインターネットに出ていきます。 そのため、悪意のあるMCPサーバをDockerコンテナとして動かしてしまうとそのまま外にリクエストされてしまうわけです。 そこで、この例ではDocker Compose内に閉じたネットワーク internal を作成し、MCPサーバにはその internal のみを割り当てることでインターネットに疎通できないようにしています。 しかし、ものによってはGitHubのAPIを叩くなどインターネットに疎通する必要があるものもあります。 その際はTCP Proxyを同じく internal ネットワーク内に用意したうえで、そのTCP Proxyのみをbridgeネットワークにも割り当てることで、許可した接続先にのみリクエストできるようにしています。 extra_hosts を利用することでMCPサーバのプログラムを一切変更することなく、安全にTCP Proxyを経由させることが可能です。 いちいち解説するまでもないと思いますが、TCP Proxyの実装はこんな感じです。 TCP Proxy func runTCPServer(listener net.Listener, target *url.URL, a *Args) error { shutdown := make ( chan struct {}, 1 ) semaphore := make ( chan struct {}, a.MaxConnections) wg := sync.WaitGroup{} go func () { for { local, err := listener.(*net.TCPListener).AcceptTCP() if err != nil { select { case <-shutdown: return default : continue } } semaphore <- struct {}{} wg.Add( 1 ) go func () { defer func () { <-semaphore wg.Done() }() defer local.Close() remoteAddress := target.Host if target.Port() == "" { switch target.Scheme { case "http" : remoteAddress = net.JoinHostPort(target.Hostname(), "80" ) case "https" : remoteAddress = net.JoinHostPort(target.Hostname(), "443" ) } } remote, err := net.DialTimeout( "tcp" , remoteAddress, 10 *time.Second) if err != nil { return } defer remote.Close() c := make ( chan struct {}, 2 ) f := func (c chan struct {}, dst io.Writer , src io.Reader ) { _, _ = io.Copy(dst, src) c <- struct {}{} } go f(c, remote, local) go f(c, local, remote) select { case <-c: case <-shutdown: local.CloseWrite() } }() } }() quit := make ( chan os.Signal, 1 ) signal.Notify(quit, syscall.SIGTERM) <-quit time.Sleep(time.Duration(a.Lameduck) * time.Second) close (shutdown) listener.Close() wg.Wait() return nil } docker-compose.yaml を配布するだけで便利なMCPサーバを社員に行き届かせることができるので一石二鳥ですね。 一つトレードオフとして、この場合MCPサーバは docker compose up でまとめて起動するため、必然的にMCPクライアントからはstdio TransportではなくHTTP Transportを利用することになります。(JetBrainsのJunieはHTTP Transportに未対応だったりします) 無理やりHTTP Transportに対応する 一方で、一つ目のリモートで動かすにせよ二つ目のDocker Composeで動かすにせよ、MCPサーバがHTTP Transportに対応していることが前提となります。 しかし野良のMCPサーバの中にはstdio Transportしか実装していないものがそれなりにあります。 そういったMCPサーバでは上記のアプローチが取れないので工夫が必要です。 そこで、我々KEELチームはstdio Transportにのみ対応しているMCPサーバを無理やりSSE Transportに対応する小さいプロキシを開発しました。 バイナリとして簡単に配布できるようにGoで書いています。 mcp-stdio-proxy package main import ( "bufio" "context" "encoding/json" "errors" "flag" "fmt" "log" "net" "net/http" "os" "os/exec" "os/signal" "runtime/debug" "sync" "syscall" "time" "github.com/google/uuid" ) type MCPMethod string // https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-03-26/schema.ts const ( CancelledNotification MCPMethod = "notifications/cancelled" InitializeRequest = "initialize" InitializedNotification = "notifications/initialized" PingRequest = "ping" ProgressNotification = "notifications/progress" ListResourcesRequest = "resources/list" ListResourceTemplatesRequest = "resources/templates/list" ReadResourceRequest = "resources/read" ResourceListChangedNotification = "notifications/resource/list/changed" SubscribeRequest = "resources/subscribe" UnsubscribeRequest = "resources/unsubscribe" ResourceUpdatedNotification = "notifications/resource/updated" ListPromptsRequest = "prompts/list" GetPromptRequest = "prompts/get" PromptListChangedNotification = "notifications/prompt/list/changed" ListToolsRequest = "tools/list" CallToolRequest = "tools/call" ToolListChangedNotification = "notifications/tool/list/changed" ) type MCPMessage struct { JSONRPC string `json:"jsonrpc"` Method MCPMethod `json:"method"` ID any `json:"id,omitempty"` } type session struct { id string responseQueue chan [] byte requestQueue chan [] byte } func main() { var address string var terminationGracePeriodSeconds int var lameduck int var keepAlive bool var verbose bool flag.StringVar(&address, "address" , "0.0.0.0:8080" , "" ) flag.IntVar(&terminationGracePeriodSeconds, "termination-grace-period-seconds" , 10 , "The duration in seconds the application needs to terminate gracefully" ) flag.IntVar(&lameduck, "lameduck" , 1 , "A period that explicitly asks clients to stop sending requests, although the backend task is listening on that port and can provide the service" ) flag.BoolVar(&keepAlive, "http-keepalive" , true , "" ) flag.BoolVar(&verbose, "verbose" , false , "" ) flag.Parse() args := flag.Args() if len (args) == 0 { log.Fatalf( "command not specified" ) } name := args[ 0 ] var arg [] string if len (args) > 1 { arg = args[ 1 :] } sessions := &sync.Map{} mux := http.NewServeMux() mux.HandleFunc( "GET /sse" , func (w http.ResponseWriter, r *http.Request) { w.Header().Set( "Content-Type" , "text/event-stream" ) w.Header().Set( "Cache-Control" , "no-cache" ) w.Header().Set( "Connection" , "keep-alive" ) flusher, ok := w.(http.Flusher) if !ok { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } cmd := exec.Command(name, arg...) stdin, err := cmd.StdinPipe() if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } stdout, err := cmd.StdoutPipe() if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if err := cmd.Start(); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } s := &session{ id: uuid.New().String(), requestQueue: make ( chan [] byte , 65536 ), responseQueue: make ( chan [] byte , 65536 ), } sessions.Store(s.id, s) go func () { scanner := bufio.NewScanner(stdout) for scanner.Scan() { s.responseQueue <- scanner.Bytes() } }() defer func () { sessions.Delete(s.id) _ = cmd.Process.Kill() _ = cmd.Wait() close (s.requestQueue) close (s.responseQueue) }() _, _ = fmt.Fprintf(w, "event: endpoint \n data: %s \n\n " , fmt.Sprintf( "http://%s/messages?sessionId=%s" , r.Host, s.id)) flusher.Flush() for { select { case data := <-s.requestQueue: _, _ = stdin.Write(data) case data := <-s.responseQueue: _, _ = fmt.Fprint(w, fmt.Sprintf( "event: message \n data: %s \n\n " , data)) flusher.Flush() case <-r.Context().Done(): return } } }) mux.HandleFunc( "POST /messages" , func (w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get( "sessionId" ) if id == "" { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } sany, ok := sessions.Load(id) if !ok { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } s := sany.(*session) var rawMessage json.RawMessage if err := json.NewDecoder(r.Body).Decode(&rawMessage); err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } var mcpMessage MCPMessage if err := json.Unmarshal(rawMessage, &mcpMessage); err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if verbose { log.Printf( "%s: %s" , id, mcpMessage.Method) } s.requestQueue <- rawMessage s.requestQueue <- [] byte ( " \n " ) w.Header().Set( "Content-Type" , "application/json" ) w.WriteHeader(http.StatusAccepted) _, _ = w.Write([] byte (http.StatusText(http.StatusAccepted))) }) mux.HandleFunc( "GET /healthz" , func (w http.ResponseWriter, r *http.Request) { w.Header().Set( "Content-Type" , "text/plain; charset=utf-8" ) w.WriteHeader(http.StatusOK) _, _ = w.Write([] byte (http.StatusText(http.StatusOK))) }) listener, err := net.Listen( "tcp" , address) if err != nil { log.Fatalf( "failed to listen: %+v" , err) } server := &http.Server{ Handler: mux, } server.SetKeepAlivesEnabled(keepAlive) go func () { defer func () { if err := recover (); err != nil { log.Printf( "panic: %+v \n %s" , err, debug.Stack()) } }() if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatalf( "failed to listen: %+v" , err) } }() quit := make ( chan os.Signal, 1 ) signal.Notify(quit, syscall.SIGTERM) <-quit time.Sleep(time.Duration(lameduck) * time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(terminationGracePeriodSeconds)*time.Second) defer cancel() if err := server.Shutdown(ctx); err != nil { log.Fatalf( "failed to shutdown: %+v" , err) } } 開発当時はStreamable HTTP TransportがなかったのでSSE Transportにのみ対応しています。 それでは実装を軽く解説していきます。 セッションの開始 SSE Transportはこちらの仕様の通り、 /sse のようなSSEのエンドポイントで初回のリクエストを受けると、ペイロードを受け取るための別のエンドポイントを event: endpoint として返す必要があります。 github.com mux.HandleFunc( "GET /sse" , func (w http.ResponseWriter, r *http.Request) { w.Header().Set( "Content-Type" , "text/event-stream" ) w.Header().Set( "Cache-Control" , "no-cache" ) w.Header().Set( "Connection" , "keep-alive" ) flusher, ok := w.(http.Flusher) if !ok { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } <snip> _, _ = fmt.Fprintf(w, "event: endpoint \n data: %s \n\n " , fmt.Sprintf( "http://%s/messages?sessionId=%s" , r.Host, s.id)) flusher.Flush() <snip> } これはSSEがサーバからクライアントにメッセージを送信するための一方向の仕組みであるためです。 developer.mozilla.org そのためSSE Transportでは2つのHTTPエンドポイントを実装することで、MCPクライアントとの双方向通信を実現しています。 もう一つのエンドポイントの実装は後述するため、ここではSSEのエンドポイントのみ解説します。 sessions := &sync.Map{} mux.HandleFunc( "GET /sse" , func (w http.ResponseWriter, r *http.Request) { <snip> s := &session{ id: uuid.New().String(), requestQueue: make ( chan [] byte , 65536 ), responseQueue: make ( chan [] byte , 65536 ), } sessions.Store(s.id, s) <snip> defer func () { sessions.Delete(s.id) <snip> close (s.requestQueue) close (s.responseQueue) }() <snip> } UUIDを発行してセッションを開始しています。 ここで開始されたセッションはMCPクライアントに通知したペイロードを受け取るためのエンドポイントからも利用するため、一つ上のスコープで sessions として保持します。 コマンドの起動 セッションの開始と同時にstdio Transportを実装したMCPサーバの起動もしましょう。 注意点として、stdio Transportで実装されたMCPサーバは内部で状態を持つため、多くの場合セッションごとにMCPサーバを起動する必要があります。 sessions := &sync.Map{} mux.HandleFunc( "GET /sse" , func (w http.ResponseWriter, r *http.Request) { <snip> cmd := exec.Command(name, arg...) stdin, err := cmd.StdinPipe() if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } stdout, err := cmd.StdoutPipe() if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if err := cmd.Start(); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } go func () { scanner := bufio.NewScanner(stdout) for scanner.Scan() { s.responseQueue <- scanner.Bytes() } }() defer func () { _ = cmd.Process.Kill() _ = cmd.Wait() <snip> }() <snip> for { select { case data := <-s.requestQueue: _, _ = stdin.Write(data) case data := <-s.responseQueue: _, _ = fmt.Fprint(w, fmt.Sprintf( "event: message \n data: %s \n\n " , data)) flusher.Flush() case <-r.Context().Done(): return } } } stdio TransportのMCPサーバの標準出力を s.responseQueue を介してMCPクライアントに通知し、セッション開始時にMCPクライアントに通知したペイロードを受け取るためのエンドポイントから送信される s.requestQueue から受け取ったリクエストは stdin.Write(data) で標準入力に書き込んでいます。 セッション終了時にコマンドを終了することも忘れないようにしましょう。 ペイロードを受け取るためのエンドポイントの実装 mux.HandleFunc( "POST /messages" , func (w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get( "sessionId" ) if id == "" { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } sany, ok := sessions.Load(id) if !ok { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } s := sany.(*session) var rawMessage json.RawMessage if err := json.NewDecoder(r.Body).Decode(&rawMessage); err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } var mcpMessage MCPMessage if err := json.Unmarshal(rawMessage, &mcpMessage); err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if verbose { log.Printf( "%s: %s" , id, mcpMessage.Method) } s.requestQueue <- rawMessage s.requestQueue <- [] byte ( " \n " ) w.Header().Set( "Content-Type" , "application/json" ) w.WriteHeader(http.StatusAccepted) _, _ = w.Write([] byte (http.StatusText(http.StatusAccepted))) }) こちらはシンプルで、クライアントに通知したメッセージをもとに sessionId が送られてくるため、そこからセッションを取得し、送られてきたペイロードをそのまま s.requestQueue に流しています。 前述の通り s.requestQueue に送信されたペイロードはセッションごとに起動しているコマンドの標準入力に書き込まれます。 ここまでのシンプルな実装で、stdio Transportのみに対応したMCPサーバを無理やりSSE Transportに対応させることができました。 後はこんな感じで使えばよさそうです。 FROM ghcr.io/lifull/keel/mcp-stdio-proxy:main AS mcp-stdio-proxy FROM ghcr.io/github/github-mcp-server@sha256:fdf04e33b437c523d2f091b7bf8dc3724c88dbf9913d6568f12f0fcf48aaff95 COPY --link --from=mcp-stdio-proxy /usr/local/bin/mcp-stdio-proxy /usr/local/bin/mcp-stdio-proxy ENTRYPOINT ["/usr/local/bin/mcp-stdio-proxy", "/server/github-mcp-server", "stdio"] まとめ このようにLIFULLでは比較的安全にMCPサーバを動かしています。 過渡期ゆえの混沌のような気もしますが、ほどほどのハックで秩序を手に入れられた気がします。 SSE Transportには負荷分散がしづらいという問題があり、それを受けてStreamable HTTP Transportが開発されるなど、MCPはまだ進化の途上です。 セキュリティに気を配りながらこれからもMCPを使っていきたいです。 コミュニティの実装も大分増えてきていて、我々が開発する無限にスケールする汎用AI(仮)にもMCPクライアントを実装して、Function CallingとMCPのエコシステムを統合したマルチエージェントで真の汎用AIを目指しています。 プラットフォーマーとしてのLLMにまつわる活動に興味がある方がいれば、是非カジュアル面談させてください! hrmos.co
グループデータ本部データサイエンスグループの嶋村です。 今回は、弊社が運営する不動産・住宅情報サービス「LIFULL HOME'S」において、 数理最適化技術を適用 して事業指標の向上に挑んだプロジェクトについて紹介します。どのようにプロジェクトを推進したのかや、推進する過程で得た知見についても、お伝えしたいと思います。 プロジェクトの概要 フェーズ1「企画段階」での取り組み フェーズ2「データ準備段階」での取り組み フェーズ3「モデル開発段階」での取り組み フェーズ4「効果検証段階」での取り組み おわりに プロジェクトの概要 LIFULL HOME'Sでは、不動産会社から物件広告をお預かりし、物件を探すエンドユーザへ届けるサービスを展開しております。不動産会社は自社の物件広告を多くのユーザに閲覧してもらいたいと希望する一方、エンドユーザは自分にぴったりでより魅力的な物件を見つけたいという双方の要望があります。そこで双方の視点を考慮し、 全体として最適な広告表示の制御を実現 するべく、数理最適化モデルを適用するという挑戦をしました。 本プロジェクトでは 事業部と研究開発組織であるデータサイエンスグループが連携する取り組み となり、私はプロジェクトマネージャとして全体を推進する役割を担いました。プロジェクトの進め方として、 データサイエンティスト協会 および 情報処理推進機構(IPA) が公開しているデータサイエンス領域に関するタスク構造図(プロジェクトフロー)を参考にしました。以下の図はそのタスク構造図をベースに自分なりに解釈して作成した AIプロジェクトフロー になります。 プロジェクトを推進する上では、取り組むべきことが多岐にわたり、関係者の数も多いことが、難しさの一つでもありました。特に、社内でAI関係のプロジェクトに関する知見やノウハウが豊富に蓄積されている訳ではないため、どのようにプロジェクトを進めれば良いのかの 進め方のイメージが関係者でバラバラ でもありました。そのため、誰がどのような役割をどの段階で遂行すれば成功するのかを共有するために、下記に示すように チーム体制図や期待する役割を図で可視化 をして関係者に共有をしました。 ここからは4つの各フェーズでの取り組みについて、どのように進めたのか、どのような難しさがあったのか、どのように乗り越えたのか、をご紹介していきます。なお、本来はフェーズ1から順番にフェーズ4まで進めるのが想定の流れではありますが、プロジェクトの目標期日の関係で各フェーズで独立に進められるところを見つけ、可能な限り並行して進める工夫をしました。 フェーズ1「企画段階」での取り組み まず、最適化対象となった箇所において、 どのような課題感があるのか事業部へのヒアリング から始まりました。ここでは過去データからの事実や考察(推察)など客観的な情報と主観的な情報が混ざるため、それらを整理していきました。そして、ヒアリングした情報から実際にデータを確認して、どのような状況になっており、どのような課題があるのか特定をしていきました。 そして、課題に関連する指標を向上させるために、何が必要なのか要素を分解していきました。最適化をしたとして、 どのようなメカニズム で指標が向上するのか、といったモデル化をしました。ここでのモデル化は、フェーズ4における評価方法にも関係しますし、そもそもフェーズ3でどのような最適化モデルを作成するのか、そのためにフェーズ2でどのようなデータが必要なのか、と全てのフェーズに関連するため、 非常に重要なプロセス と捉えています。 次に、どのような最適化であれば許容されるのか、最大化したい目的関数や制約条件に関して合意形成に取り組みました。なお、この段階ではあまり専門用語を使うと会話のハードルを引き上げてしまうため、可能な限り数理最適化を意識させるような 専門用語は使わないように留意 しました。本プロジェクトは関係者が多く、色々な思惑があるため、この部分が かなり苦労したプロセス になりました。また、掘り下げてヒアリングをしていくと、時には具体的な要望を引き出せる一方で強い制約にもなってしまうため、本当に気にしているポイントはどこなのか、どのような世界観を実現したいのかを重視してヒアリングするように努めました。その意志を尊重し、最適化問題として定式化した上で、その目指したい方向性に近づけるための最適化であることを 丁寧に粘り強く説明 を重ねました。 最後に、どのように最適化を適用するのか、 データフローやシステム全体の概要を整理 しました。下図はその一例です。なお、プロジェクトを推進するにあたって作成した図は、関係者とのコミュニケーションをする上で終始使うことが多かったです。関係者が多い中で、一丸となって推進するためには、全員で共通のゴールイメージを持ち、そのゴールに辿り着くために何が必要か共通のイメージを持てることが重要です。図を描くということは、時間を要する一方で、自分自身の思考整理に役立ちますし、共通認識を醸成するのにすごく有効な手段であると考えており、私は重要視しています。 フェーズ2「データ準備段階」での取り組み フェーズ1で検討した施策内容に基づき、フェーズ2では後段のフェーズで必要なデータを準備します。単に「データ」と呼ぶだけだと何を指すのか、 人それぞれ「データ」の解釈が異なる こともあるため、どのような種類のデータがあるのかという整理が重要なプロセスでした。下図に整理した結果を示しておりますが、大別すると4種類のデータがあり、それぞれ誰が作るのか、誰が扱うのか、が異なります。これらを意識せず「データ」とだけ表現してしまうと混乱を招くため、この整理をしたことで認識齟齬の発生を防ぐことができました。 フェーズ2では、主にデータソースからデータマートを作成しました。まず、データマートとして何が必要なのか要件を整理した上で、データソースとして何を使うのか調査検討をしていきました。そして、データマートとして加工していくためのパイプライン処理を実装していきました。様々なデータソースからデータマートが作成されるため、タイムスケジューリングも重要でした。最適化の実行タイミングは早ければ早いほど良かったため、何時にどのデータが揃い、どの時点でデータ作成ジョブを実行すれば、何時にデータマートが完成するのか、関係部署との調整をしていきました。 また、データソースの特徴を理解していくにつれて、 イレギュラーなデータが存在 することもわかってきました。たとえば、欠損値や、暫定的に入力された仮の値が入っている、人間が見ると解釈できるものの意図しない箇所に値が入っている、などがありました。しかし、そのままでは最適化ができなくなってしまうため、どのような値で補完すれば最適化をする上で問題がないか、最適化側の要件を踏まえて対応策を練りました。 結果的に、フェーズ2はプロジェクト全体の中でも 時間を要したフェーズ となりました。改めて、データを活用しやすい形かつ正しく形で維持するための、データ整備の重要性を実感しました。 フェーズ3「モデル開発段階」での取り組み フェーズ3では、フェーズ1で決定した目的や要件、フェーズ2で準備したデータをもとに、 数理最適化モデルの作成 に取り組みました。下図は数理最適化処理の全体の流れを表しており、大別すると4段階の処理がありました。 この流れの中で、③の数理最適化はもちろん重要なのですが、その 成否を分けるのは①の定数推定 でした。①の定数推定が誤っていると、どのように最適化をしたとしても、結果的に誤った制御になってしまうためです。①では、推定に用いるデータはどのような特性があるのか、探索的データ分析(EDA)から始めました。何をするにしても「 まずはデータの特性を理解する 」というのは基本中の基本です。その上で、どのような特徴量を作り、どの程度の推定精度になるのか、評価実験を重ねました。また、時系列で見た時に値が0が多いが突発的に値が上昇する指標については、値自体を推定が難しく、0か非0なのかを推定する問題にするなど工夫も重ねました。 ②の前処理は現実時間で③の数理最適化を実行するための工夫になります。愚直に解こうとすると、 現実的な時間で求解 できず、最適化結果を制御に使うことができない状況でした。そのため、問題の分割や、数理最適化を適用しなくても良い領域を定義するなど、 計算量を削減する工夫 をしました。 ③の数理最適化では、ビジネス的な制約(要望)を考慮した上で、定式化に取り組みました。定式化をする上では、非線形性をどのようになくしていくか、求解可能性をどう担保するのか、という観点で苦労をしました。さらに、事業構造を理解した上で、どのように定式化するか、 数理モデリングの難しさ を特に感じました。数理モデリングというのは 具体的な事象を抽象的に単純化して表現 するので、「本当にこれで適しているだろうか」という不安がつきまといました。定式化の検討をしては、事業部側の意向を考慮できているか、意図した最適化になるかの確認をし、 喧々諤々と議論 を重ねました。 ④のオフライン評価では、 フェーズ4で本番適用をして良いのかを判断 するための重要な段階でした。仮定を設定し、過去実績を用いたシミュレーションにより、オフライン評価をしました。どの程度の効果があるのかを事前検証するために、 従来手法と今回の最適化手法の比較評価 をしました。オフライン評価を通じて、想定通りの制御になっているか、どの程度の機会効果が見込めそうかを改めて確認しました。③で定式化をした時と想定通りの評価結果になった時はとても心地良さを感じ、チーム内で喜びを分かち合い、これで本番のオンライン評価でもいけると確信を得ました。 フェーズ4「効果検証段階」での取り組み フェーズ4では効果検証をするために、まずは 数理最適化システムのプロトタイプ実装 をし、その上で 評価実験 をしました。 フェーズ3で作成したのは数理最適化処理のコアな部分だったため、それをシステムとして動作させるための環境整備や運用体制の整備をしました。また、どのような異常が発生しうるか、その際にどのように対処するかも、整理していきました。 弊社では施策評価をA/Bテストで評価しており、今回もA/Bテストをしました。向上させたい指標(Goal metrics)と、低下を防ぎたい指標(Guardrail metrics)を決め、さらに意図した制御をしているか確認するための指標(Other metrics)を定義しました。今回の評価実験では下図に示すように、既存手法であるAと最適化手法であるBで干渉し合うという性質がありました。そのため、 A/Bテストだけでは判断がつかない可能性 があったため、Other metricsを注視すると事業部側と事前に合意形成をしました。 A/Bテストを通じて、 意図通りの制御になっていることが確認 でき、数理最適化システムは本番適用されることになりました。一方、当初の想定通り、A/Bテストでは明確にGoal metricsで差が出ず、「良さそうだが本当に効果があったのか」と はっきりしない状態 でもありました。 そこで、 A/Bテスト適用前と適用後の前後比較 をすることで、効果の推計に挑みました。A/Bテスト適用前と適用後では、外部環境も異なることになるため、単純な比較では正しく推計ができません。そのため、過去の実績データがA/Bテスト適用前と並行トレンドであることを仮定した上で、 差分の差分法を用いて効果推計 をしました。 前後比較による効果推計の結果、上図のイメージ図のように、向上させたい指標で明確な差が生じることを示すことができました。 おわりに 今回の記事では、 数理最適化 というアプローチを用いて、多くの関係者の要望や運用上の制約を考慮した 全体最適制御に挑戦 した取り組みを紹介しました。多様なニーズに応えることができる画期的な数理最適化システムを実現できたと自負しております。数理最適化は今後のLIFULL HOME'Sにおけるサービス改善の基盤となる技術であることを示すことができ、データサイエンスを活用した 新たな技術応用の可能性を示唆 する結果となりました。 また、プロジェクトを成功に導くにあたり、どのようなプロセスでプロジェクトを推進してきたのか、についても紹介をしました。各フェーズでどのように 創意工夫 をしてきたのか、少しでも読者のみなさまの参考になると嬉しいです。 引き続き、数理最適化をはじめとする有用な技術の開発や活用をしていき、より魅力的なLIFULL HOME'Sの実現に向けて邁進していきたいです。今後の取り組みも発信していきたいと思いますので、是非楽しみにしていて下さい。 最後になりますが、データサイエンスグループでは「活用価値のあるデータを創出」し「データを活用した新たな機能やサービスの研究開発」を加速して下さる シニアデータサイエンティスト(R&D) を募集しています。 興味を持っていただけた方は、 カジュアル面談 も行っていますのでお気軽にご連絡ください。 hrmos.co
エンジニアの志賀と申します。 LIFULL HOME'S の新築分譲マンション領域の開発を担当しています。 私の所属している開発チームでは、LIFULLのベトナム海外拠点であるLIFULL Tech Vietnam(以下LFTV)と協力しながら開発を行っています。 過去LFTVのエンジニアメンバーと仕事を進める中で、ブリッジエンジニアを通していました。 そのため開発者どうしはお互いをほとんど知らず、コミュニケーションもレビューのやりとり程度で、コミュニケーション量が不足していると感じていました。 相手のことを知る、理解しようとすることは円滑に業務を進めるための第一歩だと考えていたので、この課題を解消するために行った取り組みを2点紹介します。 英語での自己紹介会の実施 まず実施した取り組みは、開発メンバーどうしの英語による自己紹介会です。 あらかじめフォーマットを用意し、英語で事前に記入したシートを用い各自に発表してもらいました。 フォーマットとして、私たちのチームでは以下の項目を取り入れました。 自分の顔がわかる写真とアイコン ニックネーム、呼んでほしい呼称 趣味 経歴 得意なこと 仕事のやり方 大切にしている価値観 チームメンバーは自分にどんな成果を期待していると思うか? 自由記述 内容はチームによってカスタマイズして良いと思いますが、取り入れて特に良かった内容を紹介します。 お勧めの自己紹介項目 自分の顔がわかる写真とアイコン 直接会う機会がない場合、チャットツール等のアイコンと実際の顔が一致しないということは、よくあることだと思います。 顔がわかることで、コミュニケーションを取る際の安心感が変わってくると考えているので、この情報は可能であれば必ず入れたいポイントになります。 ニックネーム、呼んでほしい呼称 ニックネームで呼び合えると距離がぐっと近くなりますし、自己紹介中にもコメント等でニックネームを投稿することで話しやすい空気を作りやすかったため、この項目はとてもお勧めです。 大切にしている価値観 仕事を進めていく上で相手が大事にしているものを知ることは、コミュニケーションを取る際にも非常に重要だと思います。 この項目があることで、今後のやりとりを円滑に進める手助けになるかと思います。 また自己紹介会の進め方とお勧めの2つのTipsがあるので、そちらも紹介します。 自己紹介会のTips 画面共有時に一工夫する 自己紹介会をする上でちょっとしたTipsとして、自己紹介をする人は必ず自己紹介用のフォーマットを画面共有し、今話している部分を選択等でハイライトすることをお勧めします。 英語が得意ではないメンバーが多い場合話し手、聞き手両方とも慣れていないため、今話している部分が追いやすいようにしてあげるだけでも内容に集中できるかと思います。 相手の母国語を使ってみる 自己紹介中に、挨拶等相手の母国語を含めてコミュニケーションをとると、お互いの距離感が縮まりやすかったです。 私たちのチームではベトナム後を母国語としているメンバーだったので、以下のような単語を織り交ぜることができます。 おはよう:Chào buổi sáng(チャーオブオイサーン) こんにちは:Chào buổi trưa(チャーオブオイチューア) こんばんは:Chào buổi tối(チャーオブオイトーイ) お元気ですか:Bạn khỏe không(バン コエ コンプ) ありがとう:Cảm ơn(カムゥン) 朝会用のスプレッドシート改善 私の所属している開発チームの朝会では、以下のような流れで実施しています。 アイスブレ−ク タスクの共有 目標の数値の確認 共有事項 上記の中でタスクの共有をメインに実施しているのですが、これまではLFTV側のメンバーはブリッジエンジニアのみが参加し、LFTV側のエンジニアの状況を共有する形で進めていました。 そこでブリッジエンジニアなしでも、LFTV側のエンジニアが朝会に参加できるように、朝会でタスク共有する際に利用していた、Googleスプレッドシートに一工夫することで実現しました。 GOOGLETRANSLATEの利用 やったことはシンプルで、「GOOGLETRANSLATE」を導入したことです。 今までは事前に日本語でスプレッドシートに各自タスクを記入していたのですが、その隣のセルに「GOOGLETRANSLATE」を利用して日本語を英語とベトナム語で翻訳したものを表示するように変更しました。 そうすることで、記入自体は今までと同様のコストでブリッジエンジニアの助けなしでタスク共有が可能になりました。 具体的に私のチームでは以下のように関数を利用しています。 = if ( セル番号 ="" , " ※左の列をベトナム語と英語に翻訳する数式アリ " ,GOOGLETRANSLATE ( GOOGLETRANSLATE ( セル番号, " ja " , " en " ) , " en " , " vi " ) & CHAR ( 10 ) & " --------------------- " & CHAR ( 10 ) & GOOGLETRANSLATE ( S50, " ja " , " en " )) 上記以外に工夫した点として、アイスブレークの内容やシートに記載されているタイトル等にも、同様に「GOOGLETRANSLATE」を適用されるようにしたことです。 アイスブレークの内容自体は必ず伝えなければいけない内容ではないと思います。 ただ、逆の立場で考えた時に不慣れな言語で会話が成り立っていると疎外感につながってしまうと考え、少しでも話題を共有できるようにアイスブレークの内容も極力共有できるようにしています。 また、細かい部分ではありますがシート内のタイトル等も翻訳されるようになっており、どこを見ても複数の言語で確認できる状態になっていることで言語の壁を感じないような環境を目指しています。 実施したことは非常にシンプルですが、ツールをうまく活用することで、以前よりも言語の壁を感じることなくコミュニケーションをとることができるようになりました。 最後に 私たちの開発チームで上記を実施することで、海外拠点のメンバーと毎朝顔を合わせてコミュニケーションをとることができています。 まだまだコミュニケーションをとる上での課題はありますが、第一歩は踏み出せていると思うので、引き続き改善をしていこうしています。 最後に、LIFULL ではともに成長していける仲間を募集しています。 海外拠点のメンバーと仕事ができる、貴重な環境でもありますので、興味がある方はよろしければこちらのページもご覧ください。 hrmos.co hrmos.co
プロダクトエンジニアリング部の二宮です。 「 Qiita Night~企業における生成AI活用~ 」というオンラインイベントで、LIFULLの軽量化GAIチームとして、 リーダーの廣瀬さん と登壇させていただきました。 YouTubeも公開されているので、ぜひご覧ください! www.youtube.com 私は「LIFULLの内製生成AI基盤keelaiと普及戦略」というLT発表を担当しました。 www.docswell.com 個人的には、パネルディスカッションの中のみずほフィナンシャルグループのWiz Chatお悩み相談所の取り組みが気になりました。本部ビル内の食堂やカフェの中にプロンプトの悩みを相談をしているということで、自分たちも似た取り組みをした際に盛り上げ方を難しく感じていたので、細かいノウハウもお聞きしたかったなと思ってます。 その後の発展 実は発表の後にもいろいろと進展しています。発表前後に完成した機能で、発表できずに悔しい思いをしたものを書き残します。詳細な内容は、いつか各開発者から投稿してくれるはずです。 1つ目は小林さんの書いた記事の中の「今後の展望と課題」の部分にある内容です。ファイルごとにコーディングガイドラインを読み込んでレビューできるようになりました。これはコーディングエージェントと共通した設定ができ、一気通貫した開発体験が実現できています。「コアドメインでない部分はガイドラインを整備してAIエージェントに任せ、自分たちはコアドメインの開発に専念する」という未来を目指しての布石を打っています。 www.lifull.blog また2つ目として、まだ現時点で生成AIを利用しているわけではないのですが、browser-runnerというE2Eテスト機能も実装されています。これはChrome DevTools Recorder等で誰でも作れるテストケースが、リリースフローの各タイミングで自動で高速に実行されるというものです。将来的には自然言語でのテストケースの自動作成機能や、テスト失敗時の自動修復PRなどを構想しています。 LIFULLでは戦略DAYという、全体戦略や各部署の戦略を共有するイベントがあります。その中で、ほぼ全部署が生成AIを使った何かしらの取り組みを行おうとしていて、「もうこんなに時代は進んだのか」と驚きました。また、スライドの中の参考画像で、GPT-4oの画像生成も自然に使われるようになっていました。 最後に 最後に、LIFULLではともに成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは、プロダクトエンジニアリング部の鈴木です。普段はLIFULL HOME'Sの不動産会社向けプロダクト開発エンジニアのマネジメントを担当しています。 先日、社内ゼミ(勉強会)にて、障害対応時の心構えと障害対応訓練について共有しました。 このゼミは、プロダクトの開発・運用に関わるメンバーが抱える「もしシステム障害が起きたら、どのように対処すればよいのだろう?」という不安を解消し、適切な対応方法を学ぶ機会として開催したものです。 長年稼働しているシステムで有識者が少ない中、いくつかの障害を乗り越えてきた私たちチームの経験に基づいた障害対応について紹介します。 前編では障害対応についての基礎知識や心得について触れました。 www.lifull.blog 後編では、それらのスキルを実際にどのように身につけていくかに焦点を当て、チームで実施した障害対応訓練について話した内容をお届けします。 障害対応をどのように学んでいけば良いのか LIFULLでは様々な障害対応のマニュアルが用意されています。私たちのチームではこれらのマニュアルの読み合わせを行ってきましたが、実際の障害対応ではスムーズに動けないという課題がありました。これには主に3つの理由が考えられます。 障害対応のプロセスを頭で理解していても、実際に行動できるようになるには多くの課題があります。これらのスキルは実践を通じて習得できますが、実際のところ障害対応の場から学ぶことは簡単ではありません。 障害対応はあくまで問題を解決することが目的であり、学習の場ではないからです。さらに、緊迫した状況下での経験のみで全てのメンバーが成長できるとは限らず、全ての人が一定のレベルで障害対応ができるようになることは難しいと言えます。 実際にメンバーからも、「障害発生時にマニュアルを確認しようとは思い至らなかった」、「経験のない自分が役にたつと思えなかった」という声があがっていました。 それでは、障害対応はどのように学んでいけば良いのでしょうか。 障害対応訓練をやってみた 障害対応訓練とは『SRE サイトリライトアビリティエンジニアリング』では次のように説明されています。 新人は、ドキュメンテーションやポストモーテムを読んだり、トレーニングを受けることによってSREに関する多くのことを学べます。 本物のプロダクションシステムを実際に壊したり直したりすることで得られる経験はそれらに勝ります 私たちのチームでは、実践的なシナリオを通じて知識だけでなく、現場での対応力を高めることを目指し、障害対応訓練を実施しました。 障害対応訓練の流れ 事前準備 事前準備では訓練の目的、シナリオ、体制を決めます。 訓練の目的・概要: チームの課題に基づいて設定します シナリオ:発生する事象とその対応方法を事前に設定します 体制:障害対応メンバーと訓練運営メンバーに分け、役割を明確にします 訓練の目的は、日頃の障害対応や振り返りを観察し、チームが直面している課題に焦点を当てることをおすすめします。チームメンバーから何を課題に感じているかをヒアリングすることも効果的です。 例えば、私たちのチームでは以下のように目的を設定しました。 目的を設定した後は、訓練の概要を決めます。発生する事象の原因や影響範囲、訓練で対応をカバーする範囲を明確にします。私たちのチームが担当するプロダクトでは、障害対応時にはデータのリカバリが必要になることが多いため、それらを訓練に含めるかなどを検討していきました。 シナリオでは、発生し得る事象とそれに対して期待される対応を設定します。リアリティを出すために、実際と同等の開発環境で障害を起こし、訓練を行いました。 本番に近い流れができるように準備することで、実践的な訓練を実施することができます。 当日の実施 当日は、運営側からの不具合報告をきっかけとして、対応メンバーには普段どおりに、事象の確認・調査から対応を進めてもらいました。 訓練終了時間は先に決めておき、時間内にできるところまでとしたのもポイントのひとつです。 営業や広報部署なども社内ステークホルダー役は運営側で疑似役を担いました。 上長への報告・相談はもちろんですが、対応メンバーは社内ステークホルダーへの報告も行い、本番さながらの条件で訓練を実施しました。 また、記録係りは当日は休日をとっているという設定のもと、記録に徹するようにしました。 訓練中は、メンバーの様子をみながら、必要に応じて訓練運営側からサポートを行いました。 事後振り返り 訓練当日に記録した内容を元に、事実に基づいて振り返りを行いました。振り返りはKPTの形式で、障害対応と訓練自体とで振り返りの観点を分けて実施しました。 障害対応自体の振り返りの観点 いつ、なにが起きて、だれにどのような影響があったか チームの体制は適切だったか 適切に影響と原因の調査ができていたか 情報のキャッチアップ・共有・相談・報告は適切にできていたか ステークホルダーへの共有・連絡・相談は適切にできていたか 訓練のやり方・体制の振り返りの観点 役割の振り分けは適正だったか 記録係の動き方はどうだったか 障害対応の基本フローは体験できたと感じるか 振り返りを通じて改善点を明確にし、次回の訓練や実際の障害対応に役立てていきます。 まとめ 今回の社内ゼミ(勉強会)では、全体を通して障害対応時に重要なポイントとそれらをチームで実践できるようになるための訓練方法について共有しました。 訓練に参加したメンバーからは、「一度実施したことで自信になり、いざという時の対応力が向上した」「有事の際に自主性と積極性が高まった」などのポジティブなフィードバックがありました。 実際に、訓練以降に起きた障害対応でメンバーの対応スピードや自主性がアップしたことを私も実感しました。障害対応訓練を通じて、個人のスキルだけでなくチーム全体の結束も強化することができたと感じています。 障害対応の目的は、システムを直すことではなく、顧客への影響の低減・早期回復をすることです。特にエンジニアの場合、まずシステムを直すことに目が向きがちになると思いますが、大事なのは顧客のビジネスやユーザーに与える影響を軽減することです。 そのためには、企画とエンジニアが協力して対応していく必要があります。チームとして安定した障害対応を進めるために、ぜひ障害対応訓練を活用してみてください。 最後に、LIFULLでは共に成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは。フロントエンドエンジニアの根本です。 LIFULL HOME'Sのプロダクト開発とスポーツ関連の新規事業開発に携わっています。 今回、2024年度の人間中心設計専門資格認定制度に挑戦し無事にスペシャリスト資格を取得しました。 ここでは、試験のための事前準備とエンジニアとしてこの試験に挑戦した理由および人間中心設計(Human Centered Design=HCD)に取り組む意義について考えを整理しました。 人間中心設計専門資格認定制度とは? HCD-Net(人間中心設計推進機構)が人間中心設計に関して一定水準の能力を有する人材を専門家、スペシャリストとして認定する制度です。 その中で、HCDスペシャリストの受験資格は下記を想定されています。 人間中心設計の基本的な実務能力を持つ実務担当者 人間中心設計・ユーザビリティ関連の実務経験が2年以上 人間中心設計が主業務で5年未満の方や、デザイナーやエンジニアなどで兼務の方 このように、私のようなエンジニアも対象とされた認定資格であり、人間中心設計はエンジニアにとって必要なスキルの一つであることがわかります。 なぜ受験したのか 以前、 RESEARCH Conference 2024に登壇しました - LIFULL Creators Blog でも述べたように、UXエンジニアとして数年間にわたってHCDの取り組みを実践してきました。 この経験を第三者に評価されることにより、自分の知識とスキルの客観的な位置を確認し、キャリアの指針として信頼性を築きたいという思いから受験を決めました。 やっておくべきこと HCDスペシャリストの試験については公に多くの情報が出回っているため詳細は省きますが、私が試験準備として特に役立ったことを紹介します。 それは過去のプロジェクトの取り組みを「詳細に」記録しておくことです。受験時に提出するコンピタンスシートには、各大項目のコンピタンスを、3つの観点に分けて具体的かつ体系的に記述する必要があります。 目的と調査・評価設計の対象 体制と実施内容 工夫とアウトプット これまで参加してきたプロジェクトでは、HCDの目的や設計、その成果や課題を具体的な手法と関連付けて整理していました。具体的には以下の観点でドキュメント化し、管理していました。 調査設計(調査背景・目的、検証手法、検証対象、検証人数、検証期間) 分析設計(採用した分析手法、分析結果) ユーザー定義(採用したモデル化手法、活用したフレームワーク) デザイン設計(プロトタイプ・情報設計の考え方) 詳細にドキュメントを残していた理由は2点あります。   1. HCDの取り組みを正確に伝える 私たちの開発チームは多くのプロジェクト関係者が携わり、時にはメンバーが入れ替わることもあるため、情報共有が非常に重要です。   各種検証や分析プロセスを整理することで、チーム全体が一貫した一次情報を共有し、統一した理解を持つことを目指しました。   これにより、プロジェクト進行におけるコミュニケーションギャップを減らし、効率的な協力体制を築けるようになります。   2. 他チームへのHCDプロセスの伝達 具体的な手法や成果を整理しドキュメント化することで、他のプロジェクトや組織がこれを学び、参考にできることを期待しました。   私たちの開発チームはHCDプロセスにおいてまだ未熟な部分もあるため、基本的なステップも省略せず詳細に整理することを心がけました。   こうしたオープンな情報共有が、HCDプロセスの促進に少しでも寄与できればと考えています。   このようにドキュメントは他者への情報共有目的で作成しましたが、結果的に自身にとっても貴重な資産であることを改めて実感しました。 受けてよかったこと 受験を通して一番良かったことは、過去のプロジェクトでの経験を振り返ることで習得してきたスキルの確認と不足している知識の棚卸しができた点です。 具体的には、A6・A13のコンピタンスについて過去のプロジェクトでは十分に実践できず経験不足を実感しました。 A6. 新製品・新規事業の企画提案力(基本コンピタンス) ユーザー理解から生まれた視点、価値から生まれた新たなコンセプトを、関係者に提案し実現に向けて合意をとれる企画提案能力のこと アウトプットの例:ビジネスモデルキャンバス、ビジョン提案型デザイン手法、ピッチ資料、事業企画書、リサーチ分析結果 今回、ユーザーリサーチの結果を用いて企画提案する力もHCDの一部であることを初めて認識しました。直近の3ヶ月で実施した新規機能開発に向けたユーザーリサーチでは、このコンピタンスを意識し、ユーザーリサーチの結果を活用して提案を行うことに注力しました。 A13. 専門知識に基づく評価実施能力(基本コンピタンス) 人間中心設計(HCD)および関連する専門知識を用いて、製品・システム・サービスのユーザビリティ、ユーザーエクスペリエンス、ユーザーインタフェースなどの良し悪しの判断・指摘ができる能力のこと アウトプットの例:ヒューリスティック法、ウォークスルー法、タスク分析 A13に関しては、過去にユーザビリティ検証を何度も行ってきましたが、ヒューリスティック法やタスク分析などの体系的な方法論に基づいた分析には不十分であったと認識しました。今後は、ユーザーリサーチを進める際に最適な評価手法を事前に考慮し、実践していきたいと考えています。 エンジニアが取り組む意義 最後に、エンジニアとしてHCDに関する知識を持ちプロダクト開発に参加することの意義を考えてみました。 エンジニアは「どうやって作るか」という役割を担う上で、「なぜそれが必要なのか」という点を深く理解することが重要だと考えます。 ただ指示されたものを形にするのではなく、機能要件の本質を理解し、適切に足し算・引き算の提案を行うべきです。 足し算の提案:他にどのような実現方法があるかを提示する 引き算の提案:特定の機能が過剰な開発である可能性を示す そして、「なぜそれが必要なのか」という根拠を示すプロセスとしてHCDが存在すると考えています。「人間にとって使いやすいシステムとは何か」と考えていくプロセスに寄り添うことで、エンジニアはその過程で最適な解決手段を提案することができます。 結果として、ユーザー中心のプロダクト開発においてエンジニアが真の価値を創造する重要な役割を果たすことができるのではないでしょうか。 最後に LIFULLではともに成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
はじめまして、情報審査グループの山崎です。 今回は私たち情報審査グループが「データ」を仲介役として、エンジニア部門とともに協働したことをお伝えできればと考えています。 本記事ではコミュニケーションについてを軸にお伝えしますが、一般的な見解では無く私の個人的な感覚だけな点もあるのでご容赦ください。 エンジニアの方々に、私たちのような非エンジニアとのより良いコミュニケーションについて少しでも参考になれば嬉しいです。 関連して、エンジニア視点からの取り組み事例として以下の記事も併せてご覧ください。 情報審査グループの役割 取り組みの背景 情報審査におけるデータ活用 部門間連携での紆余曲折 認識のずれ タスク管理手法の違い チーム内での共通言語化 役割分担の明確化の重要性 他職種とのコミュニケーションでの学び 終わりに 情報審査グループの役割 情報審査グループでは「圧倒的な「安心」を提供する。」というビジョンのもと、以下のような業務を行っています。 ユーザーに向けて:掲載されている物件広告を正確に届ける 物件広告を掲載している不動産会社に向けて:正確な物件広告を掲載できるようチェックし問題があれば修正依頼をさせていただく 「募集が終了した物件広告を非掲載にする」という、いわゆる「おとり広告」対策に注力しており、2024年には第三者機関による調査では 物件鮮度No.1 を獲得しました。 取り組みの背景 LIFULL HOME’Sは「広告を掲載する”場”」を提供する不動産・住宅情報のポータルサイトであり、実際の物件を管理しているわけではないため募集中かどうかの情報を持ち合わせていません。 ユーザー等から「この物件は募集が終了している」という連絡を受けて、情報審査グループが電話で当該物件の管理会社に募集状況の確認をしていました。。 つまり、募集が終了した物件広告は、不動産会社が非掲載にする必要があります。 ですが毎日数百万もある物件に対して、人手のみでは対応できる件数が限られます。 そこで私たちは『データを活用して募集終了した物件を検知できれば、自動非掲載できるのでは!』とデータ活用に取り組むことになりました。 情報審査におけるデータ活用 とはいえ、当初は統一されたデータが無く、まずは物件毎に集計・分析できるデータ作りをゼロベースで始めました。 実際に物件を調査するメンバーと履歴の残し方から試行錯誤し、通報による調査や能動的な調査の結果等をデータ化することに成功した後、 より効率的に募集終了物件を非掲載にしようとエンジニアに協力をお願いする運びになりました。 以下、私たちがこれまでに実施してきた施策となります。 ・ 【ホームズ】管理会社との情報連携によるおとり物件対策  LIFULLが直接不動産の管理会社と物件データを連携することにより、LIFULL HOME'S上の募集終了物件を速やかに非掲載にする仕組みです。 ・ 【LIFULL HOME'S会員さま限定】メンテナンス見える化ツールの詳細 | LIFULL HOME’S Business 仲介・管理  特定の条件により募集終了物件を検知し、物件広告を掲載している不動産会社に確認を促すツールとなります。このメンテナンス見える化ツールを進化させ、募集終了物件をLIFULLが自動で非掲載にするシステムを開発しました。 ・ LIFULL HOME'S、自社開発AIによる「おとり物件」の検知・自動非掲載を開始 | 株式会社LIFULL(ライフル)  蓄積されている物件広告のデータや、独自に行っている調査結果のデータを自社開発したAIに学習させることで、募集終了物件を検知し自動で非掲載にするシステムを開発しました。 こちらも併せてご覧いただけますと幸いです。 不動産広告におけるDX化とLIFULL HOME’Sの取り組み このように私たちは以下のような大量のデータを活用し、日々募集終了物件を検知できるようになりました。 過去のデータ 現時点のデータ LIFULLに無いデータは協力会社から入手 部門間連携での紆余曲折 募集終了した物件の検知・自動非掲載を開始するまで、部門間でのさまざまな違いに戸惑いながら歩み続けてきました。 認識のずれ 当初はこんな会話から始まりました。 ■例1 開発メンバー:「SQLでデータ出しますね、クエリ教えてもらえますか?」 私たち:「(SQLとは何?)(クエリとは何?)」 ■例2 私たち:「これって不具合ですか!?(大変だ!)」 開発メンバー:「●●で■■な事象で起きている不具合ですね・・・!」 私たち:「(意外と落ち着いている!)」 例1の会話は「言葉(専門用語)の違い」、例2の会話は「温度感の違い」になります。 例1のように、システムについての会話では誰もがわかりやすい表現が使われる場合もありますが、専門用語が使われる場合もあります。 システムの専門用語を別の言葉に置き換えたりすると、エンジニアがデータを扱う際に何のことか迷い、かえって工数がかかったり、誤ったデータ処理を行ったりすることも考えられます。 ただ私たちは開発に不慣れなメンバーであったため、当初は認識を合わせるのに苦労しました。 例2についてのポイントは”視点”でしょうか? (私だけが感じているのかも知れませんが)大量のデータを中心に見ているエンジニアにとっては、私たちが思う以上にバグやエラーといったものに接していると思います。 逆に、私たちは『不具合なんてあり得ない!すぐ解決しないと!』と焦りがちで、エンジニアは『そう慌てないで』と見極める冷静さがあり、温度感の違いを感じることがありました。 タスク管理手法の違い エンジニア部門に何かお願いする際、簡単な内容でもJIRA等のタスク管理ツールを使ってチケット(証跡)で管理できる形で依頼していますが、私たちは『都度文章書いて、ステータス変更して面倒だな、、、』と思うことも正直いうとありました。 私たちの部署では口頭等でパパっと頼んで、サッと対応して解決ということもあります。 全てをチケット管理することに当初は手間を感じていましたが、この一見面倒なチケット管理が、やってみるとチームで物事を進めるのにいいことが分かりました。 いつ誰が何をどのように等の情報が人によって様々だったり、ずれやすい個々の意図を一致させる意思疎通を、フォーマット化されたチケット(データ管理)で行うことで明確になり、誰がみても同じ認識になるメリットを再認識しました。 記録に残れば言った言わないも防げます。 ■チケット管理するメリット 1. 情報の可視化 2. 工数管理がしやすい 3. 進捗確認がしやすい 4. 優先順位を付けやすい 5. 過去案件や類似案件を検索しやすい 6. 担当者が分かりやすい 等々 チーム内での共通言語化 あるエンジニアがチーム内の認識がずれないよう「言葉」について定義付けを行い表にまとめてくれたことがありました。 同様に、私たちもできる限りエンジニアと同じ視点でコミュニケーションできるよう、使う言葉や対応を揃えていくようにしました。 たとえば「賃料」や「面積」といったデータの列を指すとき、わたしたちは「項目」と呼んでいましたがエンジニア部門では ・BigQueryでテーブルのデータ構造を話す時には「カラム名」 ・AIモデル開発においては「変数」や「特徴量」 のように文脈に応じて言葉を使い分けていました。 お互いに齟齬が起きないよう、部門間で会話するときは「カラム名」で統一することにしました。 他にも「登録日」という言葉だけですと「物件の登録日」なのか?「データの登録日」なのか?分かりにくくなることがありますが、カラム名では「○○date」や「△△date」という言葉で明確に区別されているため、そちらの言葉を使うことにしました。 役割分担の明確化の重要性 また、私たちも簡単なクエリを書いたり、Excelで集計や分析ができるようスキルアップをしてきました。 そこで私たちは、エンジニアの手間を少しでも少なくするため「そのクエリなら私たちでも大丈夫です」とか「この集計はこちらでやっておきます」としたところ、逆に認識のずれが起こることもありました。 エンジニアが『この人たちは基本的な部分は大丈夫そうだな』と捉えてしまい、私たちが対応できなかったり理解できない部分までも「この点は皆さんであれば大丈夫だと思いますが」と説明を省かれてしまうことがあったのです。 『明確に分担した方がスムーズだったかも』と振り返った次第です。 他職種とのコミュニケーションでの学び 前述のような出来事を経て、あらためて私たちは以下の点について意識するようになりました。 専門分野の任せられるところは任せ、ゼロベースで話す 正確に伝えるため、使う言葉等の認識を合わせる システムやツール等の仕様をできる限り理解する 私は当初、良かれと思ってシステム等について把握している知識をエンジニアに伝えようとしたところ、逆に話がややこしくなってしまうことがありました。 今では専門家に任せられるところは任せ、私自身はゴール(やりたいこと、期日)をしっかり伝えた上で協力していくことが重要と考えるようになりました。 使う言葉については、何を指すか明確になっているシステムの専門用語に揃えるなど、言葉の認識を合わせることにより物事がスムーズにいくことが多かったです。 自分たちが使ってきた言葉の表現を変えることは、慣れないうちは戸惑うこともありましたが、今や「クエリ」という言葉を使わない日は無くなりましたし、慣れればなんてことはありません。 私たちがシステム等の仕様を理解することについては、細かい点まではともかく、システムが「できること」、「できないこと」といったポイントだけは理解すべきと考えています。 私たちのような非エンジニアは『人ができることはシステムもきっとできるはず!』『人ができないからシステムに期待している!』などと勝手な考えを持ちがちではないでしょうか? そのため私たちは、まずゴール(やりたいこと、期日)を曖昧にせず「しっかり伝え」、 専門外だから理解できないのではと考えるのではなく、お互いに認識が合うように「説明すること」が重要だと思います。 たとえばデータ抽出において「AのテーブルとBのテーブルから必要なデータ自体は結びつけることができます。ただしBのテーブルが膨大な量のため、時間と費用が大幅にかかってしまうんです。」といったエンジニアからの説明を聞いて、私は「技術的には可能だが、事実上できないこと」を理解した場面もありました。 その後チームで次善の策を考え解決させ、お互いできること(それが”説明力”だったり”聞く力”だったりすると思いますが)を丁寧に実行していくことができたと思います。 LIFULL HOME'S、自社開発AIによる「おとり物件」の検知・自動非掲載を開始 では、外部ベンダーとして 株式会社リーン・ニシカタ 様にお力添えいただきました。 その際もコミュニケーションロスが起きた場面がありましたが、使っているテーブルやカラム名を双方が理解できるようリスト化したり、コミュニケーションがうまくいくよう臨機応変に工夫しました。 おかげでリスト化後のミーティングやトラブルが発生した時はスムーズに対応することができるようになりました。 終わりに このようにチーム内で紆余曲折しながらプロジェクトを進め、前述のような施策を行えるようになりました。 こちらの理解力が至らない場面でも毎回「わかりにくく申し訳ありません」という言葉と共に根気よく説明してくれた方々は、神のように感じました! 「言葉の違い」や「温度感の違い」はそれこそ友人同士でも起き得るものだと思います。 大切なのは双方が歩み寄り、相手が何を伝えようとしているのか?を理解し合うことだと感じています! 最後に、LIFULLでは共に成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
プロダクトエンジニアリング部の千葉です。 LIFULL HOME'S不動産査定 と ホームズマンション売却 の開発に携わっています。 この記事では、売却査定サービスにおけるE2Eテスト高速化の取り組みについて紹介していきます。 売却査定サービスにおけるE2Eテストについて 環境 テスト観点 高速化の取り組み 背景 取り組み1 - 項目の精査と方法の最適化 取り組み2 - 処理の共通化 取り組み3 - ジョブの最適化 取り組み4 - 実行の並列化 取り組み5 - タイムアウト設定 結果 全体 Botでページを開いた際のHTTPステータスコード200の検証 モバイル端末でページを開いた際のHTTPステータスコード200の検証 PC端末での主要導線の確認 モバイル端末での主要導線の確認 まとめ 売却査定サービスにおけるE2Eテストについて 環境 売却査定サービスではE2Eテストを自動化するためのフレームワークであるTestCafeを使用したテストがデプロイ時と毎朝定時に実行され、実行結果がSlackに通知されます。 詳細については、こちらの記事をご覧ください。 www.lifull.blog テスト観点 以下の4項目についてテストを行っています。 Botでページを開いた際のHTTPステータスコード200の検証 モバイル端末でページを開いた際のHTTPステータスコード200の検証 PC端末での主要導線の確認 モバイル端末での主要導線の確認 高速化の取り組み 背景 E2Eテストの課題として、デプロイからテスト終了までの待ち時間の長さが挙げられました。 通常、テストの実行には30分以上かかり、場合によっては60分を要することもありました。 取り組み1 - 項目の精査と方法の最適化 主要導線の確認では通常、物件種別と所在地の入力から入力画面、会社選択画面、確認画面を経て、完了画面までの一連の流れをテストし、完了画面に遷移できることでテストの成功としていました。 この一連のフローで、各ステップにおいて次の項目を確認していることになります。 物件種別と所在地の入力を行うことで入力画面に遷移できること 入力画面で入力を行うことで会社選択画面に遷移できること 会社選択画面で会社選択を行うことで確認画面に遷移できること 確認画面から完了画面に遷移できること 物件種別や所在地に応じて入力画面の入力項目が変わるため、それに応じたテストをそれぞれ実施していました。 しかし、会社選択画面以降のプロセスは同じにもかかわらず、それぞれ完了画面までのテストを行っていたため、同様のテストを繰り返すことになっていました。 また、プロダクトコードの不備や画面読み込みの遅延で、必須項目が入力できず会社選択画面に進めない場合、以降のテストが実行できないという問題がありました。 さらに、TestCafeではユーザーの操作を模倣し、カーソルの移動、クリック、文字入力といった動作を通じてテストを行います。 そのため、1つのフォームの入力にも時間がかかります。 この問題を解決するために、以下のようにテストの役割を分割しました。 入力画面のテスト 入力画面で入力を行うことで会社選択画面に遷移できることを確認 会社選択画面のテスト 会社選択画面で会社選択を行うことで確認画面に遷移できることを確認 また、 会社選択画面のテストでは、入力画面での入力はDOM操作で行い、核心的なテストプロセスである会社選択画面ではUIテストを行うことで実行時間を削減しました。 各観点を独立したUIテストで実行することにより、物件種別と所在地の入力から完了画面に至る一連のフローテストはDOM操作を活用することにしました。 これにより、不必要なテストを排除するとともに、ユーザー操作のステップを省略し直接変更を行うことで、テスト実行時間の大幅な削減を実現しました。 取り組み2 - 処理の共通化 物件種別と所在地の入力では、たとえば「査定可能な会社が1社しか存在しないエリアにおいて、会社を選択しなくても確認画面に進める」というテストを実施したい場合、査定可能な会社が1社のみの所在地を入力する必要があります。 LIFULL HOME'Sをご利用の会員様(不動産会社様)の状況に応じて、各エリアの会社数は常に変動します。 このテストを実行するため、所在地の入力時には東京都⚪︎⚪︎区を入力し、会社数が2社以上の場合は次のエリア(東京都△△区)を入力し、さらに2社以上であればほかのエリア(東京都♢♢区)へと、期待した結果が得られるまで繰り返していました。 その結果、核心的なテストプロセス開始前の段階で削減可能な不要なコストがかかっていました。 さらに、この方法を複数のテストケースで同様に行っていたため、時間とリソースが無駄に消費されていました。 そこで、テスト開始前に会社が1社のエリアを特定し、そのうえでテストを行う処理を追加することでこの問題を解決しました。 取り組み3 - ジョブの最適化 4項目のテスト観点に対して、それぞれ1ジョブずつ合計4ジョブで実行していました。 ページを開いた際のHTTPステータスコード200の検証については、各3ジョブに分割し、主要な導線の確認は、UIテストとDOM操作によるテストの各2ジョブに分割しました。 これにより、全体で10ジョブとすることで1ジョブあたりの実行時間が削減され、全体の実行時間を削減しました。 一方で、ジョブが増えることでSlack通知が増えるという懸念もありました。 ジョブ1つにつき1つのメッセージがSlackチャンネルに投稿されていたため、自動テスト実行1回につき4つのメッセージが投稿されていましたが、これが10個になると情報を見逃してしまうリスクもありました。 そこで通知を改善しました。方法は以下の通りです。 CodeBuildのDOWNLOAD_SOURCEビルドフェーズ(テスト開始前にソースコードを取得するフェーズ)開始時に親メッセージを投稿する 各ジョブ終了時にそのメッセージのスレッドに返信を行う 失敗の場合はSlackの機能を用いて返信をチャンネルにも投稿する すべてのジョブが終了したら親メッセージを完了を示した文言で上書きする これにより、Slackチャンネルに流れるメッセージの数は減りつつも、自動テストの終了と失敗を見逃さないようなしくみにしました。 取り組み4 - 実行の並列化 1ジョブあたりの実行時間をさらに削減するために実行の並列化を行いました。 TestCafeの並列機能を使用して、複数のインスタンスを起動し、これらのインスタンス間でテストの作業負荷を分担することで実行時間を削減しました。 取り組み5 - タイムアウト設定 予期しない理由でビルドが途中停止してしまった際に60分経過するまで失敗通知が送信されないという問題がありました。 これはCodeBuildのタイムアウト設定を行っていなかったためです。 CodeBuildには設定した時間内にビルドが完了していない場合にビルドを停止する機能がありますが、何も設定していない場合はデフォルトで60分に設定されます。 タイムアウトを20分に設定することで、失敗が発生した場合でも迅速に結果を把握できるようにしました。 これにより、CodeBuildの実行にかかった時間に基づいた課金が軽減され、コストカットにもつながりました。 結果 取り組みの結果は以下の通りです。 実行時間には多少のばらつきがありますが、おおむね一定の時間で完了します。 実行時間を大幅に削減でき、高速化を実現しました。 また、E2EテストにかかるCodeBuildの月々のコストも約100ドルから約50ドルへと削減できました。 全体 改善前:31分0秒 改善後:11分45秒 Botでページを開いた際のHTTPステータスコード200の検証 改善前:13分23秒 改善後:7分59秒 モバイル端末でページを開いた際のHTTPステータスコード200の検証 改善前:13分53秒 改善後:8分2秒 PC端末での主要導線の確認 改善前:29分56秒 改善後:5分53秒 モバイル端末での主要導線の確認 改善前:20分6秒 改善後:5分39秒 まとめ 売却査定サービスにおけるE2Eテスト高速化の取り組みについて紹介しました。 最後に、LIFULLではともに成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
エンジニアの小林です。 LIFULLで社内ABテスト基盤を開発しています。その開発の中で社内の生成AI基盤「keelai」を活用した取り組みを紹介します。 www.lifull.blog LIFULLではkeelaiという社内向けの生成AI基盤プロジェクトを運用しており、今回はこのkeelaiを利用したコードレビュー用のGitHub Actionsを実装した事例を紹介します。 www.lifull.blog 内製AI Agentによるコードレビュー GitHub Actionsを活用して実現したAIレビューのワークフローは以下の通りです。 コードレビューの概要 GPTやClaudeなどのAPIを利用せず、内製AI Agent「keelai」を活用した理由は以下のとおりです。 社内固有の知識を活用可能 - 社内ドメイン知識を持ったレビューが可能です 継続的な改善サイクル - レビューActionsとAI基盤の両方を社内で改良できます 柔軟なモデル選択 - 用途に応じて複数のAIモデルを使い分けるなど高度なカスタマイズが可能です 以上の点から、内製のAI Agentを利用することに価値があると思っています。 keelaiは社内向けアプリケーションとしてSlack botとしての利用と各システムからAPIとして呼び出せる2つのインタフェースを提供しています。 今回の記事では、GitHub ActionsからAPIとして呼び出して活用する方法に焦点を当てています。 弊社ではkeelaiを中心にして、社内ドキュメント参照機能や営業支援など幅広い活用を進めています。 この社内エコシステムと各種モデルの進化を組み合わせることで、LIFULLの技術スタックやドメイン知識に最適化されたレビューを期待しています。 keelaiについての詳細は以下を御覧ください。 www.lifull.blog 開発の背景 端的に述べると「 人が足りなかった 」からです。 当時所属しているチームの開発を行いながら、サブタスクとしてABテスト基盤のデモ実装を行っていました。 同期エンジニア3名でフロントエンド、バックエンド、集計バッチの3領域を得意分野で分担実装していましたが、以下の課題がありました。 本業務の合間を縫っての開発作業 チームメンバーの得意分野が異なるため、不得意分野のレビューに時間を要する すべての変更を人間が詳細にレビューする時間的余裕がない そこで、生成AIを使って最低限の品質維持のヒントになるようなレビューをできないかと考えました。 2024年秋ごろから話題になっていたCode Rabbitという生成AIレビューのSaaSを個人開発で利用していたこともあり、技術的な好奇心からも実装に踏み切りました。 実装プロセス 2024年3月時点でGitHub ActionsからkeelaiのAPIを呼び出せる環境が整備されていました。 当時所属していたチームで簡単な実験を行いました。 サンプルコードのレビュー実験 しかし先述した通り、個人的にCodeRabbitを利用していたため物足りなさを感じていました。 CodeRabbitの特徴としては以下の点が挙げられます。 日本語によるレビューコメント コード内インラインコメント機能 ただのコメントで複数のレビューが並べられてしまうと、どのファイル・どの行に対するレビューコメントなのかが追いづらくなってしまいます。 そのため、インラインコメントでレビューが返ってくることは被レビュー者にとって大きなメリットです。 そこでOSSを利用して手早く高機能なレビュー機能を用意することにしました。 Code Rabbitが旧バージョンのAIレビューシステムを ai-pr-revewer としてMITライセンスで公開していたため、これをフォークしてkeelai対応版として再構築しました。 keelaiは社内からの利用を前提とした認証になっています。 そのため、エンドポイントとAPI Keyの組み合わせでは利用できず、認証を含むクライアントを自作して差し替え、社内リポジトリで利用できるActionsとしてリリースしました。 社内普及まで 2024年8月にリリースを行い、ABテスト基盤チームの中で利用を開始しましたが、この時はまだgpt-4oがリリースされたばかりのころでした。 micro serviceが大量に存在する弊社全体での利用はコスト面でまだ現実的ではないとして数個のリポジトリで利用されるにとどまりました。 しかし、2025年2月にgpt-o3-miniがリリースされ、安い!賢い!早い!の3拍子であったため、弊社のkubernetes基盤KEEL用CLIであるkeelctlで全社配布することになりました。 keelctlについては以下を参照ください。 www.lifull.blog 機能 現状 PR要約 PRの内容を要約することで、レビュー者の認知負荷を下げます。 PRの要約 インラインレビューコメント コード内の該当箇所に直接コメントを付与し、メンションを付けて返信するとチャット形式で対話可能です。 インラインレビューコメント 社内の評価 実際の運用から得られた評価をいくつか紹介します。 指摘内容:タイムアウト実装の漏れ 「リリース後の運用中に発生した障害で気付くことが多いものなので助かりました」 指摘内容:200レスポンス処理の重複 「人間が指摘するときは手間な気分になるので機械的に指摘してもらえた方が楽」 指摘内容:Goの型キャスト 「Goでinterface型の変数をキャストして利用する際の注意点を指摘してくれました。Goに慣れてない人でも危険なコードに気付けるのが良いと思いました!」 今後の展望と課題 LIFULLにおけるAIコードレビューの実装までの流れと、現状の機能について紹介しました。 現在のシステムをさらに発展させるため、以下の機能拡張を計画しています。 リポジトリ固有のコーディング規約対応 各チームのスタイルガイドに沿ったレビューの実現 リポジトリごとの設定ファイルによるカスタマイズ 言語・フレームワークに特化したレビュー 各技術スタック特有の「ベストプラクティス」に基づく指摘 新しいバージョンのAPIや推奨パターンの提案 クロスファイル分析 複数ファイル間の整合性チェック アーキテクチャレベルの設計レビュー この記事ではAIによるコードレビューについて触れましたが、keelaiや関連プロダクトは社内コードがオープンであるため、新機能を思いついたらすばやく拡張できる環境にあります。 KEELチーム(プラットフォーム)、keelaiチーム、プロダクトチームの有志が一緒になって自分たちのニーズを考え、既存コードを活かして最小限の修正で対応可能な環境が整っています。 最後に、LIFULLではともに成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
2024 年中途入社の福原です。 私の所属しているグループでは、毎週チーム全員で時間を設けて、メインとなるページのサイトパフォーマンスを監視・改善する活動を行っています。 今回はその活動の一環で、新築分譲戸建の物件詳細ページの LCP について、改善を行った結果を共有します。 LCP は、ビューポート内に表示される最大の画像、テキスト ブロック、または動画のレンダリング時間 課題特定 まずは Chrome の Lighthouse を使って計測し、数値の劣化原因を探りました。 指摘の中で最も影響が大きそうだったのは「適切なサイズの画像を使用していない」という点でした。 今回のページでは、カルーセル内に比較的大きい画像とサムネイル用の小さい画像を表示しています。 適切な画像サイズに修正することで、大きな効果が得られると考えました。 改善内容 適切な画像サイズの指定 LIFULL HOME'S には画像を適切なサイズに圧縮したり、WebP 形式に自動変換するしくみがありますが、 今回の対象ページでは使用されていませんでした。 www.LIFULL.blog 修正内容としては変数名の変更だけでしたが、それぞれに適切なサイズの画像を読み込むようになりました。 preload の削減 LCP の改善策として頻出する「 preload 」ですが、カルーセル内のすべての画像を優先的に読み込んでいたため、逆にページの初期表示が遅くなるケースがありました。 preload は 要素の rel 属性の値で、その HTML の の中で読み取りリクエストを宣言し、ページのライフサイクルの早期の、ブラウザーの主なレンダリング機構が起動する前に読み取りを始めたい、すぐに必要なリソースを指定することができます。 物件によっては 30 ~ 40 枚という大量の画像が表示されることもあるため、 優先して読み込むのは LCP の対象となる画像のみに絞ることにしました。 結果 今回の対応により、LCP が約 42%改善しました。 データ可視化ツールの Grafana で LCP を確認すると、ガクッと下がっていることがわかります www.LIFULL.blog 実際の画面を見ても画像の表示が速くなったと体感できるレベルでした。 まとめ 今回紹介した事例は、すでに提供されていた画像最適化に合わせたことが大きな改善要因のため、負債解消の意味合いが強いと感じています。 サイトパフォーマンスの改善はまだまだ継続していく予定のため、 次回は再現性のある事例を紹介できるよう、日々の業務に努めてまいります。 LIFULL ではともに成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは。エンジニアの高詰です。 LIFULLでは新卒入社2年目のエンジニアを対象にしたSET研修が開催されます。 SET研修は2年目の若手エンジニアが中心となりプロダクトをスクラッチから自分の手で開発することでWebエンジニアとしてステップアップすることを目的としています。 この記事では、研修での取り組みや研修中に直面した問題や学びについて紹介します。 SET研修とは 個人の目標設定 作成するサービスの検討 サービスの設計および実装 アプリケーション インフラ デプロイ 実装期間中のプロジェクト進行 実装期間を終えての反省点 学び 終わりに SET研修とは SET(Sophomore Engineers Training)は新卒2年目のエンジニア3〜4人チームでAWSや外部サービスを利用し、16営業日内に1つのプロダクトを作る研修です。期間及び予算は決まっていますが、それ以外はすべてチーム内で決める必要があります。 研修中のサポートは先輩エンジニアがメンターとなり開発相談やコードレビューを行います。他にも社内に数人いるベテランのスペシャリストエンジニアにいつでも相談できるようになっています。 SETは下記を目的として毎年行われています。 プロダクトを作る全段階を経験することでwebサービスの全体像を把握し、エンジニアとしての引き出しを増やす 業務では携わる機会がなかった分野に触れて幅広い能力を身につける 自分の苦手分野を正しく認識し、スキルセットのバランスを整える サポートメンバーのフォローを受けつつ、プロのエンジニアとしての知識を効率よく得る 研修の全体の流れは下記になっています。また、第一実装期間および第二実装期間は通常業務から外してもらえるので開発に集中できます。 個人の目標設定 作成するサービスの検討 サービスの全体設計 第一実装期間(8営業日) 第二実装期間(8営業日) 研修成果の社内発表 個人の目標設定 SET研修ではまず最初にメンバーそれぞれが研修を通して覚えたいことの目標を決めます。研修の目的に書いてある通り、通常業務では携わる機会がない分野や苦手な分野を中心に目標を設定することが推奨されています。 私の場合、通常バックエンドエンジニアとして既存のHOME'Sのプロダクトの改修や機能追加をメイン業務として行なっているので、個人目標には開発のベースとなる技術選定やインフラ部分に力を入れるような以下の項目を目標として設定しました。 技術選定及びインフラ設計の経験を積む デプロイの設定ができる サーバーの仕組みと設定が理解できる ミドルウェアを正しく実装できる 作成するサービスの検討 私たちのチームは交換日記サービスを選びました。 このテーマを選んだ理由としてはCRUDが満たせること、認証機能が必要であること、設計が複雑すぎないこと、メンバー全員の目標を満たせるテーマだったことです。 チームメンバーが3人いたので開発はそれぞれの個人目標を考慮し、インフラ及びサーバー担当、インフラ及びアプリケーション担当、アプリケーション担当の3つに分けました。私はインフラ及びサーバーを担当したので、開発期間前からサービス全体の設計の検討を行なっていました。 サービスの設計および実装 アプリケーション 今回開発したアプリケーションはMVC2パターンを採用しており、Expressを使用したモノリシックアーキテクチャで動作しています。 URLのルーティングに関してはDHH流のルーティングを参考にし、Controllerもそれに合わせて設計しました。 ModelはActiveRecordの考え方を参考にして開発しています。 View周りはPreactを採用しています。 今回はフロントエンド部分の開発を個人目標に入れているメンバーはいなかったので、View周りの設計やライブラリは最小限に抑えて他部分の開発により多くのリソースを割けるように調整しました。 データベースへのアクセスはTypeORMを利用することで型安全なデータ操作できるようにし、認証・認可には AWS Cognitoを活用してユーザー管理と認証を実装しました。また頻繁に必要になるデータはRedisを使ってアクセスすることでレスポンス速度を上げるように心がけてプロダクトを作りました。 インフラ AWS構成 インスタンスはシステム全体の管理のしやすさ及び再現性を担保するためCloudFormationを用いてリソースを構築しました。 サポートチームへ相談する際もgithub上でのコードを確認してもらうだけで済んだので効率よく開発を進めることができました。 設計面ではウェブサーバとアプリケーションサーバを分けることで、ウェブサーバのキャッシュ機能やプロキシ機能を使って効率よくリクエストをさばき、アプリケーションサーバに不要な負荷がかからないように注意しました。 静的ファイルはS3を使ってクライアントに返すことで高速化も図っています。 AWS CloudFrontを使う案もありましたが、時間の関係で今回は実装していません。 アプリケーションはDockerを使って開発していますが、勉強のためあえて便利なECSは使っていません。 ログ周りやインスタンスが落ちた際の設定、セキュアな環境変数の渡し方などの工夫が必要なEC2を用いてデプロイしました。 EC2で実行されているDockerが何らかの原因で止まってしまった時に再実行できるような設定や、CodeDeployなどのAWSサービスに対応できるように設定したAMIを作成し、インスタンスが落ちてしまった際も自動で早く立ち上がるための工夫もしています。 デプロイ 研修期間が短いので効率よく開発サイクルを回せるようにAWSのCode Pipelineを用いて自動デプロイを実現しました。 EC2を使っているためデプロイは以下の手順で進行します。 研修用に立ち上げたリポジトリにmainへのpushを検知すると自動的にコードをビルド ビルドに問題がなければECRへDockerイメージをプッシュ EC2インスタンスを新しく立ち上げ、Dockerイメージ及びAWS Secret Manager環境変数を取得し、イメージを実行 Blue/Greenデプロイで旧インスタンスと新インスタンスを入れ替える 上記手順に何か問題があった場合はデプロイ開始前のバージョンに切り戻す デプロイ結果はSlackに通知が届くように設定したので、研修中はコードの実装に集中できるようなりました。 実装期間中のプロジェクト進行 Github Projectの画面 プロジェクト進行のマネジメントはGithub Projectでやるべきことをissue化し、ガントを引いて管理しました。 実装期間前にひとり一人が担当する実装箇所や設計のすり合わせを行うことでスムーズに研修開始時と同時に開発を始めることができました。 実装期間中は毎日チームメンバーで10分ほどの朝礼を行いました。 前日の進捗及び当日やることについて報告を行うことで、何か困っているメンバーがいないかを確認し、期間中に終わらなさそうなものがあれば優先順位の変更を行い、メンバー全員の目標を満たしつつ最低限動くものを提出できるようにタスク調整しました。 また、第一実装期間と第二実装期間の間にメンバー内で振り返り会を行うことで開発中に起こった問題点を洗い出し、改善策を一緒に考えることで第二実装期間のタスクマネジメントをより良い方法で行えたと思います。 実装期間を終えての反省点 個人的な反省点は大まかに以下の3つです。 要件定義や画面構成の認識合わせが足りていなかった点があったので、詳細なところまで文章化するべきだった 何を決めるにしても明確な数字を出すべきだった タスク見積もりが甘く、思ったより時間がかかることが多かった ①に対しては口頭で話し合っていても各自のイメージが違っている可能性を考慮していなかったことです。 忘れることや記憶違い、認識違いが発生していたので実装開始前のサービスの検討のフェーズでメモだけではなく詳細にプロダクトのイメージを文章化すれば避けられた問題だと思いました。 ②については①の問題と関わってくるのですが、何か依頼する際には明確な数字を示さなかったことが原因でチーム内で問題が起きました。 「多め」や「なるはや」などの曖昧な言葉の解釈は人によって違うので、定量や日時を明確にした上で進めるべきでした。 ③に関してはお互いに初めて使うツールや設計で開発したので、開発期間で実際手を動かしてみると想定した以上に時間がかかることが多発しました。 タスク分割が大きいということに気づけないまま実装期間に進んでしまったので、極端に負担がかかり過ぎているメンバーが生じたり、期間内に終わらせることができないことが途中で発覚するなどの問題が起きました。 この問題についてはプロダクトマネジメントを経験しないとわからない部分だと思うので今回は大変貴重な勉強機会でした。 研修で問題点に気がつけたことで本業務ではもっと精度が高いスケジュールの見積もりができるようになると思います。 学び 本業務ではあまり携わる機会のなかったAWSの各種サービスを活用し、インフラ構築やデプロイフローの設計を経験することができました。 これによりWEBシステム全体の管理や運用、プロジェクト進行の経験と知識を身につけることができました。 また、普段の業務ではどれだけ整備された環境で開発しているのかを体感することができました。 結果としては最初に目標で設定していたことは研修を通してすべて達成できました。 開発のベースとなる部分の設計と実装を担っていたので、他のメンバーの実装に支障が出ないように速度と正確さを求められるポジションだったと感じました。 特に技術選定に関しては最後まで悩むことが多く、自身のアプリケーションの特性を深く理解した上で各技術のメリット・デメリットを考慮し、選択する必要があることを痛感しました。どんな技術を選んでもデメリットは必ずついてくるものなので、与えるインパクトを十分に理解した上で許容するのか、対策を考えるかなどのアクションが必要になってくると思います。 AIが発達し、自動でコードを生成してくれる今ではエンジニアは自分が作っているアプリケーションが持ちうるリスクを想定し、考慮した上で対処方法の設計できるかが大切なんだろうなと思った次第です。 終わりに この記事ではSET研修で取り組んだことや反省点と学びについて振り返りました。 周りについて行くことに必死だった1年目が終わり、ようやく自分が好きなことや興味があることが明確になってきた2年目のタイミングでSET研修に参加できたのは大変貴重な経験でした。 メンターとサポートチームに幾度も助けられながら大幅に成長できた2週間超でした。個人的には設計や実装に悩む時間を思う存分に楽しめたのでSETに参加して本当に良かったです。 一人前のエンジニアとして活躍できるようにこれからもインプットとアウトプットを重ねてスキルアップしていきたいです。 最後になりますが、LIFULLでは一緒に働く仲間を募集しております。よろしければぜひこちらのページもご覧ください。 hrmos.co hrmos.co
エンジニアの市川と申します。 LIFULL HOME'S の売買領域の開発を担当しています。 さて、皆さんは普段からコードレビューをしているでしょうか。 私たちLIFULL HOME'Sのエンジニア組織では、他者のコードレビューを通過しないとリリースできないというルールを設けています。 そんな中、コードレビューがボトルネックになってしまったアプリケーションがあり、それを解決するために行った取り組みを3点紹介します。 課題 1. 昇格ルールの明文化 問題点: コードオーナーに求められるスキルレベルがわからない 解決策 2. 昇格状況の可視化 問題点: 昇格プロセスの進捗が不明確 解決策 3. 昇格課題の用意 問題点: レビュー経験を積む機会が少ない 解決策 まとめ 最後に 課題 今回対象としたのは、近年基盤を刷新したアプリケーションのコードレビューです。 www.lifull.blog リプレイスによってアーキテクチャや言語、フレームワークが刷新されたことで、開発しやすくなりました。しかし、設計思想を深く理解しレビューできるメンバーが限られてしまいました。 初期段階では開発メンバーが少なく、レビューの頻度も低く問題になることはありませんでした。しかし、開発案件の増加や組織拡大に伴い、レビュー待ちのプルリクエストが目立つようになってきました。 また、弊社でも GitHubのコードオーナー機能 を利用しており、重要なビジネスロジックを追加・変更するような箇所についてはコードオーナーによるレビューを必須化することで品質を管理しています。 したがって、人数を増やせば良いというわけではなく、人数と質の両面のバランスを考慮した対策が必要になりました。 ここからは、メンバーがコードオーナーに昇格するまでのプロセスの見直しと改善について説明します。 1. 昇格ルールの明文化 問題点: コードオーナーに求められるスキルレベルがわからない まず、どのような状態になればコードオーナーになれるのかが不明確でした。 これまで初期の実装に関わったエンジニアをそのままコードオーナーとしていたため、明文化する機会がありませんでした。 解決策 現コードオーナーと開発リーダー間で議論し昇格ルールを作成しました。 そしてその昇格ルールを、開発しているGithubリポジトリのドキュメントに追加しました。 コードオーナーに求められるのは「実装能力」 だけでなく、「設計思想から逸脱した実装を見逃さず指摘できる能力」 です。その点を満たしているかを確認できるプロセスにしました。 昇格ルール(一部) 結果、いつでも確認できる場所に昇格ルールがあることで、昇格候補者にとって目指すべきゴールが明確になりました。 2. 昇格状況の可視化 問題点: 昇格プロセスの進捗が不明確 次に、現状自分や周囲が昇格プロセスの中でどの段階にいるのかが不明確でした。 そのため、あとどの領域のレビューを何回経験したら良いかがわからず、ネクストアクションが立てづらい状態でした。 解決策 現状自分がどの段階にいるのかを可視化するため、昇格状況のチェックリストを作成しました。 下記のように、各自の状況を一覧にした表を作成しました。 コードオーナー昇格に向けたチェックリスト 私の担当するアプリケーションでは、クリーンアーキテクチャやDDDといった手法を採用しているため、それらの経験を積むことが重要です。 また、LIFULL HOME'Sの知識もドメイン知識として必要ですので、項目に入れています。 導入してみた結果、自身に必要な経験やスキル不足が明確になっただけでなく、誰にどのレビュー案件を担当させるべきか、管理側としても判断しやすくなりました。 3. 昇格課題の用意 問題点: レビュー経験を積む機会が少ない ルールの明文化と自身の現状が可視化されたため、あとはレビュー経験を積むだけです。 しかし、都合よくレビュー依頼が来るとは限らないため、待っているだけではレビュー経験を積むのに時間がかかりすぎます。 解決策 レビューの経験を積むという観点では、対象が新規実装である必要はありません。そのため、リリース済みのプルリクエストの中から学びの多そうなものを選定し、それらをレビューの課題として再利用することにしました。 再利用したいプルリクエストの番号と、レビューコメントの直前のコミットハッシュさえわかれば、当時のプルリクエストと同じものを作成できるスクリプトを用意しました。 # 引数でPR番号とレビューを始める前のコミットハッシュを受け取る PR_NUMBER = $1 END_COMMIT_HASH = $2 # 一時ディレクトリを作成し、そこにクローン TMP_DIR = $( mktemp -d ) git clone $REPO_URL $TMP_DIR cd $TMP_DIR # PRの最初のコミットハッシュを取得します FIRST_COMMIT_HASH = $( gh pr view $PR_NUMBER --json commits --jq ' .commits[0].oid ' ) # 親のコミットハッシュを取得します PARENT_COMMIT_HASH = $( git rev-parse ${FIRST_COMMIT_HASH} ^ ) # 日時をフォーマットします(例: YYYYMMDDHHMMSS) TIMESTAMP = $( date + " %Y%m%d%H%M%S " ) # ベースブランチと子ブランチの名前を作成 BASE_BRANCH = " practice-base- ${TIMESTAMP} " CHILD_BRANCH = " practice-child- ${TIMESTAMP} " # ベースブランチを作成してプッシュ git checkout -b $BASE_BRANCH $PARENT_COMMIT_HASH git push origin $BASE_BRANCH # 子ブランチを作成してプッシュ git checkout -b $CHILD_BRANCH $END_COMMIT_HASH git push origin $CHILD_BRANCH # 新しいPRを作成 gh pr create --base $BASE_BRANCH --head $CHILD_BRANCH --draft --title " Practice PR ${TIMESTAMP} " --body " This is a practice PR. Based on PR # ${PR_NUMBER} . " こちらを昇格課題として利用し、コードオーナーがピアレビューを行うことで、昇格候補者が経験を積むことができます。 結果、事業状況により改修案件の頻度や粒度が異なる中で、昇格候補者が均等に経験を積める環境を整えられました。 また、「中規模以上のレビューを経験する」というルールにしても定義が難しいですが、コードオーナーが選定したプルリクエストを使うことで問題は解決されます。 まとめ 今回は、設計やコードの品質を保ちながら開発スピードを維持するための、コードオーナー増員の取り組みを紹介しました。 このしくみを活用して昇格課題に取り組み、私は先日コードオーナーに昇格しました。 また、もう1名昇格間近な若手メンバーがいます。 年次や役職によって役割を与えるのではなく、明確な道筋を整えて若手でもコードオーナーを目指せるようにしたいと考えています。 そして、このようなしくみはコードオーナーだけでなくレビュアー育成にも応用できると考えています。 最後に 最近ではAIによるコードレビューの精度も高くなり、そのうち人によるレビューがほぼ不要になるかもしれません。 そうなればレビューする機会が減るだけでなく開発者が実装に集中する時間が増えるので、より開発サイクルが早くなりそうですね。 最後に、ともに開発フロー改善に取り組んでくださる仲間を募集しています。 hrmos.co hrmos.co
こんにちは!LIFULLクリエイターの日運営委員のホです。 社内のモノづくりイベント『創民祭』が開催されましたので、その様子を共有させていただきます。 創民祭とは? 創民祭(そうみんさい)とは、業務や「クリエイターの日」、プライベートで創った物など、LIFULL社員が作ったプロダクトをお酒を飲み、ピザ・寿司を食べながらお披露目するイベントです。近年はWebに限らず、VRやイラスト等、多種多様なプロダクトが展示されています。今回はブース出展とLT(ライトニングトーク)の2本だてで開催いたしました! 前回の様子はこちら https://www.lifull.blog/entry/2024/03/18/170000 展示内容 前回同様、Webに限らずいろんなプロダクトが展示されました。以下に展示内容をご紹介します。 ここでは選抜された受賞2作品を紹介していきます。 ChatGPTと作った生活管理アプリ こちらのアプリは成果生活習慣を管理するアプリです。現代社会において生活を乱す一番の要因はスマホでしょう。スマートフォンの使用の管理&身につけたい習慣を達成できた日を可視化することで利用者により良い習慣を持たせることが可能なアプリです。開発では画面設計をしスクリーンショットをChatGPTに渡しただけでほぼ全て作ってくれたそうです。 iOS VoiceOver対応 VoiceOver機能を使ってお気に入り画面で以下の機能を提供できました。 物件カードの内容が読み上げられる ボタンの位置が見えなくても、VoiceOverを頼りにお気に入りボタンやチェックボタンをタップした時と同じ操作ができる VoiceOverはアプリのアクセシビリティを向上する上で欠かせない機能ですが、HOME'Sアプリではほぼ対応されていないのが現状でした。そこでVoiceOverについて学習し、実際に対応することでアプリのアクセシビリティ向上へ向けて動き出せました。 ライトニングトーク ライトニングトーク(LT)は3~5分程度の短い時間で発表するプレゼンテーションで、今回の創民祭でも実施されました! 世界のA/Bテスト基盤 こちらは社内のA/Bテスト基盤の担当の方に世界のA/Bテスト基盤の主流や動向について共有させてもらいました。 ChatGPTで作る生活管理アプリ ブース出展の担当者 から作った背景や今まで使った生活管理アプリについて共有させてもらいました。 keelaiでチームのどこを効率化できる こちらはLIFULL内製のAIツールであるkeelaiについて簡単な紹介と事例について共有させてもらいました。社内情報検索効率化や定型作業の自動化の事例を紹介いただきました。 最後に 創民祭、その一部をちょっとだけお伝えしました! そんなLIFULLでは、一緒に働くメンバーを募集中!新卒も中途も絶賛採用中です。ご応募お待ちしてますので、ぜひみてください! hrmos.co hrmos.co
こんにちは、LIFULLでシニアエンジニアをしている渡邉です。普段はLIFULL HOME'Sの流通領域のエンジニアチームにて、マネジメントをしています。好きなE2EライブラリはPlaywrightです。 今回は、LIFULLで取り組んでいるリリース承認の仕組みを変更し、自動化を図った取り組みについてお伝えします。 はじめに 皆さんは、普段の開発フローを一度俯瞰してみたことはあるでしょうか? 開発からリリースまでを通して見直してみると、思いがけないフローがボトルネックになっていることはありませんか? 普段やっている決まった手順が「会社の規定だから」「昔からやってきた習慣だから」と、そのまま受け継いでいるプロセスほど、意外な手間やムダが潜んでいるものです。 そこで今回は、LIFULLにおける課題感を踏まえつつ、私たちが開発フロー全体を見返す中で明らかになったリリースフローの課題に注目し、我々がサイクルタイム改善に向けてどのように手順の変更、自動化へ向けて改善を行ったのかを紹介します。ご自身やチームのリリースプロセスを見直すきっかけになれば幸いです。 前提の説明 まず、変更前のLIFULLのリリースフローについて簡単に紹介します。 リリースフローの流れ 開発者はレビューやテストなどすべての手続きを終えたタイミングでチケットを作成し、そこからリリース承認を依頼します。 リリースには上長の承認とPJ管理者の承認が必要です。 承認されたチケットをリリーサーが最終確認し、開発者以外の視点で内容をチェックしてリリースを行います。 JIRAでのチケット管理 開発、レビュー、デザインチェック、テストなど全作業工程をJIRAで管理していました。 開発者はGitHubとJIRAの両方を併用していた形です。 適用前のリリースフロー 開発フローの変更にどう踏み切ったか 我々はサイクルタイムを加速させるべく、VSM(バリューストリームマッピング)で普段の開発フローを客観的に可視化しました。すると、開発終了からリリースまでの間に時間がかかっていることが分かったのです。 バリューストリームマッピングの結果 リリースチケットの課題 リリース承認時に作成していたリリースチケットには、リリース対象のPRのURLしか記載されておらず、テストやデザインチェックの状況など、関連するチケットへ遷移できる情報がありませんでした。 PJ承認を行う企画チームはGitHubのアカウントを持っていなかったため、実際にリリースされるパッケージが何なのか分からない状態に陥っていました。 手作業確認の負担 承認者はリリースに必要な情報を、チケットやPR、時にはSlackをたどりながら、目視で確認しなければなりませんでした。そのためチェックに大きな手間と時間がかかっていました。 余計な業務の発生 弊社ではリリースチェックをリリースチケットに記載する運用を行っており、チケットに不備があった場合、リリーサーが開発者へリマインドしていました。 さらに、実際のリリースはGitHubで行うものの、確認はチケットで行うなど状況がチグハグでした。 リリーサーは当日のリリース対象PRすべてについて上記作業を行う必要があり、非常に時間と手間がかかっていました。 解決策とその効果 これらの問題を解決するために、私たちはリリースに関わる一連の作業をGitHubへ集約することにしました。 実施したこと チケットを廃止し、すべての情報をPRとIssueにより管理 リリースチケットを廃止し、GitHubに集約することで、リリースに関係する情報がすべてPR上で確認できるようになりました。 PR作成時に自動的に関連Issueが作成され、リリースに必要なテスト仕様書などのプロジェクト管理のものをまとめてタスク化でき、開発者の手順漏れがなくなりました。 今までサービス企画職(PJ承認者)はGitHubアカウントを持っていないため、リリースパッケージが何かわからない状況でリリースすることになっていましたが、集約に伴い作成していただき、すべての情報が開示できる状態になりました。 GitHub Actionsを使ってリリースに必要な情報のチェックを自動化 自動的にチェックをしてくれることで、開発者はリリースに必要な情報やコメントがそろっているかを事前に確認したうえで、承認者へ確認を依頼可能に。承認者のチェックに要する時間が減りました。 正しい承認者からの承認であることを、GitHub ActionsとGitHubのチームを用いて検証を行い、権限のある人からの承認であることを担保できるようになりました。 Issue側に用意したタスクがすべて完了しているかもCIで担保できるので、承認者は開発者が手順にのっとって開発をしていたことが一目で分かります。 GitHub Actionsにより定刻チェックを自動的に実施し、不備があれば開発者へ連絡できるようになりました。そのため、リリーサーの作業が大幅に削減されました。 具体的には、CIで一部の人間が行う確認を自動化し、人間の思考に頼る部分を最小化するフローに変更しています。 GitHub集約後のリリースフロー 承認者やリリーサーは、作業やチェックに責任が伴うので、作業量以上に心理的な労力を要します。特に、情報が一ヵ所に集約されていないと、必要な情報をたどるだけでも予想以上に時間を費やしてしまいます。しかし今回の変更により、その一部を機械的に事前確認できるようになり、心理的ハードルを下げられました。 その結果、2024年10月時点と比較して、リリースまでのリードタイムの中央値と平均値に効果が現れ始めました。特にリリース中央値はもともと50時間ほどかかっていたものが安定して20時間台にまで削減されています。 リードタイム可視化 また、リリース回数についても2024年10月から2025年2月末までの結果を見るとリリース回数が純増できました。 リリースデプロイ回数 リードタイムの平均値は休日を挟むリリースパッケージが多くなると待機時間が増えがちになり、まばらな結果になりますが、さらなる開発の数が増えれば改善がよく見えるようになると想定されています。 まだまだ、改善点や指標の取り方に変更が必要ですので、さらにアップデートを行う予定です。 解決に対して考えることと注意点 このような大規模な開発フローの改善を行う場合には、現行の開発フローがどういう意図で作成されているのかを一つ一つ 紐解き、フローにおける最大要求と最低限の担保方法を洗い出す必要があります。 特に、社内事情には十分に注意しつつ調整しましょう。 我々も、手始めにこのフローを実現するにあたって、社内の有識者への担保事項やフロー変更に基づく注意点の確認やものづくりを行うすべてのメンバーへの事情説明を行った上で理解を得て進めています。 こういった会社の常識を変える時には常に守破離を意識して丁寧に実施しましょう。 今後の展望 今回のリリースフローの改変によって、GitHubへの集約を実現し、リリースを加速する基盤を整えることができました。次の段階としては、ここで得られた自動化の基盤を活用し、リリーサーを不要にするリリースの自動化へと進める計画です。今後さらにリリースを高速化し、サイクルタイムを短縮していきたいと考えています。 終わりに 私たちは「価値創造を加速させ続ける」ことをビジョンの一部に掲げ、その実現に向けて開発フローの可視化と数値化に取り組んできました。今回メスを入れたのはリリースフローでしたが、実際にはほかにも多くの課題が隠れているはずです。 「しかたがない」と思っているポイントこそ、変化の余地があるかもしれません。 開発フローにおけるそれぞれの構成要素をあらためて見直し、最低要件を確認してみることで思わぬ改善の糸口が見つかる可能性があります。 今後も一歩ずつ改善を積み上げることで、リリースのさらなる加速化につなげていければと思います。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは、LIFULL HOME'S事業本部CSユニットサービス開発グループの吉田です。 先日、社内ゼミ(勉強会)にて、障害対応時の心構えと障害対応訓練について共有しました。 このゼミは、プロダクトの開発・運用に関わるメンバーが抱える「もしシステム障害が起きたら、どのように対処すればよいのだろう?」という不安を解消し、適切な対応方法を学ぶ機会として開催したものです。 長年稼働しているシステムで有識者が少ない中、いくつかの障害を乗り越えてきた私たちチームの経験に基づいた障害対応について紹介します。 全体の理解を深めていただくために、本ブログは前編・後編の2つに分かれています。 まずは障害対応についての基礎知識や心得をお話した前編をお届けします。 障害対応はなぜ重要か 障害対応は初動が大事 障害対応の初動をスムーズに進めるための4つの心得 1. 体制をつくる 2. 情報をオープンな場に集約する 3. 適切なコミュニケーション 4. ステークホルダーへの連携 まとめ 障害対応はなぜ重要か プロダクト開発において障害は、ユーザー体験やサービス品質に大きな影響を与える重要な課題です。 障害をゼロにすることは理想的な目標ではあるものの、現実的には避けられません。 では、なぜ障害対応は重要なのでしょうか。私たちが考える以下の3つのポイントを改めて伝えました。 第一に「顧客満足度の向上と維持」です。 障害が発生し不利益を受けるのはサービスを利用しているユーザー、つまり私たちの顧客です。迅速にサービス復旧の対応をし、顧客不満を少しでも抑えることが必要不可欠となります。 次に「信頼の確保とブランドイメージ」の保護です。 障害時の対応が良ければ、顧客はその対応力を評価し、企業への信頼を深めます。個人ではなく、企業として適切に対応することが大切です。 最後に「成長と改善の機会」です。 障害対応のスキルはもちろんですが、起きたことを振り返り、同じ問題が再発しないように次につなげていくことで、組織としての知見も溜まりLIFULL全体の成長につながっていきます。 組織の成長の機会であると考え、前向きに取り組む姿勢も大切な観点となります。 障害対応の目的はシステムを直すことではなく、顧客への影響の低減・早期回復をすることです。 不具合の先に顧客がいることを忘れず、組織として適切に対応する必要があります。 ゼミでは他社事例も紹介しながら、障害が発生したときにどう対応するべきかを説明していきました。 障害対応は初動が大事 LIFULLでは障害対応のマニュアルがあり、基本的な対応フローや緊急度の判断フローが用意されています。 ※関連して、障害管理の詳細ついてはこちらの記事にも紹介していますので関心のある方はぜひご一読ください。 www.lifull.blog このゼミでは障害対応マニュアルに沿った基本的な対応フローを改めて紹介しました。検知・報告受領からふりかえりと再発防止策までの7ステップが障害対応の基本的な流れとなります。 この中でも障害対応はステップ1~5までの初動が非常に重要となります。 まずは影響を最小限に留めるために止血する(=システムを正常に戻す)ところまでが時間との勝負です。 通常のPJよりも冷静かつ的確な対応を求められるので難易度も高くなります。 障害が起きると、つい原因を追求しがちですが、最初に把握するべきは顧客にどのような不具合が起きているか?です。 システム目線ではなく顧客目線での影響範囲の把握が重要となります。 また、サービス停止などを伴う大規模なシステム障害の際は、原因追求よりも早期復旧が最重要であることを意識しておく必要があります。 障害対応の初動をスムーズに進めるための4つの心得 障害対応を円滑に進めるために注意するべきことは何でしょうか。 わたしたちのチームが数々の障害を乗り越え、ふりかえりを実施してきた中で大事にしている4つのポイント(心得)があります。 1. 体制をつくる 特に大規模な障害対応では、明確な役割分担が不可欠です。 障害発生時に迅速に対応チームを編成し、作業者と報告者、最終意思決定者などを区分けしていきます。最初に役割を決めて明確にすることで各メンバーが自分の役割に専念し、全体の対応スピードをを高めることができます。 TODOを整理してタイムラインを決めると、より効率的に対応できるようになります。 統制役がいないと各自がバラバラに動いてしまうので、緊急度の判断がうまくできず、結果、対応に時間がかかったりしてしまいます。 障害対応はサービス目線での判断や広報も必要になるため、企画とエンジニアがワンチームとなって対応していくことが重要です。 2. 情報をオープンな場に集約する 情報の透明性を保ち、一元化することが鍵です。 LIFULLではSlackを利用していますが、影響範囲の広い障害発生時には専用のSlackチャンネルを作成し、全関係者がリアルタイムで最新情報にアクセスできるようにします。 障害の緊急度合いや影響範囲などの情報を集中的に管理して、どのメンバーもすぐに情報をキャッチアップできる状態を作ることで、無駄なコミュニケーションコストを削減します。 一部の人だけが知っている状態を避けるためにも、情報はできるだけオープンな場でひとつに集約することが大切です。 3. 適切なコミュニケーション 障害対応は時間との戦いであり、都度の状況変化を適時に共有することが求められます。 定期的に状況を共有することで、情報伝達の遅延を防ぎます。 そのため誰でも発言しやすいような雰囲気作りも重要となります。 障害対応は焦りや不安から思考がネガティブになりがちですし、普段よりも判断力が落ちることが発生しやすいです。 前向きに障害対応に向き合えるマインドを保つためにポジティブワードを発信することを心がけながら、気軽に相談できる雰囲気づくりを意識しています。 また、テキストコミュニケーションは負荷も高くなるため、Slackのハドルミーティングも積極的に活用するようにしています。 職種間コミュニケーションにおける注意点としては、「サービス目線で話す・聞くことを心がける」です。 不具合の調査や復旧にあたるエンジニアはシステム目線でプロダクトの対応にあたります。 企画はエンジニアからの報告や相談をサービス目線で話し・聞くことで、ユーザーや顧客目線で何が重要か?の認識を揃えていくことができます。 私はエンジニアではなく企画職のため、特にこの視点は大切で、障害対応において意識しているポイントであることを話しました。 4. ステークホルダーへの連携 不具合が発生していることをステークホルダーへ共有することは重要なポイントとなります。 影響を受けている顧客はもちろんですが、営業部門など直接顧客と接する社内ステークホルダーへの報告も忘れてはいけません。 迅速で的確な連携が、ステークホルダーからの信頼を維持するための鍵となります。特に、影響の大きい障害が発生した際には、障害検知の第一報を即座に関係者に知らせることが大切です。 広報や共有が後回しになっては、顧客だけでなく社内からの信頼を失うことに繋がりかねません。 まとめ 私たちのチームでは以上のことをチームメンバー全員が意識し、障害対応に取り組んできました。こうした体験や取り組みの紹介を通じて、組織全体の対応力の向上を目指していきたいと思います。 ゼミの参加者からも「障害対応について深く学びを得ました。」「障害対応の意義や意味について改めて認識することができた。」というような声をいただきました。 今回の学びが実務における障害対応に活かされることを期待しています。 私たちは常に顧客満足度の向上を目指しています。今後も積極的に対応力強化に取り組んでいき、サービスの質をさらに高めていきます。 後編は、実践編として、「障害対応訓練」についてご紹介予定です!お楽しみに。 最後に、LIFULLではともに成長していける仲間を募集しています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co