TECH PLAY

株式会社LIFULL

株式会社LIFULL の技術ブログ

656

こんにちは。テクノロジー本部の岡より、障害管理運用のより具体的な取り組み内容を紹介していきます。 障害管理に関わる方の参考になれば幸いです。 障害報告のおおまかな流れ 具体的な障害ステータスと報告内容 最近の運用改善事例 まとめ 私たちの部署では、LIFULLのさまざまな事業開発に関する活動や適切性のモニタリングとレポーティングを通して横断・間接的に必要な支援・助言・監督を行う責任を担っています。 事業運営に携わるエンジニアによる、障害分析の取り組みはこちらの記事もぜひ読んでみてください。 www.lifull.blog   障害報告のおおまかな流れ LIFULLでは、専用の障害報告管理ボードを使って障害発生から恒久対応完了までを報告することになっています。 「障害・バグが疑われる事象」「サクセスレートの低下によるエラー通知」などの問題が起きたら、誰でもすぐに報告チケットを起票できます。(起票は一部自動化がされています) その後の調査報告や対応後のステータス更新が行われ、最終的には開発責任者が報告完了まで責任を持ちます。 具体的な障害ステータスと報告内容 障害対応のステータスごとにどんな入力項目を記載することになっているか、具体的に紹介します。 以下に記載している入力項目は必須入力項目です。ステータスを変更する際に未入力の場合は必ず入力を求められ、記載漏れが起きないようにしています。 これらの入力項目以外にも、できるだけ記載に時間をかけず抜け漏れなく記入できるように、選択式の入力項目が多数あり、その時点で判明していることを記載していきます。   報告ステータス 入力項目 1 継続中 不具合が解消しておらず、対応が進行中の状態 報告書タイトル(障害の要約) 検知日時 2 暫定解消 不具合が復旧または自然解消し、正常稼働している状態 概要 発生日時 解消日時 事業領域 影響内容の詳細 対象利用者 対象デバイス 障害起因となった変更内容 対応内容 3 本解消 障害分析と再発防止策が記入され、障害対応が終了した状態 原因 再発防止策(次のいずれか) ・再発防止(恒久対応)完了報告 ・再発防止対応計画の管理資料の明記 ・許容判断(恒久対応不要)理由 開発担当部署の責任者 4 報告完了 障害分析と再発防止策が記入され、障害対応が終了した状態 (担当部署の責任者が確認) 再発防止(恒久対応)内容の最終チェック 5 完了 問題解決プロセスが終了した状態 (モニタリング部署が確認)   分岐する別経路として、以下のようなステータスもあります。この場合モニタリング部署も内容確認を行います。 経過観察 原因不明、一時的エラーで対応保留などの状態 無効 開発担当者が調査後に障害ではないと判断された状態 原因重複(別対応中) 同じ原因の複数障害報告が存在している状態 最近の運用改善事例 開発担当者は、まず不具合解消を最優先に動きますが、解消後はさまざまな要因で報告が完了しないことも起こり得ます。 障害報告の運用は定着してきているものの、対応状況の確認や適切な情報共有に課題感がありました。 そこで、報告が未完了のままとなってしまいがちな主な要因を分類し、改善策を運用ルールに追加しました。 原因不明や再現性がなく調査困難な軽微なエラー - 改善策: 「経過観察」ステータスを追加し、軽微なエラーや再発が少ないものについては一定期間経過を見て問題がなければ、モニタリング部署が完了とする 同じ要因で発生した障害が複数報告される - 改善策: 一つの障害報告に情報集約し、それ以外は「原因重複(別対応中)」ステータスにてモニタリング部署が管理する 不具合解消後、ルーズボールになり情報更新がされにくくなる - 改善策: リマインドを可能な限り自動化、リマインド時に次に取るべき行動も都度伝える 恒久対応の難易度や優先度により恒久対応時期が未定 - 解決策: 恒久対応の課題管理がされていることを条件に完了とする まとめ 必要な対応に集中できるよう報告時の開発者負担を減らしたり状況をわかりやすくするなど、運用改善を少しずつ試みた結果、障害状況の見える化が進み、未完了の障害削減にもつながりました。 全体として障害対応が迅速化し、効率的な管理運用が実現できています。 状況の変化に対応しながら、今後も引き続きより良い運用方法を模索していきたいと思います。 次回は、この障害報告のデータによる障害傾向分析やレポートについて紹介する予定です。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは、クオリティアーキテクトグループ(以下、QAG)の鐘です。 この記事では、E2Eテスト用物件のデータの正確性を維持するために、定期的にCSV形式の正しいデータを取り込むことで復旧する仕組みをご紹介したいと思います。 1. 結論 2. 背景 3. 解決したい問題 主要問題:物件データが意図せず変更されることで、テストが失敗してしまう 対応コストの増加 テスト信頼性の低下 副次問題:テスト用の物件が最適化・管理されていない 一つの物件が数種類のテストで使用されている 物件データがテスト用に最適化しづらい 4. やったこと 問題解決までの流れ 副次問題に対して:物件データの最適化 主要問題に対して:CSVでデータを復元できるのツール「DataKeeper」を提供 5. さらなる改善 1. 結論 物件データの不備が原因でテストが失敗することは運用開始以来発生していません。データが変更されても、毎日正常な値に修正されるため、テストケースに影響しません。 E2Eテスト用の物件が管理コストを削減するように工夫されました。また、各物件のデータを必要最低限の情報に整理し、どのテストケースで使用されているかが一目でわかるようになっています。 2. 背景 今回のテスト対象はLIFULL HOME'Sです。 開発中のプルリクエストでは 自動的にE2Eテスト が実行されます。 E2Eテストではユーザーのブラウザ操作を再現して物件問合せ、お気に入り登録などの機能を検証するため、40件ほどのテスト用物件が使用されています。 例: 賃貸物件リスト からテスト用物件を選択し、お気に入り追加ボタンを押下し、お気に入りリストの存在するかを検証するE2Eテスト 3. 解決したい問題 主要問題:物件データが意図せず変更されることで、テストが失敗してしまう 開発用環境とE2Eテスト用環境で同じデータストア(DBやXMLファイル)を使用しているため、開発で物件データを変更する際、E2Eテスト用の物件データも一括で変更されることがあります。 その結果、物件の名称や価格などにズレが生じ、テスト結果が失敗してしまうことがあります。 このような事象が発生すると、次の2つの悪影響があります。 対応コストの増加 テスト失敗の原因を調査する必要が生じます。 物件データに問題があると判明した場合、修正(または修正依頼)を行い、その反映や対応を待つことになります。場合によっては復旧までに最大で2時間かかることもあります。 テスト信頼性の低下 プロダクトに問題がないにもかかわらず、物件データの不備でテストが頻繁に失敗し、誤報が多発してテストの信頼性が低下します。 副次問題:テスト用の物件が最適化・管理されていない 具体的に: 一つの物件が数種類のテストで使用されている 例えば、問合せ機能のテストと一覧機能のテストで同じ物件を使用している。 デメリット:一つの物件に不具合が生じると、複数の機能のテストが失敗し、調査範囲が広がる。 物件データがテスト用に最適化しづらい 物件データがランダムで、不要なデータも含まれている 物件名が必要以上に長く、テストとの関連性が不明瞭 セールスポイントやこだわり条件など、テストに不要なデータが含まれている 1つのマーケット(賃貸など物件種別のこと)で複数の不動産会社の物件が使用されて管理しにくい、どの物件がどのテストで使われているかが分かりにくいなどの問題があります。 4. やったこと 問題解決までの流れ 物件データの最適化 CSVでデータを復元できるのツール「DataKeeper」を提供 副次問題に対して:物件データの最適化 1マーケットに3〜6の不動産会社が混在している状態を1マーケット1不動産会社に統一し、運用コストを削減しました。 1機能に対し1つの物件のみを使用するようにテストケースを調整しました。 これにより、1つの物件が壊れても影響はその機能のテストに限定され、調査が容易になります。 Before * 問合せのテスト: 物件A, 物件B * 物件検索のテスト: 物件A, 物件C * リスト表示のテスト: 物件A After * 問合せのテスト: 物件A * 物件検索のテスト: 物件B * リスト表示のテスト: 物件C テストに不要な冗長なデータを削除し、必要最低限のデータのみを残しました。さらに、物件名のフォーマットを統一し、マーケット、テスト目的、デバイスが一目でわかるようにしました。 例:品質管理テスト 変更禁止 b_mansion_inquire_sp 主要問題に対して:CSVでデータを復元できるのツール「DataKeeper」を提供 4つのマーケットの物件データを、それぞれ1つのCSVファイルにまとめてリポジトリで管理しています。 DataKeeperの実装: DataKeeperは、CSV形式で定義した物件データを元に正しい値に更新するためのツールです。 Jenkinsでマーケットごとのジョブを実行します。 各ジョブの手順は以下の通りです。 リポジトリから対象マーケットの物件データCSVを取得 CSVファイル内の日付(最終変更日、掲載期限など)を実行日に合わせてスクリプトで書き換え CSVをサーバーにアップロードし、データベースに反映 実行が完了したら、Slackに結果を通知 DataKeeperの運用: 平日朝に自動実行されるよう設定し、テスト用物件データに変更があってもなくても、定期的に正しいデータを書き込みます。 もし自動実行後にデータが正しい値以外に変更されても、不備を発見した人が手動でジョブを実行すれば、数分で正しい状態に戻せます。 5. さらなる改善 ここまでで、E2Eテスト用の物件データが最適化・管理されるようになり、データの不備によるテスト失敗の問題が解決されました。しかし、以下のような改善点がまだあります。 必要最小限のデータ以外、他のテストで必要なデータはまだ対応されていませんが、将来的に対応したいです。 E2Eテスト用以外のテスト物件にも正しいデータを書き込む対応を提供 他のテストレベル、テストタイプのテスト用物件データもDataKeeperで正確に保てるようにしたいと考えています。
グループデータ本部データサイエンスグループの嶋村です。 LIFULLでは、不動産や住まい探しに関する研究の活性化や、人工知能(AI)・情報学分野での人材育成への貢献を目的として、 2015年から学術研究者向けに LIFULL HOME’Sデータセット を提供 しています。 このデータセット提供は 国立情報学研究所(NII) が運営する 情報学研究データリポジトリ(IDR) の枠組みを活用した取り組みです。LIFULL HOME'Sデータセット利用者は年々増えており、これまで 171件の研究成果が公開 されています。 本記事では、2024年12月13日に開催されたIDRユーザフォーラム2024の様子をお伝えします。 IDRユーザフォーラム2024での発表 IDRユーザフォーラムについては過去の投稿でも紹介しておりますので、是非下記をご覧下さい。 「IDRユーザフォーラム2023」参加報告 - LIFULL Creators Blog 集会報告 NII-IDRユーザフォーラム2016 2020年初頭からのコロナ禍により、過去3回(2020、2021、2022)のIDRユーザフォーラムはオンラインのみで開催されました。今年は昨年2023と同様に 現地会場での開催 となりました。オンライン配信も組み合わせたハイブリッド形式だったのですが、現地会場での参加者がとても多く約150名規模となり、オンライン参加者は約50名規模となりました。 データ利用者の発表 として、各社のデータセットを利用した研究発表が32件、研究アイデアの発表が21件、合わせて53件の発表がありました。また、 データ提供者の発表 として、弊社LIFULLを含む民間企業から14件、研究者から8件、合わせて22件の発表がありました。 発表プログラムについては、以下のページをご参照下さい。 情報学研究データリポジトリ ユーザフォーラム LIFULL HOME'Sデータセットを活用した研究発表 LIFULL HOME'Sデータセットを利用した住まいに関する研究発表について、いくつか紹介したいと思います。今回は合計4件の発表がありました。 関西学院大学の福地さんらによる研究発表「 地域に対するレビューと地図画像を用いた地域特性学習モデルの提案 」は、地域に対するレビュー情報と地図画像を組み合わせることで、様々な地域が持つ独特な特性を把握するという取り組みです。レビュー情報を集めるのは手間がかかるため、もし地図画像のみから地域特性が把握できると、とても有用なのではないかと感じました。 東京科学大学の藤田さんらによる研究発表「 東京都における低層賃貸住宅の外観に対する印象と建築年代・空間分布・賃料の関係 」は、外観画像から建物の印象を定量評価し、その建物が存在する地域の印象を定量評価する取り組みです。物理的に近い地域であっても、地域の印象が大きく異なることはあるため、印象の定量評価がされていると住まい探しでも有用ではないかと感じました。 電気通信大学の綾部さんらによる研究発表「 築年代推定におけるViTとCNNモデルの比較 」は、外観画像から築年代を推定する取り組みです。850万枚もの画像データを学習データとして用いて、複数の画像認識モデルで比較評価をしており、とても興味深い結果でした。 東京科学大学のLiuさんらによる研究アイデア発表「 Assessing the 'Openness' of Real Estate Properties based on Diverse Perspectives 」は、不動産物件の開放感を定量評価する取り組みです。間取り図から得られる構造情報や、内観画像から得られる空間情報を組み合わせて定量評価を試みるアイデアであり、今後の発展がとても楽しみです。 様々な研究発表がありとても興味深かったです。また、データセットに関する要望など、利用者から直に声を聞くこともできたため、貴重な場となりました。 LIFULL HOME’Sデータセットのこれから LIFULL HOME'Sデータセットは 2015年の公開から9年が経過 し、「 LIFULL HOME'S 3D間取り 」など、サービス価値の向上につながる研究成果を生み出しています。 今後も 公開済みデータセットの更新 や 新たなデータセットの追加 等さらなる拡充をしていき、不動産や住まい探しに関する研究の発展に寄与できればと考えています。 最後になりますが、データサイエンスグループでは「 活用価値のあるデータを創出 」し「 データを活用した新たな機能やサービスの研究開発 」を加速して下さる シニアデータサイエンティストを募集 しています。 興味お持ちいただける方は、 カジュアル面談 も行っていますのでお気軽にご連絡ください。 hrmos.co
プロダクトエンジニアリング部の二宮です。この記事は LIFULL Advent Calendar 2024 Part2の20日目です。 LIFULLにはkeelaiという社内向けの生成AI基盤プロジェクトがあり、私はその開発にコミットしています。今のところ特に社内用Slack botとして最もよく使われています。 www.lifull.blog keelaiの基本的なコンセプトを私達はマルチエージェントと呼んでいて、サブタスクを解決するために自律的に動くエージェントを複数組み合わせて協調させることで無限にスケールすることを目指します。 このエコシステムを土台に、エンジニア以外のメンバーからの改善リクエストや新しいアイデアをうまく取り込んでいます。その中から、生成AIヘビーユーザーの営業職メンバーから「営業日報を簡単に作りたい」というアイデアをいただいて、内製AIを使ってうまく機能提供した様子を紹介します。 営業サイドからのニーズヒアリング 既存機能の再利用で短期間開発 開発後の効果とフィードバック まとめ 営業サイドからのニーズヒアリング keelaiの開発チームでは、全社の活用方法の相談窓口となる サポート用のSlackチャンネル を設けています。そこで、営業職メンバーからプロンプトの相談を受けていました。 そこで何度かやり取りしていて、この2つ面白いフィードバックがありました。 Chrome拡張機能(keelext)で提供しているメール返信文作成機能に対する好評 営業日報作成を自動生成して省力化したいという要望 keelextとは生成AI(keelai)以前からあった内製Chrome拡張です。ここ最近はkeelaiとの連携機能も増えていて、その中の一機能として次の画像のようなGmailの返信文の作成機能を提供していました。これは半ばkeelaiのAPI連携機能のデモとして作ったつもりのものだったのですが、私たち開発チーム側が考えていた以上に需要が大きかったようです。 ※画像にあるメールの内容は架空のものです もう一つは営業日報の作成が負担で、生成AIで自動化したいという話です。 Slack AutomationsによるLLMノーコードツールの提供 にあるやり方を紹介しました。 既存機能の再利用で短期間開発 そのまましばらく動けてなかったんですが、更にしばらくして開発チーム内で「やっぱり営業職への普及促進は重要なんじゃないか」と課題が挙がります。そこで日報作成の省力化として、日報作成機能のkeelext組み込みにトライしてみることにしました。 最初に作った機能を紹介します。次のようになぐり書きに近いメモ書きから、ボタン一発で必要な項目に整理します。 私はkeelextのリポジトリを…というよりChrome拡張機能を実装したことが無く、フロントエンドやJavaScriptの経験に乏しいので当初は心配でした。しかし実際にやってみると、うまく機能がテンプレート化されていて、いくつかの箇所を触るだけで実現できました。 まずはmanifest.jsonにソースの読み込み設定を書き足します " content_scripts ": [ { " matches ": [ " <all_urls> " ] , " js ": [ ... ] , ... } , { ... } , { " matches ": [ " https://*.salesforce.com/* ", " https://*.lightning.force.com/* " ] , " js ": [ " src/salesforce.js " ] } ] , 次に実行ファイル( src/salesforce.js )を実装します。とはいえほとんど実装は使いまわしです。 <DOM要素のセレクタ> と、「保存」ボタンと被ってしまっていたボタンの位置を右側に移動させる設定だけは必要でした。 (() => { const monitor = ( selector , cb ) => { cb ( document . querySelectorAll ( selector )) ; new MutationObserver (( records ) => { records . forEach (( record ) => { record . addedNodes . forEach (( addedNode ) => { if ( addedNode . nodeType === Node . ELEMENT_NODE ) { cb ( addedNode . querySelectorAll ( selector )) ; } }) ; }) ; }) . observe ( document . body , { childList : true , subtree : true , }) ; } ; monitor ( `<DOM要素のセレクタ>` , ( nodes ) => { // ボタンを表示する実装 // ここでボタン押下時にtextboxの内容を取得してAPIを呼び出す実装をする }) ; })() ; そして最後にAPIの呼び出し部分を追加します。ここでプロンプトを修正します。 const response = await fetch ( "https://<keelai APIのエンドポイント>/v1/chat/completions" , { method : "POST" , headers : { "Content-Type" : "application/json" , "Authorization" : `Bearer ${ cookie . value } ` , } , body : JSON . stringify ({ messages : [{ role : "system" , content : `Your task is to create a sales report from the following text in ${ chrome . i18n . getUILanguage ()} . Output it thoroughly and carefully without omissions as a report for the supervisor. Adhere to the following terms. - CL = Sales Closing Structure and organize the report according to the following content. Output these contents in markdown with ### as titles, only the items for which information is available. - <項目A> - <項目B> ... - メモorトピックス ` , } , { role : "user" , content : message . content , }] , stream : true , }) , signal : abortController . signal , }) 更に言うと、これも先程の営業チームで使われていたプロンプトを大幅に参考にしています。「CL = クロージング」というよく使われる略語や、<項目A>などの各項目タイトルはそのまま流用しています。プロンプト全体を英語にして整理し直した点と、内容によっては含まれない項目を only the items for which information is available. とした点は追加で工夫しました。 理想を言うと、文章に必要な追加情報も自動で取ってこれるようにしたいとかは思いつくのですが、すぐ提供できる範囲で実用的な機能は提供できたと思います。 開発後の効果とフィードバック これで営業チームはワンクリックでメモを整理し、日報記入時間を大幅に短縮できるようになりました。協力してもらった方に連絡したところ、次のような嬉しいコメントいただけました。 実際に試してみました~! 商談メモがワンクリックで整理された日報になるのでは感動しております、、、!!! その後、営業チーム内の勉強会でSlack botやChrome拡張を紹介していただけたそうです。こんな感じで機能アイデアや対応のやり取りして、各チームのアーリーアダプターといい協力関係を築けてるように思います。 また、既存の機能(Gmail生成)を転用し、個別開発コストをかけずに横展開できることも確認できました。今後は他のツールの転用も視野に入れています。 まとめ このように、keelaiやkeelextは社内コードがオープンであるため、新機能を思いついたら素早く拡張できます。部署をまたいだニーズにも、既存コードを活かして最小限の修正で対応可能な環境が整っていると思います。 この日報支援機能等をきっかけに、他の業務ツールへの適用してみんなで楽できる流れを広げたいなあ…と思ってます。この後、社内向けの生成AIユースケースのデモ目的を半分、素直に自分が使いたい意図半分で、次のような機能も実装してみました。 Jira(プロジェクト管理ツール)やGitHubのPR, Issueなどを要約し、残課題や次のアクションをまとめてくれる機能 ブログを書くときによりわかりやすい書き方をアドバイスしてくれる機能 これをきっかけにユースケースを多くの社員に想像できるようにして、簡単に社内ツールの要約や提案機能をエンジニア全体で作っていけるようにもしたいです。また余談ですが、LIFULLには社内用Chrome拡張機能ストアがあったり、keelextに自動アップデート機能があったりして、こうした活動がスケールする仕組みが整備されていると感じます。 最後に、LIFULLでは一緒に自分たちの仕事を良くしていくエンジニアを求めています。これらの取り組みに興味を持っていただけたら、ぜひ求人情報やカジュアル面談のページもご覧ください🙇 hrmos.co hrmos.co
エンジニアの松尾と申します。LIFULL HOME'S で中古住宅売買領域の開発を担当するチームのマネージャーとして働いています。 2024年は新卒1名、中途1名の新入社員が自部署に仲間入りしました。今回はそれらの内容をふりかえり、LIFULLのオンボーディングの工夫や今後の伸びしろについて整理してみたいと思います。 エンジニアのオンボーディング 企業の魅力因子 ふりかえり ⭐️ Philosophy(理念・方針) 1. 会社/上位部門/自部署のビジョンを自分のことばで伝える 2. 会社/上位部門/自部署の目標やミッションを伝える 3. 自部署におけるルールやコミュニケーションのスタンスを伝える 🚀 Profession(活動・成長) 4. 開発環境の構築をサポートする 5. できるだけ早く初リリースをしてもらうサポートをする 6. 成長曲線をイメージするための指針を作る 🧑‍🧑‍🧒 People(人材・風土) 7. 自己紹介をし合う場を作る 8. 業務上で関わりを持ちそうな組織/人と引き合わせる 🎁 Privilege(待遇・特権) 9. 会社のルールや福利厚生を伝える 10. エンジニア職におけるルールや福利厚生を伝える 今後の伸びしろ 完了までのスケジューリング 周囲のメンバーとの分担 まとめ エンジニアのオンボーディング あらためてオンボーディングとは、新しく組織に加わったメンバーの職場への順応を促進することで、早期の戦力化を目指す取り組みです。 エンジニアとしては 開発環境/使用する言語/ドメイン知識/プロジェクト管理方法/ステークホルダー などを理解し、身につける必要があります。 私はLIFULLの魅力を多面的に知ってもらうことで、本人を取り巻く環境に安心を感じ、前向きに取り組んでいける流れを作りたいと考えています。 「採用の過程で伝えてきたLIFULLの魅力を、入社後に再認識してもらえるオンボーディング」について検討してみます。 企業の魅力因子 人が企業に感じる魅力を、下記の4点に分類します。 Philosophy(理念・方針) Profession(活動・成長) People(人材・風土) Privilege(待遇・特権) 人によってどの要素を重視するかは異なりますが、採用の候補者や新入社員には漏れなく伝えていく必要があります。 ここからはオンボーディングにおいて行った取り組みをこれらに分類して整理し、不足がなかったかをふりかえります。 ふりかえり 取り組んだ内容は次回以降やほかのどなたでも活用できるように、チェックリスト風に10項目でまとめてみました。 ⭐️ Philosophy(理念・方針) 1. 会社/上位部門/自部署のビジョンを自分のことばで伝える LIFULLでは経営理念を頂点として、それを落とし込んだビジョンが組織ごとに存在します。「LIFULLエンジニア像」という「エンジニアとしてありたい姿」もインプットする必要があります。これらをどのように体現していくかを自分のことばで伝えます。 note.com ビジョンは半期ごとにふりかえり見直しているため、ふりかえりの場でほかのメンバーのビジョンへの想いも聞いてもらうことで、理念を腹落ちさせていきます。 2. 会社/上位部門/自部署の目標やミッションを伝える 組織の粒度に応じた目標や戦略があるため、会社の方向性を知ってもらう意図でそれぞれを伝える必要があります。社内でのKGIやKPIの定義やとらえ方も合わせて伝えていきます。 戦略については、弊社には「戦略DAY」という各部門が戦略を発表する場があるため、この内容を視聴して理解してもらいます。直接スピーカーに質問することもできますし、必要に応じて1on1などで補足します。 3. 自部署におけるルールやコミュニケーションのスタンスを伝える 下記のようなグループ内で決めているルールを共有します。 グループメンバーの大まかな役割 カレンダーに定例で入っている会議の説明 グループで出社する曜日、リモートワークのお作法 把握していなければ翌日から困ることが多いため、配属初日で必ず伝えるようにします。 🚀 Profession(活動・成長) 4. 開発環境の構築をサポートする 基本的にはドキュメントを参考に進めてもらいますが、ドキュメントのメンテナンス不足により「ここがうまく動きません…」が起こるため、専用のSlackチャンネルを作成して伴走します。 出社時は近くに座っていつでも質問を受けられるようにし、リモートでも声をかけやすいようにSlackのハドルに常駐します。チームで声を掛け合い、新メンバーが1人になる状況をできるだけ作らないようにします。 5. できるだけ早く初リリースをしてもらうサポートをする チームの一員であることを「成果」によって感じてもらうため、軽めのバグ改修などをお願いして短期でリリースしてもらいます。周囲からの歓迎をあらためて伝えるために、リリース直後には「〇〇さんの初リリース完了しました!!!」と全体メンションで発言し、リアクションを集めるようにします。 「案件がない…」とその場で困らないために、普段からGood First Issueを整えておくことが大切だと感じます。 6. 成長曲線をイメージするための指針を作る 入社から1年間のマイルストーンを作成します。「1年後にこの領域の設計ができるようになってほしいです」「この時期にはこの規模の実装ができるようになっていると思います」という階段を作り、本人には具体の部分を埋めて目標設定に臨んでもらいます。 入社段階では、新しい環境で本人の能力が最大限発揮できるかは未知です。まずは焦らず順応してもらうため、活躍は期待していること/ただし直近での大きな成果は計算には入れていないことを伝えます。 🧑‍🧑‍🧒 People(人材・風土) 7. 自己紹介をし合う場を作る 自部署において、既存のメンバーと新メンバーがお互いに経歴や嗜好を語り、気になるところを質問し合う会を設けます。エンタメ好きなメンバーには「好きなYouTuberは?」、中途入社のメンバーには「前職のユニークな社内用語は?」など質問はさまざまです。 また、LIFULLでは4半期に1回の頻度で全エンジニアが集まる総会もあり、新入社員にはその場で自己紹介をしてもらう風土があります。 8. 業務上で関わりを持ちそうな組織/人と引き合わせる 新卒のメンバーには配属前の研修で関わった社員や同期がいますが、中途社員は相対的につながりが少ないです。今年はフロントエンドのメンバーを迎えたため、他部署のフロントエンドエンジニアやデザイナーのメンバーとのランチや飲みを調整しました。バックエンドの場合はインフラやアーキテクトの部門との連携を検討します。 一方で、 START という、別の部署の先輩社員がメンターになる制度もあります。「万一上司の私に言いづらいことがあれば、さらに上司やSTARTのメンターに相談してね」と伝えるようにしています。 🎁 Privilege(待遇・特権) 9. 会社のルールや福利厚生を伝える 大枠は入社時にバックオフィスの社員から伝えられますが、評価制度の詳細や勤務ルールなどは必要に応じて伝えていきます。 社内には 多様な取り組み があり、上司である私が認識していないものもあります。本人に近い属性の人が「こういうのもあるよ」と伝えてくれるとありがたく、そのためにも前述のつながりを増やす取り組みは重要だと思います。 10. エンジニア職におけるルールや福利厚生を伝える LIFULLには keelaiという社内のAIチャットボット や エンジニアいつでも相談 というQ&Aフォーラムなど、利用すると効率的に/安心して開発を進められるしくみがあります。 また アクセシビリティに専門性を持つメンバーの1on1 や エンジニアキャリアクリニック のように本人の成長のために活用すべきものもあり、知らなければもったいない情報が多々あります。 LIFULLでは「入社の手引き」という社内ドキュメントに入社後知っておくべき情報をまとめているため、そちらの継続的な整備を行っていきます。 今後の伸びしろ 完了までのスケジューリング 早期の戦力化を期待しているとはいえ、一気に伝えるには多すぎる情報量だと思っています。 「今伝えないといけないか」を考慮して無理のないスケジュールを組みつつ、なるべく早く戦力になってもらえるように徐々に短縮していきたいと感じています。 周囲のメンバーとの分担 あらためて、これらを上司が1人で抱えるとたいへんだと思いました。 実際にチームメンバーが「Slackハドルに常駐しておくので、開発のフォローは任せてください」「他部署のあのメンバーもランチ誘えるけどどう?」と声をかけてくれて助かっています。 特に「Profession(活動・成長)」と「People(人材・風土)」の部分はチームの総員で行うのが望ましいと感じました。スケジューリングの段階で「誰がやるか」も決めておけば効率的かもしれません。 まとめ 2024年の自部署でのオンボーディングについてふりかえりました。今年の進め方が満点だったかというとそんなことはありませんが、2人とも元気で前向きに開発をすすめてくれていて何よりだと感じています。 これは偶然ですが、先日は他職種(プロダクトマネージャー)においてもオンボーディングの取り組みが共有されました。 note.com LIFULLでは職種を問わず新メンバーを大切にし、ともに成長していく文化があります。一緒に働く仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは。 LIFULL Tech Vietnam (以下、LFTV)CEOの加藤です。 2023年10月にLFTVのCEOに就任し、早1年が経過しました。 この1年LIFULLグループでは「グローバル開発体制強化」を掲げ、その実現に向け多くの検討と取り組みを繰り返してきました。 そうした活動を繰り返す中で、我々は目指すべきグローバル開発を進めるためには以下の「3つの壁」を超えていくことが重要であるという結論に至りました。 「意識・理解」の壁 「仕組み」の壁 「言語」の壁 今回はその中でも主に「意識・理解」の壁を越えるためにLFTVとして行った取り組みを3つ紹介します。 3つの壁については当社CTOの以下の記事でも紹介しています。 note.com また、LFTVにおける今後のビジョンやグローバル開発体制強化の詳細は以下の記事で紹介していますので、よろしければご覧ください。 www.lifull.blog 関心・理解を深める「# times-lftv」 取り組みの概要 ベトナムからの情報発信 双方向のコミュニケーション カルチャーを醸成する「ガイドライン研修」 研修の構成 第1部:全体把握 第2部:ケーススタディ 第3部:プレゼンテーション 研修後の定着に向けた取り組み 深く戦略を理解する「OneTeam OpenTalk」 OneTeam OpenTalkの特徴 質問の事前収集 双方向の対話 隔週30分、継続的な開催 最後に 関心・理解を深める「# times-lftv」 組織の垣根を越え一つのチームとして連携を深めていくためには、お互いの理解が重要です。 物理的な距離が離れた中で、いかに身近な存在として意識できるようになるかが、結束力を強める鍵となると考えています。 そのような機会を生み出すため、ベトナムからの情報発信を行うSlackチャンネル「# times-lftv」を開設し、運用してきました。 # times-lftvでの投稿 取り組みの概要 ベトナムからの情報発信 # times-lftvでは、LFTVでの業務や取り組みに加えて、ベトナムの文化や習慣、日常生活に関する情報も幅広く発信しています。 ベトナムランチの紹介 ベトナムの交通事情 15時のTea Breakの様子 テト休暇のお祝い など こうした日常の話題を通じて、メンバーがベトナムそのものに興味を持ち、親近感を抱くことを狙いとしています。 双方向のコミュニケーション # times-lftvは単なる一方通行の情報発信にとどまらず、双方向のコミュニケーションが生まれる場としても機能しています。 運用を続ける中で、以下のような動きも見られるようになりました。 日本やマレーシアのメンバーからのコメントをきっかけに会話が広がる。 日本のメンバーからベトナムのメンバーへ向けて、日本に関するトピックが投稿される。 こうした交流を通じて、物理的な距離を越えたつながりが築かれ、メンバー間の関心が高まり、それに伴い理解もより深まっています。 カルチャーを醸成する「ガイドライン研修」 プロダクトチームとして高い成果をあげるためには、一人一人が同じ価値観を共有し、同じ行動規範によって判断することが重要です。 LIFULLグループでは社是の「利他主義」や経営理念を始めとし、日々の行動規範を定めたガイドラインなど共通して持つべき価値観が定められています。 ※ガイドライン含む企業理念は以下を参照 lifull.com これら理念を浸透し、組織の文化として定着させるため、LFTVでは「ガイドライン研修」を実施しました。 研修は大きく3部構成となっており、段階的に理解を深めるプログラムが設計されています。 研修の構成 第1部:全体把握 社是や経営理念、ガイドラインそれぞれのつながりについて理解を深めるセッション。 これにより、日々の行動規範として意識すべきガイドラインがLIFULLグループの理念全体とどのようにリンクしているかを把握します。 第2部:ケーススタディ 各ガイドラインごとに実務を想定したケーススタディを実施。 参加者はチームごとに分かれ、特定の状況においてガイドラインに沿った行動を考え、答えを導き出す形式で進行しました。 これにより、抽象的な概念を具体的な行動に結び付けています。 ケーススタディの様子 第3部:プレゼンテーション 各グループに分かれ、代表者が最も好きなガイドライン項目に対し、自身の経験や考えを結び付けたプレゼンテーションを実施。 実際のエピソードと紐づけて深掘りをすることで、ガイドラインが実際の業務や行動にどのように結び付くかを深く理解する機会となりました。 プレゼンテーションの様子 研修後の定着に向けた取り組み 今回の研修終了後も4半期に一度を目安とし、継続してプレゼンテーションの機会を設けることを予定しています。 また、10月より人事評価にガイドラインの要素を組み込んでおり、定期的な振り返りとフィードバックの機会を創出することで定着を図っています。 深く戦略を理解する「OneTeam OpenTalk」 組織全体が一体となって目標を達成するためには、ビジョンや戦略の理解が欠かせません。 しかしながら、従来のビジョンシェアリングだけでは、CEOから社員への一方通行の形式に偏りがちで、細かな部分や背景までは伝わりきらないという課題があります。 そこで、LFTVでは双方向の対話を通じてビジョンや戦略を深く理解し合うための取り組みとして、「OneTeam OpenTalk」を開始しました。 OneTeam OpenTalkの特徴 質問の事前収集 社員がビジョンや戦略に対して持つ疑問を事前に集めることで、具体的なトピックに基づいた対話を実現しています。 これにより、社員一人一人が抱く「なぜ?」や「どうして?」を解消する機会を提供しています。 双方向の対話 当日は集めた質問に対してCEOが回答しながら、テーマについて深掘りする形式で進行。 単なる説明にとどまらず、所々で社員からの追加の質問や対話を交えながらビジョンや戦略の理解を促しています。 隔週30分、継続的な開催 OneTeam OpenTalkは隔週30分という短時間で、1回につき1~2つの質問をテーマに設定して実施しています。 このように小規模なテーマ設定を行うことで、特定の内容に集中して理解を深めることができ、日々の業務に負担をかけることなく継続的な実施が可能となっています。 OneTeam OpenTalkの様子 最後に 我々LIFULLグループではLIFULL本社および海外開発拠点であるLFTV、 LFTM の垣根をなくし、一体となって開発を推進するグローバル開発を目指し日々挑戦しています。 今回はその実現に向けた「意識・理解」の壁を超える取り組みを3つ紹介させていただきましたが、LIFULL、LFTV、LFTMでは一緒に働く仲間を募集しています。 LFTVでは日本とは異なる文化や価値観に触れ、日々刺激を受けながら成長する機会に溢れています。 海外のメンバーと直接連携しプロダクト開発に携わることで、コミュニケーションの取り方や考え方の違いなど多くの面で新たな発見が得られます。 また、ベトナムならではの社内イベントも多数開催しているため、ベトナム文化の中で働きたいと考える方にとっても魅力的に感じていただけるはずです。 そして、グローバル開発を推進する中で、国や所属する開発拠点にとらわれない関わり方を目指し、幅広い領域や役割を担う機会を生み出していきます。 幅広い経験が得られる機会の中から、一人一人に合ったキャリアパスを実現するための支援も行っていきます。 グローバル開発に共感いただき、同じ志のもと挑戦したいと感じて下さった方がいらっしゃいましたらぜひともご応募お待ちしています! hrmos.co hrmos.co lifull-tech.vn lifull-tech.my
こんにちは。テクノロジー本部アーキテクトグループの福留です。最近気になっているものは model-checking/kani です。 アーキテクトグループは、LIFULL のエンジニアの開発生産性を、エンジニアに寄り添いながら向上させていくことをミッションとして活動しています。 そこで今回は、昨年から今年にかけて行った LIFULL HOME'S のリリースフローの改善、通称「即日リリースプロジェクト」について紹介します。 このプロジェクトでは、 LIFULL HOME'S のリリース作業を完全自動化し、リリースにかかる時間を 1/5 に短縮しました。 その取り組みについて、順を追って説明します。 従来の LIFULL HOME'S のリリースフロー リリースフローの課題 課題 1: リリーサーの負担が大きい 課題 2: リリースまでの時間が長い 即日リリースの目標 即日リリースの適用 Phase 1. リリーサーの確認業務自動化 Phase 2. リリース作業の自動化 Phase 3. リリースフローの高速化 まとめ 従来の LIFULL HOME'S のリリースフロー LIFULL HOME'S はいくつかのマイクロサービスで成り立っています。 このマイクロサービスでの機能の公開、いわゆるリリースは「リリースチケット」と呼ばれる Jira チケットと GitHub の PullRequest によって管理されています。 リリースチケットは PullRequest ごとに作成される Jira チケットで、開発グループ長と、担当企画のリリース承認と、リリース状況の管理を行います。 また、LIFULL HOME'S のステージング環境を含めた環境は、テスト環境、プール環境、本番環境の 3 種類があります。 それぞれの環境は以下のような役割を持っています。 テスト環境 develop ブランチの内容が反映される テストデータによる動作確認が可能 プール環境 release ブランチの内容が反映される 個人情報等のセンシティブ情報をマスクした、本番環境相当のデータによる動作確認が可能 本番環境 master ブランチの内容が反映される ユーザーに提供される本番環境 従来のリリースフローでは、これらを用いて以下の流れでリリースが行われていました。 1 日目 (Jira) リリース承認を開発グループ長・企画担当者からもらう (GitHub) PullRequest を develop ブランチにマージし、テスト環境に反映 (テスト環境) テスト環境で動作確認 2 日目 (プール環境) 開発者がプール環境で動作確認 3 日目 (本番環境) 開発者が本番環境で動作確認 以上をまとめると、従来は次の図のようなリリースフローでリリースが行われていました。 従来のリリースフロー このフローの中では、「リリーサー」と呼ばれる人たちが、以下のような作業を行っていました。 PullRequest とリリースチケットの突き合わせ develop ブランチを release ブランチにマージし、プール環境へ反映 release ブランチを master ブランチにマージし、本番環境へ反映 反映された PullRequest に対応するリリースチケットのリリースステータスを「プール反映完了」「本番反映完了」などに変更 リリーサーの作業 リリースフローの課題 このリリースフローには、以下のような課題がありました。 課題 1: リリーサーの負担が大きい リリーサーの作業はすべて手作業で行われている上、上記のリリースフローで運用されているリポジトリは 5 つあります。ゆえに、リリーサーの確認作業はとても煩雑なものでした。 このリリーサーが手作業で行う作業は、月曜から木曜まで、1 日 4 時間ずつ、合計で 16 時間もかかっていました。 しかも、リリーサーはリリースフローと、リリースされるアプリケーションについて大まかな知識を持っている必要がありました。そのため、比較的社歴の長い、経験豊富なエンジニアがリリーサーを担当する傾向にありました。 つまり、 経験豊富なエンジニアの時間がリリーサー業務に、週 16 時間も使わなければならない という、もったいない事態が起こっていました。 課題 2: リリースまでの時間が長い 図の通り、開発者が変更を develop ブランチにマージしてからリリースされるまでには、約 3 日が必要です。 リリースまでのリードタイムは、最短で約 40 時間かかっていました。 本来なら新しい価値をどんどんユーザーに提供していきたいはずが、 価値提供のボトルネックがリリースフローにもある ことは明白でした。 即日リリースの目標 このような課題から、アーキテクトグループでは、以下の目標を立て、リリースフローの改善を行うことにしました。 リリーサー業務を自動化し、リリーサーという存在を不要にすることで、エンジニアが開発に集中できる環境を作る リリースフローを効率化することで、develop マージ後 1 日以内でのリリースを可能にする 2 番目の目標から「即日リリースプロジェクト」と名付け、3 つのフェーズに分けて取り組むことにしました。 即日リリースの適用 Phase 1. リリーサーの確認業務自動化 最初に、リリーサーの作業で最も時間を取られていた確認業務を自動化することから始めました。 ところが、リリーサーはリリースチケットの上で、「リリース承認されていること」以外に何をチェックしているのか、具体的には分かっていませんでした。 というのも、リリーサーは部署をまたいで持ち回りで担当が決まっていたため、公式な引き継ぎ資料がなかったためです。 非公式な引き継ぎ資料はあったものの、あくまでリリーサーが所属する部署内での資料だったため、チェックしている内容はリリーサーによってまちまちになっていました。 たとえば、あるリリーサーは最低限承認されていることをチェックしているが、あるリリーサーは PullRequest の内容まで入念にチェックしているらしい、という状況のようでした。 そこで、リリーサーのチェックを一緒にやってみることで、実際のチェック内容を把握しつつ、標準となるチェック項目を作成しました。 具体的なチェック項目については割愛しますが、人間でなければ判断できないチェック項目はリリーサーやエンジニアとの合意の上で削減しました。そして、自動化しやすく、かつ守るべきところは守れるチェックリストに落とし込みました。 最終的にできたチェック項目について、手元で動かせる cli ツールを作成してリリーサーに使ってもらうことで、作業の負担を大幅に減らしました。 Phase 2. リリース作業の自動化 Phase 1 ではリリース前の確認作業を自動化しました。ここから各環境に反映していく作業、つまりリリース作業も自動化することで、リリーサーの存在を不要にできます。そこで、リリース作業を自動で行ってくれるアプリケーションの開発を計画しました。 リリース作業とは単に GitHub のマージボタンを押すだけかと思いきや、実はそうではありません。 LIFULL HOME'S を構成するアプリケーションは API 層・BFF 層・フロントエンド層に分かれており、それぞれに依存関係があります。 この依存関係を踏まえると、前段のアプリケーションがリリースされたことを確認してから、次のアプリケーションをリリースしなければなりません。 たとえばフロントエンドと BFF に同時に変更を行った場合、BFF の方が先にリリースされてしまうと、フロントエンドの参照部分が壊れてしまい、障害につながります。 つまりリリーサーは、「前段のアプリケーションのリリースが完了したこと」を確認してから、次の段のアプリケーション(リポジトリ)のマージボタンを押さなければなりません。 ところで LIFULL には、 KEEL という全社で横断的に利用しているアプリケーション実行基盤があります。KEEL のワークロード監視ダッシュボードからは、現在のアプリケーションの状態、具体的には Kubernetes の Pod の状態を確認できます。 KEEL については、ぜひ以下をお読みください。 www.lifull.blog KEEL チームに依頼し、Pod の状態として、現在デプロイされている Git のコミットハッシュを追加してもらいました。これで、リポジトリの master ブランチのコミットハッシュと Pod のコミットハッシュを比較できるようになりました。 PodのステータスにSHA-1ハッシュを表示 リポジトリのコミットハッシュと Pod のコミットハッシュが同じになったのを見れば、アプリケーションがリリースされたかどうかを自動で判断できるようになりました。 また、リリースの進捗状況を Slack へ通知するようにしました。 通知内容は以下のようなものがあります。 現在どのアプリケーションをリリースしているのか GitHub の障害などによるマージの失敗 カナリアリリースの失敗 これらの内容を、対処方法も含めて通知することで、リリースの進捗状況をほぼリアルタイムで把握できるようにしました。 slack通知の例 同時に、このアプリケーションに、リリーサーが行っていたチェックを自動化するツールを組み込むことで、リリースのすべてを自動で行えるリリースシステムが完成することになりました。 リリースシステムによって自動化 Phase 3. リリースフローの高速化 リリーサー業務の自動化とは別に、リリースフローの高速化にも取り組みました。 従来のリリースフローをおさらいすると、次の図のようになっていました。 従来のリリースフロー(再掲) プール環境とは、本番環境のデータから個人情報をマスクしたデータを利用できる環境で、主に本番相当の環境としてテストを行うために利用されています。 この反映と確認のために 1 日かけることが、結果としてリリースまでのリードタイムを長くしていました。 そこで、プール環境にテスト環境と同じ develop ブランチを参照させることで、 テスト環境とプール環境へ同じ日に反映を行い 、リードタイムを短くすることを計画しました。 まずエンジニア全体にアイデアの共有を行い、そこで受け取った質問に対して回答や説明をしました。 当時の質問票 そして、機能追加に責任を持つ企画・エンジニアの代表の方にプレゼンを行い、合意を得ることで、リリースフローの変更を行うことに決まりました。 リリースフロー変更のプロポーザルの一部 最終的にできたリリースフローは以下のように、シンプルなものになりました。 新しいリリースフロー 1 日目 (Jira) リリース承認を開発グループ長・企画担当者からもらう (GitHub) 開発者が PullRequest を develop ブランチにマージし、テスト環境とプール環境に反映 (JIRA) リリースシステムがリリースチケットのステータスを変更 開発者がテスト環境とプール環境で動作確認 2 日目 リリースシステムが develop ブランチを master ブランチにマージし、本番環境に反映 (JIRA) リリースシステムがリリースチケットのステータスを変更 開発者が本番環境で動作確認 このリリースフローの変更に合わせて、以下のようなリリースのルールを設けました。 (即日リリース) 当日 11 時までにテスト環境とプール環境で動作確認できていれば、その日のうちにリリース可能 (即時リリース) プール環境での確認が必要なく、KEEL の機能で作成される Ephemeral Environment *1 で十分にテストできていれば、 直接 master ブランチにマージしてもよい ルール 1 によって、従来のリリースフローでリリースされるまでに約 40 時間かかっていたものが、最短 8 時間、つまり 約 5 倍速く リリースできるになりました。 また、ルール 2 に従えば、リリースまでのリードタイムはより短くなり、 もはやリードタイムは存在しません。 これらの改善だけが原因ではないと思いますが、年間のリリース数は、 即日リリース適用以前と比べて約 20 %増加しました 。 まとめ LIFULL HOME'S のリリースフローを改善することで、リリーサー業務を自動化し、リリーサーという存在を不要にできました。その結果、開発者が、開発業務により集中できるようになりました。 また、リリースフローを効率化したことで、リリースまでのリードタイムを大幅短縮し、価値提供が従来よりも速くなりました。 即日リリース PJ は、アーキテクトグループならではの「開発者に寄り添った開発生産性の向上」ができた、良い例だと思います。 今後も、エンジニアの生産性向上に向けて、さまざまな取り組みを行っていきます。 最後に、LIFULL ではともに成長していける仲間を募集しています。 12 月 2 日現在、アーキテクトグループでもメンバーを募集中です!よろしければこちらのページもご覧ください。 hrmos.co hrmos.co hrmos.co *1 : Firebase のプレビュー環境のような、PullRequest に紐づいて作成されるテスト環境
こんにちは。クオリティアーキテクトグループ(以下、QAG)でQAエンジニアをしている片野です。 QAGでは横断組織として自動テストやツール開発、プロセス改善などのしくみ作りに取り組んでいます。 今回は、横断QA組織が開発組織の品質に関する課題を見つけるための取り組みについて紹介します。 QA組織の紹介 背景 やったこと QAサークル QA通信 QA探検隊 まとめ QA組織の紹介 LIFULLでは設計や実装を行っている開発エンジニアがテストも行っています。 ですので、QAエンジニアのプロジェクトの関わり方としては、下記のような開発者への支援活動を行っています。 プロジェクト支援 テスト計画サポート 自動テスト相談 テストのお悩み支援 提供ツール・データ テスト仕様書の共通テンプレート Bucky 開発プロセス・そのたの活動 Bucky Shift-Left 探索的テスト実施・導入支援 組織の成り立ちについてはこちらの記事をご覧ください。 https://www.lifull.blog/entry/2019/12/15/000000 背景 横断QA組織の立場としては次のような課題があります。 社内には、さまざまな開発や保守のプロジェクトが同時に動いているので、QAGがすべてのプロジェクトへ均等な支援を行えない 各プロジェクト・組織の品質面で困っている事や改善したい事がQAGから見えにくい 各プロジェクトの困りごとは相談を受けてから気付くことが多く、QAGは受け身になることが多い やったこと 上記に書いた課題を解消するために、下記の3つの活動を実施しました。 QAサークル QAサークルは、QAGと開発チームのコミュニケーションの機会を増やす事を目的に、チームや個人でたまっている品質に関する課題やモヤモヤとした問題点の意見交換を行う場です。 開催形式はQAGがコンテンツを作成して発表する講義形式やみんなで意見を交わすディスカッション形式など、毎回試行錯誤しています。   ※過去に実施した探索的テストをテーマにした会の目次、チャーター作成ワークの結果 実施データ 開催回数:3回 開催頻度:3ヵ月に1回(1時間)程度 延べ参加人数:26人 開催テーマ:テスト全般、 テスト仕様書の共通テンプレート 、探索的テスト 狙い QAエンジニアと開発者が定期的にコミュニケーションをとり、相談がしやすい下地づくりをする QAGについて知ってもらう QAGが行っている施策の普及 工夫している点 タイムリーな話題をテーマに選定することや地道な広報活動を行った 開催テーマに関する良い事例を持っている方にインタビューし、QAサークル内で事例紹介をした ワークで実際に手を動かしてもらい理解度を高めた 効果 当初は想定していなかったエンジニア以外(企画職)の方の参加 少しずつ着実に、個別の相談や支援につながっている 参加者はイベントを通して、下記のような学びや気付きがあったそうです。 今まで知らなかった新しいテスト手法(探索的テスト)について理解が深まった  困ったらQAに相談しようという心持ちを作れた ※探索的テストについては別の機会にブログを書いてみたいと思います。 QA通信 Slackに最近のQAGの取り組みについての紹介記事を毎月投稿しています。 実施データ 投稿回数::16回 開催頻度:1ヵ月に1回 狙い QAGの認知度向上 QAGへのサポート依頼数増加 工夫している点 Block Kit Builder でフォーマットを統一し可読性を向上させた 絵文字を効果的に使用することで、書き手の感情を読み手に伝えるとともに情報をコンパクトにまとめて伝達した 効果 投稿した記事を読んだ人が、同僚に読むことを薦めているslackメッセージが複数見受けられ、QAGや開発したツールの認知度向上に寄与した QAGのツールやお知らせを気軽に発表できる場になった QA探検隊 Slackに投稿された品質に関するキーワード(たとえば「バグ」、「テスト」)を自動で定期的に検索し、困っている人がいないかを探しました。 実施データ 実施期間:2024/1〜2024/5 不定期に実行(社内イベント開催直後など) 狙い slack上に流れている品質課題に関する情報を能動的に取得する 工夫している点 社内で開発したツールを用いてslack api経由でエゴサーチを実施した 膨大な検索結果を効率的に仕分けするしくみを作成した 効果 テスト仕様書の共通テンプレートについての困り事を素早く拾う事ができ、改修や機能追加の優先度付けに役立った 各組織のSlackチャンネル毎のキーワード発話量を可視化でき、プロジェクト支援先を決める一助となった 下記の理由のため、現在QA探検隊は定期的な活動をしていません。 膨大な検索結果を効率的に仕分けするしくみを作成しましたが、完全な自動化ができていなく、手作業の部分もあるため費用対効果が見合わなかったため。 まとめ 横断QA組織が開発組織の品質に関する課題を見つけるための取り組みについて紹介しました。 活動を通して、課題を見つけるためには定期的に開発組織とコミュニケーションを取ることと、QAGが何をしているかを定期的に広報することが大切だと感じました。 今後も今回説明した活動やブログなどを活用して、QAGが実施している事を広めていきたいと思います。 最後まで読んでいただきありがとうございました。
こんにちは、エンジニアの中島です。 この記事はフロントエンドのパフォーマンス調査の記録を記事にしたものです。 弊社は自社プロダクトのパフォーマンス劣化を検知するために WebPageTest を導入し、数時間に一度自動計測をするようにしています。 そこでとある日から TBT (Total Blocking Time) が劣化しているようなので原因を調査してほしいという依頼が来ました。 現象確認 まずは本当に問題が起きているかの確認です。 WebPageTestのタイムラインを見てみます。 たしかにいくつかのタイミングで大きめのスタイルの再計算が走っていることがわかりました。 しかしながら、これらが"ある日を境に"、TBTの大幅な劣化を引き起こした犯人かどうかはまだわかりません。 数日分のタイムラインをみて確認する必要があります。 タイムライン情報を一つ一つ目でみて確認するのも大変だなと思ったのでこのタイムラインの元になっているjsonファイルをパースして導くことができないかと考えました。 生データを見るとあまり詳しくはわかりませんが以下のように配列で50万行ほどログがつまっているようでした。 { ... " traceEvents ": [ { " args ": { ... } , " name ": " XXXX ", " dur ": 2 , .... } , { " args ": { ... } , " name ": " XXXX ", " dur ": 1 , .... } , { " args ": { ... } , " name ": " XXXX ", " dur ": 3 , .... } , { " args ": { ... } , " name ": " XXXX ", " dur ": 1 , .... } , ... ] } nameにはどういうログなのかが記載されており、具体的にはFunctionCallやUpdateLayoutTree、ResourceFinishといった値がみられました。 またdurはdurationの略のようで1/1000ms単位の数値が入っていることがわかりました。 ざっくりとこのログに対応する モデル を作り、そこから10ms以上のUpdateLayoutTreeだけを抽出してその実行時間を出力してみれば少なくとも発生の根拠になるかなと考えました。 WebPageTestを簡単にスクレイピングして該当日の前後数日のtimeline情報のjsonファイルのurlを抽出し、そのコンテンツを取得してパースしていきます。 records = Performance :: Timeline :: Record .from_timeline( " path/to/timeline.json " ) p ([date, records .select(& :update_layout? ) .select{|record| record.duration > 10 } .map(& :duration )] (タイムラインデータのモデル) Performance::Timeline::Record class Performance :: Timeline :: Record def initialize (record) @record = record.deep_symbolize_keys end def name @record [ :name ] end def duration ( @record [ :dur ] || 0 ) / 1000.0 end def update_layout? name == ' UpdateLayoutTree ' end def self . from_timeline (file) result = JSON .parse( File .read(file)) records = result[ ' traceEvents ' ] records.map {|record| new(record) } end end 実行結果 [YYYY-MM-DD 19:41:43 +0900, [15.948]] [YYYY-MM-DD 19:41:43 +0900, [15.984]] [YYYY-MM-DD 19:41:48 +0900, [20.741]] [YYYY-MM-DD 19:41:09 +0900, [11.888]] [YYYY-MM-DD 19:42:24 +0900, [25.136]] [YYYY-MM-DD 19:41:59 +0900, [14.453]] [YYYY-MM-DD 19:42:15 +0900, [14.962, 13.14, 11.3871, 11.410]] [YYYY-MM-DD 19:41:49 +0900, [15.190, 15.919, 10,873, 11.733]] [YYYY-MM-DD 20:57:31 +0900, [16.211, 10.861, 15.085, 11.209]] [YYYY-MM-DD 20:59:48 +0900, [26.105, 19.971, 17.264, 11.762]] [YYYY-MM-DD 20:55:14 +0900, [18.890, 15.410, 12.570, 14.569]] [YYYY-MM-DD 19:37:16 +0900, [17.059, 11.72, 11.735, 13.011]] [YYYY-MM-DD 19:38:57 +0900, [14.816, 17.057, 13.122, 10.301]] 実行結果を見ると、確かにある日を境にスタイルの再計算が急増してることが確認できました 原因調査 ある日を境にということであれば、リリースだったり、GTMなどのツール経由のタグ挿入などが疑われます。 WebPageTestは数時間に一度回しているので、同じ手順で問題発生の時間を特定します。 まずはその特定された時間にリリースされたものを一つずつ洗っていきます。 しかしながら今回は対象となるページに影響を与えるような実装はどこにも含まれていませんでした。 そうなるとツール経由のタグ挿入の影響の線が濃くなります。 WebPageTestとchromeのdevtoolsで計測したPerformanceのログは中身が基本的に同じなので実際に手元のブラウザでそれらのツールをネットワークブロックした時とそうでない時のログを見比べて判断していきます。 とはいえ、色々なものの影響をうけるためtimelineが完全に安定することはなく、複数回分のログを取得して判断していく必要があります。 その工程が面倒なのでPuppeteerを使って自動化していきます。 require ' puppeteer-ruby ' ... Puppeteer .launch( headless : false ) do |browser| page = browser.new_page block_list = [ # タグ挿入系のサービスのパス (ex: gtm) # ... # ... ] # ネットワークリクエストのインターセプトを有効化 page.request_interception = true page.on( ' request ' ) do |request| if block_list.any? {|uri| request.url.include?(uri) } request.abort # リクエストを中止してブロック else request.continue end end # とありえず10回分計測する ( 1 .. 10 ).each do # トレースの開始 page.tracing.start( path : ' trace.json ' ) # 検証対象のページに移動 page.goto( ' https://www.homes.co.jp/path/to/target/ ' ) # トレースの停止 page.tracing.stop # トレース結果の読み込み trace_data = File .read( ' trace.json ' ) trace_json = JSON .parse(trace_data) records = Performance :: Timeline :: Record .from_timeline( ' trace.json ' ) p records.select(& :update_layout? ) .select{|record| record.duration > 10 } .map(& :duration ) end end このように検証したところ、今回はとあるタグ挿入ツールをブロックすると問題のスタイルの再計算はなくなることが判明しました。 ツールから挿入されてるタグを列挙し、二分探索でブロックしていき原因となるスクリプトを特定します。 今回はこのような手順で現象の確認と原因の特定ができました。 最後に この調査方法が王道的な調査方法かはわかりませんが、生ログを見る調査方法は汎用性の高いアプローチなので何かの参考になれば幸いです。 LIFULL ではともに成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co https://hrmos.co/pages/LIFULL/jobs/010-9999 hrmos.co
こんにちは、エンジニアの中島です。 この記事は2024年10月のLIFULL社でのアクセシビリティ改善およびやっていき活動の報告です。 この活動報告は月次で出すかもしれないし出さないかもしれないくらいの温度感で運用されています。 目次 目次 サービス改善 LIFULL HOME'S iOSアプリのお気に入り画面のアクセシビリティ対応 アクション操作物件にチェックを入れる例 育成・啓発の取り組み 各開発グループを対象にした勉強会実施 LIFULLアクセシビリティガイドラインのWCAG2.2対応の開始 アクセシビリティ1on1 お知らせ サービス改善 本期間中はアクセシビリティ推進部署による修正はなかったため、各サービス開発部署の取り組みのみを紹介させていただきます。 LIFULL HOME'S iOSアプリのお気に入り画面のアクセシビリティ対応 iOSアプリ開発チームが、お気に入り追加した物件の一覧画面のアクセシビリティ対応を行いました。 修正内容は以下の通りです。 ナビゲーション内の各ボタンの読み上げ内容を意味の伝わるものに修正 お気に入り解除や物件チェックなどの機能をVoiceOverで操作できるように修正 コントラストが不足していた一部テキストのコントラスト調整 各ボタンの十分なタップ領域確保 物件コンテナ内の各機能はアクションに登録することでVoiceOverからも操作ができるようになりました。 アクション操作物件にチェックを入れる例 アクション起動 チェック機能を呼び出し チェック実行 育成・啓発の取り組み 各開発グループを対象にした勉強会実施 もともとフロントエンドエンジニアやデザイナー向けに1対1のアクセシビリティ育成を行っているのですが、それに加えて社内の大半の開発部署がアクセシブルなアプリケーション開発を自力で行っていけるようになることを目的として部署ごとに学習機会を設けることにしました。 対象となる開発部署は20グループ程度で、本期間中は3つのグループに受講いただきました。 内容は スクリーンリーダーを利用している当事者の動画の視聴 動画視聴であらわになった問題点とそれに言及しているWCAG項目の対応確認 どのように実装すれば支援技術でUIの役割や状態を伝えることができるかの学習 となっています。 LIFULLアクセシビリティガイドラインのWCAG2.2対応の開始 現在のLIFULLアクセシビリティガイドラインはWCAG2.1をベースに作っており、新しく認知分野やモバイルデバイスへのサポートを含んだWCAG2.2の内容を取り込めておりませんでした。 ワーキンググループ内で各追加項目の読み込みを行い、各項目をどのように反映させるかを決定し、具体的な作業に入り始めました。 アクセシビリティ1on1 本期間中はフロントエンドエンジニア9人、アプリケーションエンジニア1人、デザイナー1人に対して行いました。 内容はWCAGの解説、APG(aria authoring practices)の解説、coga-usableの解説、実際のアプリケーション開発時でのアクセシビリティ配慮に関する相談などが主なものとなります。 お知らせ LIFULLではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
グループデータ本部データサイエンスグループの嶋村です。 LIFULLは、2024年10月15日(火)から開催される 国土交通省初となるデータコンペ 「 第1回 国土交通省 地理空間情報データチャレンジ ~国土数値情報編~ 」に対して 協賛 をしており、LIFULLの主力サービスの一つである LIFULL HOME’Sのデータセットを提供 しています。今回、その取り組みを紹介したいと思い記事を書きました。この記事をご覧の皆様も是非コンペにご参加いただけると嬉しいです。 本コンペでの企画運営は一般社団法人不動産建設データ活用推進協会(PCDUA)になりますが、LIFULLはPCDUAに特別会員として加入しており、​不動産および建設分野のデータ活用やデジタル推進のエコシステム構築を目指しています。その一環として、不動産・建設分野におけるデータ活用の活性化になればと思い、LIFULL HOME’Sのデータセットを提供することになりました。 これまで、学術機関向けに対しては、国立情報学研究所(NII)の情報学研究データリポジトリ(IDR)を通じて、「 LIFULL HOME’Sデータセット 」を公開してきました。当時の公開の経緯などは是非下記の過去の記事をご覧いただきたいと思いますが、多くの研究者の方々に利用していただき、たくさんの研究成果の創出にも貢献できたと実感しています。 「HOME'S」の物件・画像データセットを研究者に提供開始します! - LIFULL Creators Blog 「HOME'Sデータセット」に高精細度の間取り図画像データを追加しました - LIFULL Creators Blog 「IDRユーザフォーラム2023」参加報告 - LIFULL Creators Blog 一方で、民間に対して広く公開する取り組みは 今回が初めて となり、どの程度のコンペ参加者が集まるのか、コンペ参加者によってどのようなデータ分析がされるのか、とてもワクワクしています。社内のデータを外部へ提供することはリスクにもなりえるのですが、一方で多くの社外の方々との連携につながる良い機会でもあり、多くの方にデータに触れていただきLIFULLを少しでも知っていただくきっかけになればと願っています。 また、今回他の企業も協賛としてデータセットや分析環境を提供をして下さっており、 オープンデータと複数の民間企業のクローズドなデータを組み合わせて分析できる機会は非常に稀 だと思います。詳細は コンペ特設サイト をご覧下さい。 最後になりますが、データサイエンスグループでは「活用価値のあるデータを創出」し「データを活用した新たな機能やサービスの研究開発」を加速して下さる シニアデータサイエンティストを募集 しています。また、事業部内でもデータ分析で事業に貢献する データアナリストも募集 しております。 興味お持ちいただける方は、 カジュアル面談 も行っていますのでお気軽にご連絡ください。 hrmos.co hrmos.co
こんにちは、エンジニアの中島です。 この記事は2024年9月のLIFULL社でのアクセシビリティ改善およびやっていき活動の報告です。 この活動報告は月次で出すかもしれないし出さないかもしれないくらいの温度感で運用されています。 目次 目次 サービス改善 売買物件登録画面の各コントロール・グループへの名前設定 育成・啓発の取り組み LIFULL Creative School WCAG輪読会 アクセシビリティ1on1 お知らせ サービス改善 本期間中の改善取り組みのターゲットはLIFULL HOME'S 賃貸・流通マネージャーサイトです。 こちらはLIFULL HOME'Sをご利用いただいている会員様(不動産会社様)が、物件を掲載する際にお使いになる管理画面になります。 諸事情で発表できないものもありますが公開可能な取り組みを紹介させていただきます。 売買物件登録画面の各コントロール・グループへの名前設定 前回のお知らせでは賃貸の物件登録画面におけるコントロールの名前設定についてご紹介しましたが、売買物件登録画面についても同様の作業を行いました。 名前のないコントロール・及びグループに適切な名前を設定し、支援技術ユーザーにとってもコントロールの目的が理解しやすいように修正を行いました。 育成・啓発の取り組み LIFULL Creative School 弊社のデザイナーや広報・企画職の一部を含むクリエティブ本部のメンバーは、LIFULL Creative Schoolという一回2時間程度の勉強会を不定期で行っています。 9/13に行われた会では、啓蒙活動の一貫としてアクセシビリティ推進WGのメンバーが講師を担当し、アクセシビリティに関する基本的な知識を共有しました。 用語の整理から始まり、弊社ビジョンや品質基準、Design Philosophyとの照らし合わせ、障害当事者のUT動画の視聴、WCAGの簡単な解説、すぐに身につけられるチップスの共有・自社事例を交えた座談会などを行いました。 WCAG輪読会 アクセシビリティやっていき勢向けにWCAGの輪読会を隔週で行っています。 本期間中は9月19日に行われました。 範囲は達成基準1.4.6〜1.4.7です。 アクセシビリティ1on1 本期間中はフロントエンドエンジニア1人、アプリケーションエンジニア1人、デザイナー1人に対して行いました。 過去の育成対象者が育成する側に回るといった循環ができるようになってきました。 内容はWCAGの解説、APG(aria authoring practices)の解説、coga-usableの解説、実際のアプリケーション開発時でのアクセシビリティ配慮に関する相談などが主なものとなり ます。 お知らせ LIFULLではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
プロダクトエンジニアリング部の海老澤です。 普段は LIFULL HOME'S の 賃貸領域 のフロントエンド開発をしています。 今回はLIFULL HOME'S の UI 構築を手助けするコンポーネントカタログ(以下「カタログ」)を作った取り組みについて紹介します。 最初にお断りしておくと、この「カタログ」はいわゆる 「デザインガイドライン」「デザインシステム」のことではありません🙇 背景 LIFULL では日々プロダクトの改善を行っており、UI の改修もそのひとつです。 しかし以前から複数のフロントエンドエンジニアが開発生産性と品質面での課題を感じていました。 開発生産性の課題 LIFULL HOME'S の UI はすべて、サービスのベースとなる「LIFULL HOME'S デザインガイドライン」をもとに設計されています。 このデザインガイドラインは現在 Adobe XD で公開されており、コードベースのものはありませんでした。 LIFULL HOME'S ではマイクロサービス化が進んでいるため、必然的に各リポジトリで同じような HTML/CSS をゼロから再生産しています。 共通利用できるコードがあれば開発生産性の向上にもつながるのに、それがないのはもったいないと感じました。 品質面の課題 UI の改修のすべてをフロントエンドエンジニアで受け持っているわけではなく、普段あまり HTML/CSS を触らないエンジニアが担当することもあります。 そのためマークアップやスタイリングの品質がまばらでレビュアーの匙加減で決まっているところがありました。 また、アクセシビリティ品質の担保にも課題がありました。 社内ではアクセシビリティを重視する文化が定着しており、 LIFULLアクセシビリティガイドライン に準じてコーディングをしています。 しかしその品質をすべてのエンジニアが担保するのは非常に難易度が高いです。 フロントエンドエンジニアが改修する場合でも、複雑な js とアクセシビリティ要件が絡むコンポーネントの場合は開発に時間がかかってしまいます。 このような問題を解消するため、「誰もが汎用的に使える実装リファレンス集」がほしいと考えました。 実現したいこと このカタログで実現したいことは以下です。 プロダクトの技術スタックに依存せずコピー&ペーストできる マークアップとアクセシビリティの品質を備えていること カスタマイズに制約は設けないこと 1. プロダクトの技術スタックに依存せずコピー&ペーストできる コンポーネントの展開方法には大きく分けて2つあります。 npmパッケージを配布して利用箇所でimportするやり方と、ファイルまたはコードのコピー&ペーストで展開するやり方です。 今回はコピー&ペーストを採用しました。 パッケージ化のデメリット パッケージ化は以下の理由から開発生産性を損ねる可能性が高いと判断しました。 カスタマイズの自由度が制限される サービスごとに多様な意思決定・UI 個別最適を行っているため、微調整がしづらくなる しくみが複雑化する カスタマイズ用のパラメータを追加し続けるとコンポーネント自体の使い勝手が悪くなる 改修の意思決定のためのコストがかかり、スピード感が損なわれる コピー&ペーストであればコードだけ持っていって各サービスへの最適化もしやすいですし、必要な機能だけのせていけるため複雑化も避けられます。 採用技術とコピー&ペーストの親和性の高さ LIFULL HOME'S はマイクロサービスが進んでおり、フロントエンドではさまざまな技術選定がされています。 ただ Tailwind CSS と Stimulus はほとんどのプロダクトで採用されており、既存の技術構成に後載せすることも比較的容易です。 そのためこれらをベースにコードを掲載していくことにしました。 Tailwind CSS は付与されたクラスから自動的に CSS を作成してくれるため、サービスに合わせてスタイリングの微調整が可能です。 Stimulus も以下のような特徴からコピー&ペーストとの親和性がとても高いです。 バックエンドのフレームワークを問わない 依存関係がなくビルドシステムを必要としない アタッチも HTML に data 属性を付与するだけで良い 汎用的な振る舞いを切り出したコントローラの組み合わせで作れる これらの理由からコピー&ペーストの方が開発生産性の向上のアプローチには良いと考えました。 2. マークアップとアクセシビリティの品質を備えていること コンポーネントカタログに掲載されるパーツは、最低限のマークアップとアクセシビリティの品質を備えていなければなりません。 品質の高いものがコピー&ペーストできることで、利用するプロダクトの品質レベルが底上げされます。 アクセシビリティ要件を満たす実装はしばしば難しく知識と経験を要しますが、コピー&ペースト元のコードがアクセシブルであることで、ジュニアレベルのエンジニアでもアクセシブルなプロダクトづくりに貢献することができます。 3. カスタマイズに制約は設けないこと 使用にあたって改変の制限はありません。 このカタログでは 素 HTML + Tailwind CSS + Stimulus をそのまま載せているので、スペーシングや振る舞いの調整も容易です。 そこからコンポーネント化するのは各サービスの開発者に委ねており、特に縛りは設けていません。 このカタログはあくまでも「実装リファレンス集」としての機能のみを提供し、UI 実装の初動を速く正確に行うことを目指しています。 成果物 LIFULL HOME'S Frontend Recipes こうしてできあがったのが LIFULL HOME'S Frontend Recipes です。 開発は社内の有志3名で行いました。 フレームワークは Astro を使っています。 軽量かつ mdx でサクサク書けるためカタログサイトにはぴったりのフレームワークでした。 現在は以下のコンテンツを配信しています。 Foundation カラートークン、アイコン、フォントなど最小限の要素 Objects ボタン、カード、リスト、フォームパーツなどの Component role, aria-* の付与のほか、フォーカス時や強制カラーモード時でも見やすい状態を担保 ちなみに英語で作成されていますが、これは近年海外拠点にある子会社と一緒に開発することが増えているためです。 ポイント コード出力形式の設定が可能 プロダクトに合わせて以下の設定が可能です。 HTML <-> JSX の切り替え インデント形式の指定 タブ or スペース、およびその数 Tailwind CSS の prefix の指定 後から Tailwind CSS を入れたリポジトリで prefix を指定しているパターン があるため コードの出力形式を設定できる機能 アクセシビリティに関する注意事項の記載 コードをコピー&ペーストするだけでなく、見て学べるような作りにしました。 checkbox コンポーネントのアクセシビリティ注意事項 フォーカスリングの可視性や強制カラーモード時の表示など、ピンと来にくいものはサンプルコードで比較できるようにしています。 フォーカスリングの可視性の比較 強制カラーモード時の表示 デザインガイドラインに掲載されていないコンポーネントも掲載 Q&Aコンポーネント たとえばこちらの Q&A 方式のコンポーネントですが、デザインガイドラインには載っていません。 しかし実際の業務ではたびたび見かけるパターンのため、カタログには収録しています。 とにかくあるあるなパターンを集めていくことで開発生産性の向上を狙っています。 ちなみにガイドラインに記載されているコンポーネントについてはラベリングを行っていますので、そこで区別は可能です。 テキストリンクのページ 終わりに カタログの公開後に自分でも使ってみましたが、どんな環境であろうとコピー&ペースト→クラス微調整だけで済むためかなりスピーディなコーディングができました。 既存のプロダクト改修はもちろんですが、新規のプロジェクトでプロトタイプをさっと作る時にも役立ちそうです。 今後は DatePicker などの難易度の高いコンポーネントや、 Behavior としてコンポーネントのスタイルに依存しない Stimulus Controller を拡充していく予定です。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは! LIFULLエンジニアの吉永です。 普段はLIFULL HOME'SのtoC向けCRMチームにてエンジニアリングマネジャーをやっています。 本日は私がCRMチームにジョインしてからの4年間で行ってきたチームとしての技術負債解消活動について紹介します。 技術負債解消活動の変遷、技術負債解消と新規開発がどのようなバランスで進められてきたのか、現状はどうなっているか、今後取り組んでいきたいことについて触れていきます。 アジェンダ 技術負債解消活動の変遷 技術負債解消と新規開発のバランス 現状について 今後取り組んでいきたいこと 最後に 技術負債解消活動の変遷 2020年~2021年ごろ 当時はレコメンド物件をユーザー毎に取得してメールコンテンツを作成するサーバ、LINEサーバからのWebhookで応答するチャットボットサーバ、メールを送る為のマーケティングオートメーションツールなどさまざまなシステムがプログラミング言語もプラットフォームもバラバラな状態で構築されていました。 EC2インスタンスもたくさんあり、デプロイもほとんどが手動対応が必要な状態でした。 これらの課題を解消しようと動き始めたのが2020年ごろの話です。 役割が細かくなりすぎてしまっていたアプリケーションサーバ群を一つにまとめ、プログラミング言語にGoを採用、デプロイはGitHub Actionsで自動的に行えるようにシステムをリニューアルして2021年の初頭にファーストリリースされました。 この時リリースされたシステムの名称がeos(Elastic Omni channel Service)と言います。 その当時の活動については下記の記事にて紹介されています。 www.lifull.blog www.lifull.blog www.lifull.blog 2022年~2023年ごろ eosリリース以降はレガシーシステムをeosへ移植していきました。 さまざまなプラットフォームかつプログラミング言語もバラバラだった状態は少しずつ解消されていき、この頃にはeosの開発時にGo、マーケティングオートメーションツールでは専用のプログラミング言語といったようにサーバサイドに関しては開発時に使用する言語は2種類になりました。 eosはクリーンアーキテクチャを採用したことで初期実装は苦労しました。 しかし機能拡張はしやすく、LINEやメールの動的コンテンツを作成する為の開発はeos上で完結するようになり、生産性が上がって開発速度はどんどんと速くなっていったように思います。 その当時の活動については下記の記事にて紹介されています。 www.lifull.blog www.lifull.blog 技術負債解消と新規開発のバランス 2020年~2022年ごろまではおおむね50:50くらいの比率だったと思います。 個人で見ると、技術負債解消タスクが8割くらい、また別の人は新規開発系が8割くらいなどメンバーによってどちらに比重を重く持っているかの違いはあれど、チーム全体では半分ずつの割合でした。 2023年5月ころからはConventional Commitsを採用して、コミットメッセージベースでの技術活動バランスの可視化ができるようにという取り組みも始めました。 チームによって適切な状態は異なると思いますが、コミットメッセージベースで集計、可視化することで直近の活動は意図した活動になっているか?の一つの判断材料には使えると思っています。 Conventional Commitsを採用してみて得た知見などは下記の記事にて紹介しています。 www.lifull.blog 現状について 重めの技術負債解消タスクはここ数年でだいぶ片付いてきたので、直近だと新規開発の割合が少しずつ増えています。 技術負債解消はシステムリニューアルではなく、リファクタリング系タスクの割合が増えていて、ソースコードの保守性向上を目指した取り組みが多いです。 新たなチャレンジをする機会も増えており、機械学習に挑戦してくれるメンバーがいたり、イベントに参加して新技術のキャッチアップを行ったりができています。 www.lifull.blog www.lifull.blog 今後取り組んでいきたいこと マーケティングオートメーションツールの初期導入から約10年が経過していますが、メールを配信する為の顧客情報連携アーキテクチャは基本的にあまり変わっていません。 配信するコンテンツの内容や、コンテンツを配信する為の仕掛けはマーケティングオートメーションツールのバージョンアップや企画チームの各種施策で改良されていっていますが、ベース部分の改良まで踏み込めていないのが現状です。 ベース部分のところではコンテンツを配信するトリガーとなる情報やトリガーの引き方に制限があり、現状だと「サイトで問合せしてからn日経過した人にこのコンテンツを」という施策はやりやすいのですが、サイトで起こしたアクション(例えばサイトで特定の物件をお気に入り登録してからn日後、サイトの最終訪問からn日後など)を起点としてトリガーに対応させようとするとちょっと工数がかかってしまい、工数がボトルネックとなってそのような施策は優先度が上がりづらい課題があります。 現在はこのベース部分の改良がしやすいようにする為のリファクタリングや一部リニューアルを少しずつ進めており、この活動がゆくゆくはさまざまなコンテンツ配信トリガーを実現することに繋がっていくと思っています。 LIFULLのプロダクトエンジニアリング部 部長の河津は下記インタビューの中で「思いついたらすぐにリリースできるように、開発の速度をもっと速めていきたい」と語っています。 techplay.jp 私達のチームでもこの考え方に沿って各種施策を素早くリリースできるように継続的に技術負債解消と新規開発に取り組んでいきます。 最後に 技術負債解消と新規開発のバランスはそのチームの置かれている状況やフェーズによって最適なものが変わると思います。 このような時間配分になっていることが望ましいというバランスは常に意識し、定性的な面だけでなく定量的なデータを可視化して定期的にチームメンバーと認識合わせをしておくことが必要だと思います。 プロジェクト管理ツールのチケットへ入力した工数、日々創出されていくコミットメッセージなど活動のバランスを可視化する為のデータはあるけど、活用しきれていないというのはどこの組織でも多かれ少なかれあると思いますので、LIFULLで取り組んでみて得た知見を今後もブログなどで共有できればと思います。 最後まで読んでいただきありがとうございました。 LIFULLでは共に成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
フロントエンドエンジニアの嶌田です。 アイコンは、UIデザインにおいて欠かせないパーツです。弊社が提供する不動産情報サイトであるLIFULL HOME'Sでも、多数のアイコンが使われています。 ウェブページにアイコンを埋め込む手段は無数にあります。あなたはいくつ言えますか? それぞれのメリットとデメリットについて説明できるでしょうか? この記事では、LIFULL HOME'Sのフロントエンドで選ばれているアイコン表示方法およびその理由を説明します。その後、アイコンの管理や実装にまつわる不便な点をサービス横断的に解消するために作られた社内ツールと配信システムである、 LIFULL Icon CDN を紹介したいと思います。 私たちのアイコン実装方法 無数にあるアイコン実装方法のうち、私たちが選んだ実装方法は極めてシンプルです。 < img src = "assets/icon-star.svg" width = "24" height = "24" alt = "" > そう、 img 要素を使うのです。これまでさまざまなアイコン実装方法を試してきましたが、結局これが一番だろうということになりました。 なぜなのか? いくつかの観点から見てみましょう。 極めて手軽に使える アイコンを使いたい場面で img 要素を埋め込むだけなので、事前の準備や仕組みの構築が不要です。ブラウザがHTMLをパースしながら img 要素に出会うと、外部リソースとして画像データを読みにいき、自動的にその部分にはめ込んで表示してくれるのです。当たり前ですね! SVGスプライト *1 のようなテクニックは、ページ内で使う可能性のあるアイコンをあらかじめひとまとめにしておく必要があります。不要なアイコンを読み込まないようにする等の最適化を想定すると、トリッキーな仕組みを構築しなければならないでしょう。 img 要素を埋め込むだけなら、 どのプロダクトでも 極めて手軽に使えるのです。 十分なパフォーマンス 表示速度は十分に速いです。 img 要素のぶんだけ別々にHTTPリクエストが発行されることから、パフォーマンスがよくない印象を持たれるかもしれませんが、実際に速いです。 リクエスト数が増える問題は、HTTP/2のストリーム多重化により気にする必要がなくなっています。インラインSVG *2 に比べると、画像のデコード処理を別スレッドに逃がすことができるので、メインのHTMLのパースを素早く終えられるというメリットもあります。 標準的なHTTPキャッシュの恩恵を受けられることも大きな利点です。2度目の表示にはブラウザキャッシュが参照され、ネットワークリクエストは発生しません。 また、 loading="lazy" 属性を使った最適化も期待できるでしょう。アイコンは得てして装飾的に使われるので、読み込みを遅延させても問題ない場面が多いでしょう。 誰にとっても分かりやすい どストレートなやり方なので、HTMLのごく基礎的な知識さえあれば仕組みを理解できます。エンジニアと他職種との連携の円滑さにもつながるはずです。 アクセシビリティ アイコンの代替テキストは、 img タグの alt 属性によって提供します。まったくひねりのない方法ですが、問題なく動作します。 アイコンは、実装方法によってはアクセシビリティ上の問題を引き起こしてしまうことがあります。 たとえばアイコンフォントは、フォントデータが読み込めなかったときやユーザーが意図的にフォントを上書きしていた場合、アイコンが表示されなくなってしまう場合があります。SVGスプライトやインラインSVGでのアプローチは、代替テキストの指定に難があり、幅広いサポートを目指すためにWAI-ARIAに頼らざるを得ない場合があります。 他にも、OSのハイコントラストモード(強制カラーモード)への対応や、色反転モードに対する対処が望まれます。それについては記事の後半で触れたいと思います。 色の変更 SVGスプライト、インラインSVG、アイコンフォント *3 の利点は、色変更がCSSで手軽にできることでした。 img 要素に戻るのは、色変更の手軽さを放棄することにならないでしょうか? どうということはなく、私たちはこのようにします。 < img src = "assets/icon-star--red.svg" width = "24" height = "24" alt = "" > 色を変えたアイコンを読み込めばよいのです。 あっ、まだブラウザの戻るボタンは押さないでください。本気か?と思うかもしれませんが、本気です。たしかに手軽とはいえないでしょう。使う色が増えるたびにファイルを増やさなければいけません。 でもたとえば、クエリパラメーターで色が指定できたらどうでしょう? < img src = "assets/icon-star.svg?color=ff0000" width = "24" height = "24" alt = "" > 裏側の仕組みはともかく、少なくとも色の指定はやりやすくなりそうですよね。 アイコンをもっと手軽に使いたい! 正直なことを言うと、マークアップエンジニアにとって、アイコンは非常に面倒くさいです。まじめに取り組むと、アイコンを書き出して最適化し、配信の仕組みに乗せ、アイコン用コンポーネントを実装しなければなりません。いずれの工程もノウハウの塊です。 LIFULLは多数のサービスを提供しています。不動産情報サイトのLIFULL HOME'Sだけをとっても、いくつものサービスの集合体です。これら一つひとつのサービスごとに、別々のエンジニアがアイコンのお決まり手順を踏んでいると考えると、とても非効率な気がしませんか。 そこで私たちは、アイコンにまつわるこれら実装上の課題をサービス横断的に解消するために、 LIFULL Icon CDN と呼ばれる仕組みを構築しました。 LIFULL Icon CDNの特色 LIFULL Icon CDNは大きく分けて2つのコンポーネントで構成されています。 APIと呼ばれるコンポーネントは、ドメイン名を持ち、エンドユーザーからのリクエストに応えます。前段にCDN *4 を配置しており、静的キャッシュを持つことで高速化とオリジンサーバーの負荷軽減をしています。 Appと呼ばれるコンポーネントは、社内向けのアイコン管理アプリケーションです。外部からのアクセスはできません。アイコンを一覧し、必要なアイコンをページに埋め込むためのコードを取得することができます。 ここからは、スクリーンショットをまじえつつ、LIFULL Icon CDNでどんなことができるのかを見ていきます。 定義済みアイコンを一覧できる LIFULL Icon CDNが取り扱うアイコンは、デザインガイドラインで定義されたアイコンです。プロダクト横断的に利用されることが想定されたアイコンがずらっと並んでいます。執筆時点で300余りのアイコンが登録されています。 開発者は、この中から使いたいアイコンを選びます。 埋め込みコードをコピーできる 詳細ページからはエンドユーザーがアイコンにアクセスするためのURLと、埋め込むためのコード(HTML形式およびJSX形式)が取得できます。 アイコンをサービスで利用するための開発者の手順はとてもシンプルです。HTMLのコードをコピーして、コードに貼り付ければ終わりです。アイコン利用のための事前準備は不要です。 色を自由に変更できる アイコンの詳細ページ上からアイコンの色を設定できます。色を変更するとクエリパラメーターに反映され、指定した色のアイコンのURLが取得できます。 たとえば日本地図のアイコンはデフォルトでオレンジと黄色の2色から構成されていますが、黒に塗りつぶしたり、 https://icon.lifull.com/lh/japan-map-twotone?fill=black 2色を別々に塗り替えることも可能です。 https://icon.lifull.com/lh/japan-map-twotone?modify[yellow]=mono-300&modify[orange]=accent-blue 無秩序に使われることを避けるため指定できる色には制約があり、デザインガイドラインで定義された色トークンの中から選定する仕様にしています。 高パフォーマンス 配信用アプリケーションの前段にCDN(Amazon CloudFront)を設け、静的キャッシュを担わせることで、ほとんどすべてのリクエストをCDNから応答するようにしています。そのため動作は非常に軽快です。 各アイコンのSVGデータはオブジェクトストレージ(Amazon S3)に保管されていますが、すべての色指定に対する物理的なSVGデータが準備されているわけではありません。アプリケーション層で応答時に色変換の処理を適用しています。CDNには処理後のSVGレスポンスをキャッシュさせているため、軽快な動作を保っています。 アクセシビリティを高める工夫 いくつかの配慮を加えることでアイコンの使いやすさが向上します。 1つは、 強制カラーモードへの配慮 です。WindowsにはOSレベルの強制カラーモード、いわゆるハイコントラストモードが搭載されています。有効にすると、ウェブページを含むOS全体の配色が事前に定義された色で描画されるようになります。 img 要素を通じて埋め込まれている画像は、デフォルトでは強制色が適用されません。一方、アイコンの背景色は透過されているため、たとえば 黒基調の強制色における黒いアイコンは背景に溶け込んで見えなくなってしまう という問題が起こります。 2点目は、 色反転モードへの配慮 です。特に配慮が必要なのはスマート反転モードと呼ばれるもので、macOSやiOSなどのAppleのOSに搭載されています。スマート反転モードでは、OS全体の色をネガポジ反転しますが、画像や動画は反転されない挙動をとります。 この場合も、たとえば白の背景に置かれた黒のアイコンは、スマート反転モードを適用すると、背景色のみが反転するため、 背景に溶け込んで見えなくなってしまう 状況に陥ってしまいます。 2つの問題に対処するため、各SVGファイルには次のようなCSSを宣言しています。 < svg xmlns= "http://www.w3.org/2000/svg" ...> < style > @media (forced- color s: active) { @media (prefers- color -scheme: dark) { [ fill ] :not([ fill = "none" ]) { fill: #fff !important } } @media (prefers- color -scheme: light) { [ fill ] : not([ fill = "none" ]) { fill: #000 !important } } } </ style > ... </ svg > そして、LIFULL Icon CDNを使用する側には次のようなCSSを適用します。 /* 強制カラーモードが有効なとき、img要素経由で読み込まれるSVGに prefers-color-schemeメディアクエリが引き継がれない問題がある。 SVG側からカラースキーム文脈を取得するために、以下のスタイルを アイコン読み込み元に記述する必要がある。 */ @media (forced- color s: active) { @media (prefers- color -scheme: light) { img [ src ^= "https://icon.lifull.com/" ] { color -scheme: light; } } @media (prefers- color -scheme: dark) { img [ src ^= "https://icon.lifull.com/" ] { color -scheme: dark; } } } /* macOSのスマート反転モードが有効のとき、img要素は反転対象から除外される。 背景が透過されているアイコンが色反転から除外されると意図せずコントラストが 不足する可能性があるため、以下のスタイルにより反転に含まれるようにする。 */ @media (inverted- color s: inverted) { img [ src ^= "https://icon.lifull.com/" ] { filter : invert ( 0 ); } } 細かく書くと1記事分の分量になってしまうため省略しますが、ブラウザ側の不具合に対処しつつ、LIFULL Icon CDNを使う img 要素にだけアクセシビリティの向上のためのスタイルを適用しています。 デザイン上の工夫 LIFULL Icon CDNはエンジニアのデザイナーとエンジニア発案のプロジェクトとして始まりましたが、デザイナーも巻き込み、アイコンの統制や管理の仕組みを含む全体的なプロジェクトになりました。その意味ではアイコンのデザインシステムと呼べるものかもしれません。 デザイナーからみたLIFULL Icon CDNについては、別記事として公開されています。ぜひご一読ください! note.com おまけ 実装方法のバリエーション 以下はLIFULL Icon CDNのドキュメントに記載されている内容で、アクセシビリティに配慮したいろいろな実装パターンを解説しています。 もっとも基本的な使い方 アイコン詳細ページで取得できるHTMLをコピーして、そのままプロダクトのコードに貼り付けます。LIFULL Icon CDNチームはこれが ベストプラクティス であると考えており、推奨しています。 < img src = "https://icon.lifull.com/l/chevron-right" width = "24" height = "24" alt = "" /> 使用例1:ボタン内のアイコンとして使用する(装飾としての利用) テキストラベルの横に配置される場合や、単なる装飾としてのアイコン利用の場合、代替テキストは不要です。 alt 属性は空文字列に設定してください。なお、 alt 属性自体は省略しないでください。 次へ < button class = "flex items-center rounded border border-mono-400 bg-white px-4 py-2" > < span > 次へ </ span > < img src = "https://icon.lifull.com/l/chevron-right" alt = "" width = "24" height = "24" class = "ml-2 size-4" > </ button > 使用例2:ボタン内のアイコンとして使用する(機能ラベルとしての利用) テキストラベルが併記されず、アイコンがボタンやリンク内における唯一の要素である場合、代替テキストが必要です。 alt 属性に機能や状態を表す代替テキストを指定してください。 < button class = "flex items-center rounded border border-mono-400 bg-white p-3" > < img src = "https://icon.lifull.com/lh/star-twotone?fill=orange" alt = "お気に入り" width = "56" height = "56" class = "size-6" > </ button > 使用例3: 背景画像として利用する(非推奨) 背景画像には代替テキストが設定できず、強制カラーモードで非表示になってしまうため非推奨です が、装飾としての用途に限っては使用しても構いません。 次へ < button class = "button" > 次へ </ button > < style > .button { min-height : 24px ; padding-right : 32px ; background : url( 'https://icon.lifull.com/l/chevron-right' ) no-repeat right center / 24px ; } </ style > 使用例4:インタラクションに応じて色を切り替える シンプルさを重視し、切り替え前後のアイコンをあらかじめ埋め込んでおくアプローチを推奨します。 以下は、ボタンへのマウスオーバーで色が若干濃くなるアイコンのコード例です。 < button class = "group flex items-center rounded border border-mono-400 bg-white p-3" > < img src = "https://icon.lifull.com/lh/star-filled" alt = "お気に入り" width = "56" height = "56" class = "size-6 group-hover:hidden" > < img src = "https://icon.lifull.com/lh/star-filled?fill=orange-800" alt = "お気に入り" width = "56" height = "56" class = "hidden size-6 group-hover:block" > </ button > 使用例5:任意の色を使用する 原則として、定義済みのカラートークンを使用します 。どうしても任意の色を使用する場合は、 mask-image プロパティを使うテクニックが使用できます。 < span class = "mask-icon" style = " background-color: rebeccapurple --mask-icon: url('https://icon.lifull.com/lh/star-filled'); " ></ span > < style > .mask-icon { display : block ; width : 24px ; height : 24px ; mask: var( --mask-icon ) no-repeat center / 100% ; } </ style > Tailwind CSSを使う場合は以下のようになります。 < span class = "block size-6 bg-[rebeccapurple] [mask:var(--mask-icon)_no-repeat_center/100%]" style = "--mask-icon: url('https://icon.lifull.com/lh/star-filled')" ></ span > 次期バージョンも進行中 LIFULL Icon CDNはすでに社内エンジニアに便利に使われていますが、社内向けアプリケーションの機能拡充が予定されています。 現在のアイコンの追加・更新作業は、AWSコンソールにログインしS3に直接アップロードする運用フローになっています。かかる手間やヒューマンエラーのリスクに対処するために、管理アプリケーション上から直接アップロードや更新等の作業が行えるようになっていく予定です。 クレジット <LIFULL Icon CDN運営チーム> LIFULL HOME'S事業本部 エンジニア Dongjae Park、Shin Mingyu クリエイティブ本部 デザイン部 ぼこ、URITA RIKI スペシャルサンクス 鄭 在淳、曺 承鉉、そめ お知らせ LIFULLは主体性を重んじる社風で、LIFULL Icon CDNのようなエンジニア発案のプロジェクトが多数進行しています。他職種の協力も手厚いです。転職先に弊社をぜひご検討ください! hrmos.co hrmos.co *1 : SVGスプライト……ページ内で使うアイコンセットを事前にsvg要素でまとめて定義しておき、利用箇所でフラグメント参照を使って手軽にアイコンを使えるようにする仕組み。CSSで色変更がしやすい利点がある。 https://css-tricks.com/svg-sprites-use-better-icon-fonts/ *2 : インラインSVG……svg要素を使いSVG画像をHTMLにインラインで記述すること。CSSで色変更がしやすい。 *3 : アイコンフォント……アイコンデータをフォントファイルの字形データとして格納し、文字としてアイコンを表示するテクニック。CSSで色変更がしやすい。 *4 : CDN……Content Delivery Network。ウェブコンテンツをユーザーに高速で配信するために、世界中の複数のサーバーにデータを分散させる仕組み。
こんにちは、エンジニアの中島です。 この記事は2024年8月のLIFULL社でのアクセシビリティ改善およびやっていき活動の報告です。 この活動報告は月次で出すかもしれないし出さないかもしれないくらいの温度感で運用されています。 目次 目次 サービス改善 賃貸物件登録画面の各コントロール・グループへの名前設定 取扱物件一覧ページの詳細条件設定ダイアログ内のコントロールの名前設定 育成・啓発の取り組み WCAG輪読会 アクセシビリティ1on1 お知らせ サービス改善 本期間中の改善取り組みのターゲットはLIFULL HOME'S 賃貸・流通マネージャーサイトです。 こちらはLIFULL HOME'Sをご利用いただいている会員様(不動産会社様)が、物件を掲載する際にお使いになる管理画面になります。 諸事情で発表できないものもありますが公開可能な取り組みを紹介させていただきます。 賃貸物件登録画面の各コントロール・グループへの名前設定 賃貸物件登録画面には物件情報を入力するためのフォームがありますが、その中には名前の設定されていないコントロールや、名前は設定されているものの文脈が不明瞭なラジオボタン・チェックボックスを含んだグループなどがありました。 これらのコントロール・及びグループに適切な名前を設定し、支援技術ユーザーにとってもコントロールの目的が理解しやすいように修正を行いました。 取扱物件一覧ページの詳細条件設定ダイアログ内のコントロールの名前設定 不動産会社様がLIFULL HOME'Sに掲載しようと登録した物件の一覧ページには、確認したい物件を絞り込むための検索フォームが設定されていますが、より詳細な条件設定を行えるダイアログが存在しています。 このダイアログ内の各コントロールも名前が未設定のものが多くありましたので適切な名前を指定する修正を行いました。 育成・啓発の取り組み WCAG輪読会 アクセシビリティやっていき勢向けにWCAGの輪読会を隔週で行っています。 本期間中は8月22日に行われました。 範囲は達成基準1.4.4〜1.4.5です。 アクセシビリティ1on1 本期間中は先月と同じくフロントエンドエンジニア6人、デザイナー1人に対して行いました。 内容はWCAGの解説、APG(aria authoring practices)の解説、coga-usableの解説、実際のアプリケーション開発時でのアクセシビリティ配慮に関する相談などが主なものとなります。 お知らせ LIFULLではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは。プロダクトエンジニアリング部でAndroidのネイティブアプリエンジニアをしている久野です。 今回はイマーシブモデルルームというApple Vision Pro向けのアプリを日本発売日に合わせてリリースした話をします。 なぜAndroidのネイティブアプリエンジニアの私がApple Vision Proの開発を担当したかというと、元々学生の頃からインターンや趣味でVRコンテンツ作成に携わっていた中、Apple Vision Proの発売を聞き「これを使ったアプリの開発をしてみたい!」と強く思い、上長や会社に挑戦してみたいと相談したところ、快く承諾して頂けたのがきっかけです。 イマーシブモデルルームについて 苦労したこと 技術調査および実機開発 リリース時期 デザイン、モデルの確認 工夫した機能 空間ビデオ スプラッシュの作成 まとめ イマーシブモデルルームについて イマーシブモデルルームは没入感のある高精細かつリアルなモデルルームや臨場感のある3D映像(空間ビデオ)で物件の魅力を体験することができるApple Vision Pro向けのアプリです。 2024年6月28日(金)にApple Vision Pro向けアプリにおいて国内不動産ポータルで初めて *1 の提供です。 App Storeにおいて公開していますので、Apple Vision Proをお持ちの方は体験してみてください。 prtimes.jp イマーシブモデルルーム LIFULL Co., Ltd ナビゲーション 無料 apps.apple.com 苦労したこと 技術調査および実機開発 今回イマーシブモデルルームはUnityを利用して開発を行いました。 UnityのApple Vision Pro用SDKであるPolySpatialは2023年の11月中旬に公開されましたが、情報が非常に少なくアプリのビルド等を進めるのも困難でした。また、SDK自体の更新も頻繁に行われていたのでそれに合わせてUnityを利用してApple Vision Proのアプリで何が実現できて何が出来ないかということの検証を進めていました。最新の情報をキャッチアップすることが重要だったため、Unityのフォーラムや個人の方が公開していらっしゃる記事を常にチェックしながら開発を進めました。 Apple Vision Proの実機に関連することでも苦労しました。Apple Vision Proの基本的な操作は目線と手を利用して行います。そのため、アプリケーションの開発においても実機を利用して、目線と手を使った操作の確認が必要でした。ただ、SDKが公開された11月段階ではアメリカにおいても実機は発売されていないので、Unity Editor及び Apple Vision ProのSimulatorを利用して確認を行ないました。 しかし、Unity EditorやApple Vision ProのSimulatorでは両手の入力をするようなコンテンツ(イマーシブモデルルームの機能ですと3Dモデルのマンションを回転、サイズの変更をする)といったジェスチャーの確認を行うことが困難です。そのため、弊社で3月にアメリカで発売された実機を入手して技適申請を行うまでの間はデベロッパラボを月に一度程の頻度で応募をして利用させて頂きました。それによって開発中のアプリを実機で挙動確認させて頂きました。 developer.apple.com リリース時期 リリース日を日本での発売日に合わせるために行っていた取り組みを説明します。 Apple Vision Proが日本で発売される時期がわからない状況で、発売日にリリースすることを目標にして進めていました。 そのことから、アプリのリリース時期を3段階に分けてそれぞれの段階においてどのようなアプリをリリースするかということをチームで定めていました。 チームの計画としては下記3パターンを考えていました。 7月リリースパターン 9月リリースパターン 12月リリースパターン 7月のリリースパターンでは時間も少なかったため必要最小限のプロダクトを開発し、そこからブラッシュアップしていくという方法を取りました。早い段階から動く形にしておくということを意識しながら進めることで想定よりも早い段階でのリリースに対応することが出来ました。 デザイン、モデルの確認 まずUnity Editorを用いてビジュアルのチェックを行なっていました。しかし、Apple Vision Proの特性を最大限に活かすためにはこれだけでは不十分です。実際にアプリをApple Vision Proにビルドし、モデルがどのように見えるか、ユーザー体験がどのように実現されるかを確認することが不可欠でした。 この確認作業ではデザイナーの方と密に連携し、デザイン及び体験の確認を何度も繰り返しました。特にリリース直前にはデザイナーの方とUnity Editorの画面を一緒に見ながら確認及び細かい修正を行いました。 またモデル更新の反映など軽微な確認にはPolySpatialの Play to Device 機能を活用しました。Play To Deviceを利用することでBuildをせずにUnity Editorから再生したものをApple Vision ProのSimulatorで確認できます。これにより開発プロセスの効率が大幅に向上しました。 工夫した機能 空間ビデオ 今回アプリの中で空間ビデオを表示する機能も提供しています。 空間ビデオはUnityのPolySpatialの VisionOSVideoComponent でも対応しています。上記のドキュメントに従って設定を行なっていたのですが、動画が再生されないという問題にぶつかりました。そのため以下のような対応を行いました。 初めに、Volume cameraの設定です。 以下二つの設定に対して同じものを指定する必要がありました。 Project Settings > PolySpatial > Default Volume Camera Window Config Scene内の Volume Camera > Volume Window Configuration この二つに設定しているVolume cameraを同じものにすることで、Apple Vision Proの実機で空間ビデオの再生をすることが出来ました。 また、動画はコーデックが空間ビデオに対応されているものである必要がありました。iPhone等で撮影した後でPCに送る際にはオートコーデックが発生して別のコーデックに変更がかからないように注意する必要がありました。 スプラッシュの作成 今回のアプリの中でユーザーがアプリを開始するたびにスプラッシュを表示しています。 Unityにはデフォルトで Splashを表示する機能 が存在していますが、今回のPolySpatialでは利用することが出来ないということをフォーラムで見かけ、実際に調査を行ったのですが利用することが出来ませんでした。 そのため、デフォルトの機能を利用せずにスプラッシュの代わりにアプリ起動時にアプリのキービジュアルを表示する機能を作成しました。 実装面では、Volume cameraの WindowEvent を活用しました。 こちらを利用して、アプリが開かれた際にキービジュアルを数秒間表示してから、アプリのコンテンツを体験出来るように実装しました。 まとめ Apple Vision Proの日本発売日に合わせて、「イマーシブモデルルーム」を無事リリースしました。技術調査や実機開発、デザイン確認など、数々の課題はありましたが、3Dモデルデータを利用した住まい探しの新たな可能性を広げるプロダクトを生み出す第一歩になったのではと思います。これからも、住生活を革進することの出来るプロダクトを開発していこうと思います。 LIFULLでは共に成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co hrmos.co *1 : 2024年6月28日時点、自社調べ
KEELチーム の相原です。 前回のエントリは「LLMを利用したPlatform Engineering」でした。 www.lifull.blog 今回は、小さい経路最適化ミドルウェアを実装してAZ間通信を削減した話を書きたいと思います。 背景 我々KEELチームはKubernetsベースの内製PaaSであるKEELを開発しており、LIFULLのほとんどのサービスがこのKEEL上で動いています。 www.lifull.blog そして、KEELは巨大なマルチテナントのKubernetesクラスタとしてAWSの複数のAvailability Zone(以下AZ)に展開されていて、多くのmicroservicesが互いに通信しあっています。 そのためAZ間通信はプラットフォームとして重要な関心事の一つです。 レイテンシやAWSのAZ間通信に対する課金を最小限に抑えるため、なるべくAZ間通信を減らしたいという要求があります。 我々のプラットフォームでは現在LLMのサポートを強めており、それに伴い扱うペイロードのサイズも増加傾向にあることからも対応の優先度が上がってきています。 www.lifull.blog 実際にKubernetesの Topology Aware Routing をプラットフォーム側で配布してデフォルトで有効にしたり、Istioの Locality Load Balancing をクラスタ全体で有効にして、クラスタ内の通信の経路最適化に努めてきました。 ※Istioが有効になっているPodではTopology Aware Routingは機能しないため、全てのPodにIstioを導入できていない場合はクラスタ全体として両方を有効にする必要があります 一方で、KEELはステートレスなクラスタとして永続性が必要とされるデータストアは基本的にクラスタ外に依存しており、未だ最適化できていない経路も存在しています。 そこでその残されたAZ間通信も削減すべく、小さい経路最適化ミドルウェアを実装する判断に至りました。 背景 実装 シンプルなConnection Pool 余談: Connection Storm Topology Aware Routing 注意点 最後に 実装 さて、KubernetesやIstioが実現するクラスタ内通信の経路最適化はKubernetesのService Discoveryに依存しているため、クラスタ外への通信で真似することはできません。 つまりクライアントはサーバがどのトポロジに配置されているかを知る共通の手段を持っていないということです。 サーバ側がDNSのSRVレコードなどに対応していればそれをそのまま利用できますが、そうでもない場合全てのサーバに新規にService Discoveryを入れるということはあまりに大変です。 そこで我々はTCPサーバとして振る舞うConnection Poolのミドルウェアとして経路最適化を実現することに決めました。 LIFULLにはRubyのUnicornなど1リクエスト1プロセスなプロセスモデルのアプリケーションサーバがいくつかあり、元々プロセスをまたいだConnection Poolを実現したいという要求があります。 しかし、プラットフォームとしてProxySQLを提供するなど特定のユースケースでは対応できていたものの、 あらゆるAZ間通信 には対応できていませんでした。 Connection Poolでの経路最適化のアイデアはシンプルで、維持している接続をトポロジごとに管理して、なるべくクライアントと同じトポロジの接続を取り出して使うだけです。 つまりConnection Poolが維持している接続を疑似的にService Discoveryとして利用するということです。 これが実現できれば色々と都合が良さそうなのでやっていきましょう。 シンプルなConnection Pool まずは普通のTCPプロキシにシンプルなConnection Poolを実装していきます。接続を取り回すだけのソフトウェアになるのでGoが無難でしょう。 MinIdleConnections , MaxIdleConnections でアイドルな接続をコントロールできるようにして MaxIdleTime , MaxLifetime で生存期間を設定できるよくあるやつです。 設定は大体こういうインタフェースでしょうか。 Connection は MaxIdleTime の判定で利用する returnedAt を持てるようにしただけの net.Conn の薄いラッパーです。 type ConnectionPoolStrategy int const ( FIFO ConnectionPoolStrategy = iota LIFO ) type ConnectionPoolOption struct { MaxConnections uint MaxIdleConnections uint MinIdleConnections uint MaxIdleTime time.Duration MaxLifetime time.Duration Dialer func (context.Context) (net.Conn, error ) ConnectionPoolStrategy ConnectionPoolStrategy } type ConnectionPool struct { option *ConnectionPoolOption connectionMutex sync.Mutex connections []*Connection idleConnections []*Connection } コンストラクタは省略するとして、接続を取り出す部分はこんな感じになります。 func (p *ConnectionPool) Get(ctx context.Context) (*Connection, error ) { p.connectionMutex.Lock() defer p.connectionMutex.Unlock() defer func () { go p.prepareIdleConnection(ctx) }() for { connection, err := p.getIdleConnection(ctx) if err != nil { return nil , xerrors.Errorf( "failed to get idle connection: %w" , err) } if connection == nil { break } now := nowFunc() if (p.option.MaxIdleTime > 0 && now.Sub(connection.returnedAt) > p.option.MaxIdleTime) || (p.option.MaxLifetime > 0 && now.Sub(connection.createdAt) > p.option.MaxLifetime) || !connection.isHealthy() { _ = connection.Close() p.removeConnection(ctx, connection) continue } return connection, nil } connection, err := p.newConnection(ctx) if err != nil { return nil , xerrors.Errorf( "failed to create connection: %w" , err) } return connection, nil } ロックを取りながらアイドルな接続を取り出し、生存期間を満たしていれば死活監視をしてから接続を返します。 アイドルな接続がなければ新規に接続を作成して返します。 prepareIdleConnection は MinIdleConnections を満たすようにアイドルな接続を用意していて、 isHealthy はソケットに read(2) して EAGAIN or EWOULDBLOCK であることの確認です。 func (c *Connection) isHealthy() bool { // A zero time value disables the deadline. _ = c.conn.SetReadDeadline(time.Time{}) syscallConnection, ok := c.conn.(syscall.Conn) if !ok { return false } rawConnection, err := syscallConnection.SyscallConn() if err != nil { return false } healthy := false if err := rawConnection.Read( func (fd uintptr ) bool { b := make ([] byte , 1 ) _, err := syscall.Read( int (fd), b) if errors.Is(err, syscall.EAGAIN) || errors.Is(err, syscall.EWOULDBLOCK) { healthy = true } return true }); err != nil { return false } return healthy } そして、使い終わった接続を維持するためのPutを実装します。 MaxIdleTime で判断するための returnedAt の更新も忘れないようにしましょう。 func (p *ConnectionPool) Put(ctx context.Context, connection *Connection) error { p.connectionMutex.Lock() defer p.connectionMutex.Unlock() if p.IdleConnections() < int (p.option.MaxIdleConnections) { connection.returnedAt = nowFunc() p.idleConnections = append (p.idleConnections, connection) } else { _ = connection.Close() p.removeConnection(ctx, connection) } return nil } ちなみに、ここまでの内容のTCPプロキシでの利用イメージはこんな感じになります。 func main() { ... semaphore := make ( chan struct {}, maxConnections) shutdown := make ( chan struct {}, 1 ) wg := sync.WaitGroup{} go func () { ctx := context.Background() for { local, err := listener.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() remote, err := connectionPool.Get(ctx) if err != nil { return } defer connectionPool.Put(ctx, remote) defer remote.Cancel() 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() } }() } }() // Graceful shutdown quit := make ( chan os.Signal, 1 ) signal.Notify(quit, syscall.SIGTERM) <-quit time.Sleep(time.Duration(lameduck) * time.Second) close (shutdown) listener.Close() wg.Wait() } 余談: Connection Storm この実装では、維持している接続が MaxLifetime に達した時に強制的にその接続を破棄します。 古い接続が使われ続けることを防ぐためのものですが、これには比較的大きなトレードオフがあります。 それは Connection Storm と呼ばれる現象で、維持されていた接続が一斉に MaxLifetime によって切断されることで、新規の接続が大量に確立して接続先に大きな負荷がかかるというものです。 それを防ぐためには リトライなどでよく知られるJitter を入れるとよいです。 Jitterはランダム性を注入する概念で、生存期間の判定箇所にJitterを入れて一斉に切断されることを防ぎます。 リトライにおけるJitterは、接続先の障害発生時などに複数のリトライが同時に行われるとカスケード障害を起こしてしまうため、リトライにランダム性を持たせるために利用されています。 type ConnectionPoolOption struct { MinIdleConnections uint MaxIdleTime time.Duration MaxLifetime time.Duration + Jitter func (time.Duration) time.Duration Dialer func (context.Context) (net.Conn, error ) ConnectionPoolStrategy ConnectionPoolStrategy } now := nowFunc() - if (p.option.MaxIdleTime > 0 && now.Sub(connection.returnedAt) > p.option.MaxIdleTime) || - (p.option.MaxLifetime > 0 && now.Sub(connection.createdAt) > p.option.MaxLifetime) || + if (p.option.MaxIdleTime > 0 && now.Sub(connection.returnedAt) > p.option.Jitter(p.option.MaxIdleTime)) || + (p.option.MaxLifetime > 0 && now.Sub(connection.createdAt) > p.option.Jitter(p.option.MaxLifetime)) || !connection.isHealthy() { _ = connection.Close() p.removeConnection(ctx, connection) 実際のJitterの実装はこんな感じになると思います。 jitterPercentage でどの程度ランダム性に開きを持たせるかを調整できるようにするイメージです。 テストの時には引数の duration をそのまま返せばいいでしょう。 func (duration time.Duration) time.Duration { jitter := time.Duration( float64 (duration) * jitterPercentage * (rand.Float64()* 2 - 1 )) return duration + jitter } 閑話休題。 Topology Aware Routing ここまででシンプルなConnection Poolの実装が完成したので、ここからようやく本題の経路最適化の実装に入ろうと思います。 アイデアは先に書いた通りシンプルで、維持している接続をトポロジごとに管理して、なるべくクライアントと同じトポロジの接続を取り出して使うだけのものを目指します。 トポロジはIPをベースに判断するとして、今回はサイドカーとしてクライアントのプログラムと同じサーバで動かすことを想定しているため、クライアントのトポロジは自身のIPから判断することができそうです。 トポロジの一覧を --topologies=a=192.168.0.0/24,b=192.168.1.0/24,c=192.168.2.0/24 のように受け取り、プログラム内では以下のように取り扱うことにします。 type Topology struct { Name string CIDR net.IPNet } var topologyList []Topology var topologyName string if topologyAwareRouting { for _, t := range strings.Split(topologies, "," ) { if t == "" { continue } parts := strings.Split(t, "=" ) if len (parts) != 2 { log.Fatalf( "invalid topologies: %s" , topologies) } _, cidr, err := net.ParseCIDR(parts[ 1 ]) if err != nil { log.Fatalf( "failed to parse CIDR %s: %+v" , parts[ 1 ], err) } topology := Topology{ Name: parts[ 0 ], CIDR: *cidr, } topologyList = append (topologyList, topology) if topology.CIDR.Contains(net.ParseIP(ownIP)) { topologyName = topology.Name } } } CIDRを持ったトポロジの一覧が topologyList として管理されていて、自身のIPから判断したクライアントのトポロジを事前に topologyName として定義しておきます。 ( topologyName を事前に定義するのは計算コストを嫌っているだけで、当然クライアントの接続から都度判断することも可能なのでサイドカーではなく独立してデプロイすることも可能です) Kubernetesでは自身のIPを Downward API から取得可能で、AWSでも インスタンスメタデータ から取得できるのでこれを使います。 それでは先ほどのConnection Poolの実装にこれらを与えてTopology Aware Routingに対応していきましょう。 まずは topologyList を受け取るれるようにしつつ、維持している接続をHashMapでトポロジごとに管理できるように構造体を変更します。 type ConnectionPoolOption struct { MinIdleConnections uint MaxIdleTime time.Duration MaxLifetime time.Duration Jitter func (time.Duration) time.Duration + TopologyList []Topology Dialer func (context.Context) (net.Conn, error ) ConnectionPoolStrategy ConnectionPoolStrategy } type ConnectionPool struct { connectionMutex sync.Mutex connections []*Connection - idleConnections []*Connection + idleConnections map [ string ][]*Connection } 次に、接続を取り出す Get でクライアントのトポロジ topologyName を受け取って、同じトポロジの接続を取り出す部分です。 - func (p *ConnectionPool) Get(ctx context.Context) (*Connection, error ) { + func (p *ConnectionPool) Get(ctx context.Context, topologyName string ) (*Connection, error ) { p.connectionMutex.Lock() defer p.connectionMutex.Unlock() defer func () { go p.prepareIdleConnection(ctx) }() for { - connection, err := p.getIdleConnection(ctx) + connection, err := p.getIdleConnection(ctx, topologyName) if err != nil { return nil , xerrors.Errorf( "failed to get idle connection: %w" , err) } if connection == nil { + if n := p.pickRandomIdleTopologyName(); n != nil { + topologyName = *n + continue + } break } now := nowFunc() if (p.option.MaxIdleTime > 0 && now.Sub(connection.returnedAt) > p.option.Jitter(p.option.MaxIdleTime)) || (p.option.MaxLifetime > 0 && now.Sub(connection.createdAt) > p.option.Jitter(p.option.MaxLifetime)) || !connection.isHealthy() { _ = connection.Close() - p.removeConnection(ctx, connection) + p.removeConnection(ctx, topologyName, connection) continue } return connection, nil } connection, err := p.newConnection(ctx) if err != nil { return nil , xerrors.Errorf( "failed to create connection: %w" , err) } return connection, nil } pickRandomIdleTopologyName はフォールバックの処理で、同じトポロジの接続がなかった際に適当なトポロジの接続を代わりに使います。 フォールバックの戦略を外から与えられるようにしてもいいですね。 他はそれぞれ先のHashMapを扱って接続を取り出したり削除したりしてるだけです。 細かい話ですが、 idleConnections がHashMapになることで総接続数を得るための計算コストが O(1) でなくなってしまうため、別でAtomicなカウンターを用意しておいた方がよいです。 最後に Put の実装です。 func (p *ConnectionPool) Put(ctx context.Context, connection *Connection) error p.connectionMutex.Lock() defer p.connectionMutex.Unlock() + topologyName := p.topologyName(connection.RemoteAddr()) + if p.IdleConnections() < int (p.option.MaxIdleConnections) { connection.returnedAt = nowFunc() - p.idleConnections = append (p.idleConnections, connection) + p.idleConnections[topologyName] = append (p.idleConnections[topologyName], connection) } else { _ = connection.Close() - p.removeConnection(ctx, connection) + p.removeConnection(ctx, topologyName, connection) } return nil } net.Conn の RemoteAddr() で接続先のIPを取得し、以下の関数で topologyList として受け取ったCIDRから適切なトポロジを取得します。 func (p *ConnectionPool) topologyName(addr net.Addr) string { for _, t := range p.option.TopologyList { switch addr := addr.( type ) { case *net.TCPAddr: if t.CIDR.Contains(addr.IP) { return t.Name } case *net.UDPAddr: if t.CIDR.Contains(addr.IP) { return t.Name } } } return "" } これで経路最適化を行うTopology Aware Routingも完成です。 簡易的な実装による楽観的な経路最適化ですが、接続先のService Discoveryに依存しない あらゆるAZ間通信 に対応するものができました。 例えばAWSのElastic Load BalancerはAZごとにIPを持っており、それがDNSラウンドロビンで返却されます。 このミドルウェアをクライアントに導入することで、AWSのロードバランサに対する接続であってもAZ間通信を削減することに成功しました。 なお、AWS Application Load Balancerはデフォルトで クロスゾーン負荷分散 が有効になっていて、これを導入してもロードバランサとUpstreamの間ではAZ間通信が発生する可能性があります。 Upstreamが十分にAZに分散されていればクロスゾーン負荷分散を無効にする判断ができるかもしれません。 注意点 このミドルウェアに限らずこういった経路最適化で考慮しなければならないことがあります。 それはトポロジ間の分散です。 クライアントとサーバでトポロジ間の分散が不十分な場合、例えばクライアントが一つのトポロジに集中してデプロイされてしまっている場合に、そのトポロジのサーバに負荷が集中してしまいます。 これを防ぐためには慎重にトポロジ間で分散させる必要があり、それにはKubernetesの Pod Topology Spread Constraints を利用することができます。 これはデプロイ時にトポロジ間のばらつきに対する制約をかけられる機能で、LIFULLではKubernetes Manifestを生成するためのコードジェネレータでKubernetesのTopology Aware Routingの設定とともにデフォルトの設定として配布しています。 www.lifull.blog ただしこれはあくまでデプロイ時の制約で、Podがスケールインした後のリバランスまでは面倒を見てくれません。 そこで、あわせて kubernetes-sigs/descheduler を導入して RemovePodsViolatingTopologySpreadConstraint を有効にすることをお勧めします。 これにより Pod Topology Spread Constraints に違反しているPodを再スケジュールしてくれるため、その後の再デプロイで再度制約をかけることができます。 またこの制約を機能させるためにはノードのトポロジ間のバランスも重要で、 cluster-autoscaler などで適切に設定する必要があることにも注意してください。 (AWSのAuto Scaling Groupはスポットインスタンスを使っていなければ いい感じにやってくれる ので特に気にする必要はない気がします) 最後に Connection Poolのミドルウェアに簡易的なTopology Aware Routingを実装することで あらゆるAZ間通信 を削減することに成功しました。 これによりレイテンシの改善とAZ間通信に対する課金を抑えることができます。 AZ間のレイテンシはLIFULLの環境では最悪ケースで5msにも及ぶことがあり、それなりのインパクトを期待しています。 マルチプロセスなアプリケーションサーバを運用しているとそれっぽいConnection Poolが欲しくなることはあって、手前味噌ですがこのアプローチはちょうどいい塩梅なのではないでしょうか。 最近は調子に乗ってこれにRedisのプロトコルパーサを載せて、RedisへのクエリのRead/Writeの分離機能を実装するなどもしてしまっています。 KEELではRead Heavyなキャッシュなどの用途に向けて運用が簡単なRedis Sentinelクラスタを提供していまして、クライアント実装に手を入れづらい際にこれを入れるだけでいい感じに動くというちょうどいいTCPプロキシとしての立ち位置を確立しつつあります。 KEELチームでは、いくつかの機能をProxy-Wasmで実装してIstio経由で配布していたりもしますが、こういう実装はProxy-Wasmでは実現しづらく第二のService Meshとして今後もこのちょうどいいTCPプロキシを改善していく予定です。 (オーバーエンジニアリングとの境目を気にしながら、)プラットフォームとして必要なものがあれば何でも実装するKEELチームにもし興味を持っていただけた場合は、是非カジュアル面談をさせてください! hrmos.co hrmos.co 次のエントリでは、KubernetesのPodの終了際のログが欠損してしまう問題をeBPFで unlink(2) を bpf_override_return して対処した問題について書こうと思うので購読してお待ちください :D
KEELチーム の相原です。 これまでKEELチームではKubernetesベースの内製PaaSであるKEELを開発・運用しながら、合間で社内で汎用AI(仮)と呼ぶAutoGPT実装 keelai を開発してきました。 www.lifull.blog 我々はあくまでプラットフォーマーであり、目指すところはLLMを利用したPlatform Engineeringです。 いくつかの取り組みが実を結んできたのでここで紹介したいと思います。 APIの提供 GitHub ActionsからのLLM利用 Slack AutomationsによるLLMノーコードツールの提供 Chrome Extensionで実現するシームレスなLLM体験 コマンドラインツールでのLLM活用 Jupyter Notebookでの高度なLLM活用 まとめ APIの提供 何はともあれまずはAPIの提供でしょう。 ここでは我々が開発するAutoGPT実装である keelai をAPIとして提供しています。 移植性のためにOpenAI互換のインタフェースになっていて、当然Server Sent Eventsにも対応しています。 KEELチームで開発しているSAMLベースの認証基盤の下で社内に公開しており、Kubernetesクラスタ内通信も含めてIstioのAuthorizationPolicyで認可しています。 FastAPIを使っていてちょうどよくWebインタフェースが用意されていることもあり、開発者はAPI Keyなどを発行することなくすぐにAPIを利用することができます。 IstioのRequestAuthenticationで任意のJSON Web Tokenを検証することもでき、あらゆる場所から安全に呼び出すことが可能です。 また、セグメントごとのトークンベースのRate Limitを実装してあり、SAML認証済みのユーザやJSON Web Tokenの任意のペイロード・呼び出し元ワークロードに応じてヘッダを付与し、それをもとにSliding Windowで流量制限しています。 呼び出し元ワークロードの判定をヘッダから行うためには、まずIstioのPeerAuthenticationでmTLSを強制して呼び出し元のEnvoyを保証し、Envoyが付与する x-envoy-peer-metadata をProxy-Wasmでパースしてワークロードを取得するなどちょっとハックしているので、この辺はまた別のエントリで書くとしましょう。 当然サイト信頼性に責任を負う立場として、各種メトリクスやOpenTelemetryによるトレースの収集、適切なリトライやエラーハンドリングによってProduction Readyな品質となっており、コンシューマ向けのプロダクトからもこのAPIが呼ばれています。 各種ベクトルデータベースの提供 や Semantic Search用のコンポーネントの提供 もしていますが、嬉しい誤算で汎用AI(仮)ということもあり基本的に現在はこのAPIを介してあらゆる機能を実現しています。 (OpenAIへの課金を懸念して llama.cpp ベースのオープンソースLLMのAPIサーバも提供していますが、これも嬉しい誤算でLLMの価格競争による低コスト化が進んでいるので今のところ出番はなさそうです) 今後はKubernetesクラスタであるKEEL自身のLLMを使ったAIOps(?)を強化していく予定で、Alertmanagerから発火する運用自動化用のKnative Serviceが存在しているため、そこから同様に keelai のAPIを呼ぶことで更なる自動化を進めていきます。 keelai はSelf-hostingされた汎用AI(仮)として社内の監視基盤とも連携できるのでここは色々と遊べそうです。 GitHub ActionsからのLLM利用 APIには前述した通りIstioのレイヤでJSON Web Tokenの検証と認可が実装されているため、同じく我々が提供する GitHub Actions Self-hosted runners を利用することでGitHub Actionsから以下のように呼び出すことができます。 www.lifull.blog $ ID_TOKEN = $( curl -sSL -H " Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN} " " ${ACTIONS_ID_TOKEN_REQUEST_URL} &audience= ${GITHUB_ACTION_PATH} " | jq -r .value ) $ curl -sSL -X POST -H " Authorization: Bearer $ID_TOKEN " -H " Content-Type: application/json " https://keelai-api.keelai.svc.cluster.local:8080/v1/chat/completions -d " $( echo $@ | jq -sc --arg model " $MODEL " ' . as $messages | {"model": $model, "messages": $messages} ' ) " これには GitHubのOpenID Connect を利用していて、サーバ側はこのようにGitHubから送られてきたJSON Web Tokenのclaimsをもとに認可することが可能です。 apiVersion : security.istio.io/v1beta1 kind : AuthorizationPolicy metadata : name : keelai-api spec : action : ALLOW rules : - when : - key : request.auth.claims[iss] values : - https://token.actions.githubusercontent.com - key : request.auth.claims[repository_owner] values : - lifull selector : matchLabels : app.kubernetes.io/name : keelai-api これにより各リポジトリにAPI Keyを配布することなく、気軽にLLMを利用した自動化を始めることができています。 さて、LLMによるソフトウェアエンジニアリングの生産性向上でやはり最初に思いつくものはコードレビューでしょう。 既にOpenAIのAPIを叩いてコードレビューするようなものはありふれていますが、目指すべき先は社内のコーディングガイドラインや過去の障害実績をもとにしたコードレビューだと思います。 そのためには社内情報にアクセスできる必要があり、そこで汎用AI(仮)を目指す keelai が役に立ちます。 keelai はAutoGPT実装として、目的達成のため自律的に複数の関数を使い分けながら回答を作成できるため、(今はまだ精度に改善点はあるものの)必要に応じて過去の障害情報を参照しながらコードレビューすることが可能です。 我々はプラットフォーム戦略としてKubernetes Manifestからドキュメントまでを一手に生成する コードジェネレータも開発しており 、これによって多くのGitHub Actionsを配布しています。 その中でコードレビューの機能も実験的に提供しており、プロンプトに以下のような指示を加えて、 git diff の出力をもとにコードレビューの結果を reviewdog の形式で出力させています。 The output MUST be `%f:%l: %m` format. <snip> %f: File name %l: Line number %m: Message </snip> Line number MUST be the line number of the file that calculated from the diff. こうしておくことで reviewdog を使って雑にPullRequestへのインラインコメントが可能です。 echo " $body " | reviewdog -efm =' %f:%l: %m ' -reporter = github-pr-review -filter-mode = nofilter このメソッドは手軽な割に意外と汎用的で、セキュリティに特化したコードレビュー機能が社内で配布され始めたりと好感触です。 他にも、利用しているOSSのバグチケットをGitHub Actionsのスケジュール実行で監視して翻訳・要約してIssueとして起票するチームがあったりとGitHub ActionsからのLLM利用が進んでいます。 Slack AutomationsによるLLMノーコードツールの提供 次はSlack Automationsを利用した疑似的なLLMノーコードツールを提供している話です。 Slack AutomationsとはSlackの有料プランで利用できるワークフロービルダーです。 リアクション時やスケジュール実行でチャンネルへの発言からスプレッドシートへの追記まで割となんでもできます。 api.slack.com Deno Slack SDK を使ってステップと呼ばれる任意の処理も自作可能で、組み込みのステップは多くないものの自分で作ってしまえば問題ありません。 LIFULLではこのSlack AutomationsからLLMを呼び出せるようにして、LLMノーコードツールとして職種問わず広く社員が業務を自動化できる環境を整えています。 とは言うものの、Slack Automationsは2024年8月現在でステップのSelf-hostingに対応していません。 つまりプライベートネットワーク上で公開されているAPIにアクセスすることができません。 幸い我々のAutoGPT実装である keelai はSlack Botとしても実装されており、Slack AutomationsからメンションすることでLLMを発火させることができました。 www.lifull.blog 実際の利用例は以下のようなイメージです。 基本的にはリアクションなどを起点に組み込みの Reply to a message in thread を使いながらメンションしてLLMを発火させているだけです。 リアクション時にペイロードとして渡ってくるメッセージのURLからメッセージ本文を取得するステップを実装していますが、これはFew-shot promptingをしやすくするためのものであり必須ではありません。 そして設定したリアクションをすると以下のようにLLMが呼び出されます。 これもなかなか好評で、プログラミングなしでLLMによる業務の自動化が可能であることから職種問わず広く利用されています。 ちなみに、回答結果へのリアクションは keelai 自身が行っており、Slackの chat.update を通してServer Sent Eventsを表現しているためそれの完了通知という意味もありつつ、ここから更にリアクション起点のSlack Automationsを発火させるためという意味もあります。 Slack Automationsには簡易的なフォームを作成する機能もあり、 総務部門など各部署が自分達で相談の一次受けのフォームを用意するといったことが行われています 。 汎用AI(仮)である keelai は当然社内でのこれまでの相談内容や社内ドキュメントをSemantic Searchできるようになっているため、そこそこの精度で回答することが可能です。 そして、 Submit すると事前に定義されたプロンプトにフォームの入力を埋め込んでLLMが呼び出されます。 Slack Automationsにはスプレッドシート連携の機能もあるため、完了通知のリアクションを起点に質問と回答結果のペアをスプレッドシートに追記しておいて、それをもとにプロンプトの改善に繋げるといった運用になっています。 他にも経理部門が為替の日次確認の自動化を実現していたり、RSSによって通知されるニュースをリアクションで要約するワークフローが普及していたりと、こういったことをエンジニアでなくてもすぐに実現できる点が汎用AI(仮) + Slack Automationsの強みです。 元々Slack Botとして keelai を開発し始めた狙いでもありますが、コミュニケーションツール内でLLMを利用することにより社員が日頃からLLMの活用を目にしやすく、利用者の拡大にも一役買っていると思います。 我々はプラットフォーマーとして多くのステップを Deno Slack SDK で開発しており、ループ機構を実現するステップやSlack Automations側でOAuthのサポートがあるためそれを使ったGitHubやGoogle Apps Scriptへアクセスするステップなどが既に実装されています。 (Self-hostingのサポートを待ちつつ)今後もLLMノーコードツールとして機能開発を続けていきます。 Chrome Extensionで実現するシームレスなLLM体験 LIFULLのプラットフォームを広く開発する我々KEELチームは、プラットフォーム戦略の一環としてChrome Extensionも開発しています。 KEELチームが開発する汎用AI(仮)が keelai なので、このChrome Extensionの名前は keelext です。 当初はGrafanaのユーザ体験を補完するなどプラットフォームの利用補助が主なスコープでしたが、汎用AI(仮)の開発に伴いそのスコープが拡大してきています。 LLM周りでの主な機能としては、対応している社内サービスなどのページを開くと keelai のアイコンが表示され、それをクリックするとモーダルが開いて例えばConfluenceの場合はそのページの要約が生成されます。 他にはGrafana上のエラーメッセージの解決方法を過去のエラー対応のやり取りをもとに提案してくれる機能や、右クリックのメニューからは選択範囲の文字列の翻訳・要約するなどありがちな機能も実装してあります。 生成結果の言語はブラウザの設定言語に従いますが、モーダル内でそのまま翻訳することも可能です。 このシームレスなユーザ体験により、利用者は keelai のアイコンを見かけたら とりあえずクリックするだけで何かがいい感じに生成される ということが実現されています。 他にもフォームの自動入力など社内の各種プラットフォームのLLM連携が実現されており、LLMを利用したPlatform Engineeringを支える重要なコンポーネントとなっています。 当然これも keelai のAPIを利用することで実現されていて、 APIの提供 で紹介した社内の認証基盤の認証フローを chrome.identity.launchWebAuthFlow で踏んでいます。 そのため利用者が手元にAPI Keyを用意することなく安全に呼び出すことができています。 あとは愚直にひたすら実装していくだけです。あえて言うとすれば生成結果をServer Sent Eventsでそのままモーダルにストリーミングしていて、性質上複数回クリックするなどしてリクエストが重複する可能性が高いので丁寧にAbortControllerで重複を防いでることくらいでしょうか。 Gmailで利用できるメール返信文の自動生成を実装しているなど本家と衝突して危ういものもありますが、メールは職種によっては評判がよかったりするのでとりあえず短期の生産性向上を目指してあらゆるページにLLM連携を実装していっています。 とはいえChrome Extensionだと権限の問題で実現できる範囲に制約があり、現在は鋭意デスクトップアプリケーションを開発中です。 (プラットフォーマーとして、Kubernetesベースの内製PaaS・GitHub ActionsからKubernetes Manifest, ドキュメントまでを握るコードジェネレータを中心としたコマンドラインツール・Chrome Extensionとやってきたので次はデスクトップアプリケーションかなというところです) コマンドラインツールでのLLM活用 我々は keelctl と呼ばれるコマンドラインツールも開発しており、先に紹介したコードジェネレータもこの keelctl に実装された機能の一つです。 www.lifull.blog 実はコマンドラインツールでのLLM活用は以前に似た内容のエントリを書いていて、社内の認証基盤の認証フローをコマンドラインから踏んでAPIを利用するという大枠は変わっていません。 www.lifull.blog こちらも順調に機能拡充が進んでいて、Conventional Commits形式のコミットメッセージの自動生成の他にも色々なタスクをコマンドラインツールからLLMを活用することで実現しています。 中でも、CSVのカラムを入力として他のカラムを埋める keelctl llm fillcsv という機能が好評です。 System: You are an AI assistant. Given a question and its answer, your task is to generate alternative expressions (paraphrasing) for that question to provide more options. User: Paraphrasing provides a new perspective on the question and aids users in obtaining the answer. Please generate at least 10 paraphrasings. Paraphrasing MUST be joined semicolons. question: keelaiの利用方法 answer: keelaiを利用する際には対象のSlackチャンネルにkeelaiを招待してメンションするか、keelaiのSlack Appsに対してDirect Messageで話しかけてください paraphrasing: keelaiを利用するには;keelaiの使い方;keelaiのドキュメント;LLMを使いたい question: %s answer: %s paraphrasing: 例えば上記のようなプロンプトテンプレートを与えると、以下のCSVを行ごとに処理して paraphrasing カラムを question と answer から生成した想定される質問の別バリエーションで埋めることができます。 question,answer,paraphrasing LIFULLに興味がある場合の次のアクション,お気軽に[カジュアル面談](https://hrmos.co/pages/lifull/jobs/010-9998)をお申込みください, ... 利用イメージは以下です。 コマンドを実行するだけでLLMを使ったタスクを並行で一括実行することができます。 中身は汎用AI(仮)なので、社内情報へのアクセスはもちろんのこと検索や画像生成まであらゆるタスクをこなすことができます。 $ keelctl llm fillcsv input.csv question,answer paraphrasing --prompt-template /path/to/paraphrasing.tmpl --concurrency 10 先のプロンプトテンプレートは社内知識をEmbeddingに変換する時に検索精度向上のために利用しているもので、他にも社内のよくある質問集の多言語対応をする際にも似た要領で一括実行しています。 最近では膨大な業務データのLLMを利用したタグ付けにも使用されたりと、簡単に使える一括処理として広く使われるようになってきました。 Jupyter Notebookでの高度なLLM活用 一方で、CSVの行ごとの一括処理ではまとまった単位で処理をしたい時や複雑な前処理が必要な時に対応することができません。 KEELチームはプラットフォームの機械学習サポートとして JupyterHub を運用しており、そこでそういった高度なユースケースに対応しています。 JupyterHubは社内の認証基盤と統合されており、社員であればURLにアクセスするだけでJupyter Notebookを利用可能です。 そして認証基盤との統合により keelai のAPIをNotebook上から認証なしに実行することができ、すぐにプログラムからLLMを使い始めることができます。 余談ですが、Jupyter Notebookは比較的リッチなUIを実現できるため、このような簡易的な画像生成用のNotebookも提供しています。 これとは別にStable Diffusion WebUIのホスティングもしていて、利用者は用途に応じて画像生成AIを使い分けることが可能です。 このJupyter Notebookの表現力を活かし、Slack上の keelai でCode Interpreter相当の機能を実現するためのインタフェースとしても利用されます。 また、コマンドラインツールの実行者はソフトウェアエンジニアに限られてしまいますが、Jupyter Notebookであれば誰もが簡単に実行できます。 共有ストレージをNotebookにマウントして、作成したJupyter Notebookの配布機構も備えたことで、作成したLLMの高度なユースケースを配布するプラットフォームとしても機能するようになりました。 まとめ 今回はLIFULLでのLLMを利用したPlatform Engineeringを紹介しました。 LIFULLのプラットフォームを開発する我々KEELチームは、Kubernetesベースの内製PaaSであるKEELを開発・運用しながら、社内で汎用AI(仮)と呼ぶAutoGPT実装 keelai も開発しています。 そして、コードジェネレータを備えた keelctl 、プラットフォームのユーザ体験を補完するChrome Extensionである keelext といった複数のチャネルを活かして、LLMを利用したPlatform Engineeringを進めてきました。 Slack Automationsを利用した疑似的なLLMノーコードツールやJupyter Notebookの提供では、利用者が自律的にLLMを用いた業務改善を行い更にそれを配布する仕組みも作ることで、社内のLLM活用にレバレッジを効かせることにも成功しています。 今後は根幹を担う汎用AI(仮)を中心に機能を増やしつつ、KubernetesクラスタであるKEEL自身のAIOps(?)を強化し、あらゆる面からLLMを使ってLIFULLの生産性向上に貢献していきたいと考えています。 もし興味を持っていただけた場合は、是非カジュアル面談をさせてください! hrmos.co hrmos.co
エンジニアの小林と申します。 LIFULL HOME'S の横断領域の開発を担当しています。 私たちの開発しているLIFULL HOME'Sでは、A/Bテストの実施によって市場学習(≒PDCA)の回数を増やし、より良いプロダクトを作り上げることを目的として、日々多くのA/Bテストが実施されています。 しかしながら、いくつかの問題があります。 今回はLIFULL HOME'SにおけるA/Bテストの成熟度と課題、そして現在の取り組みをご紹介します。 A/Bテストの成熟度と課題 A/Bテスト成熟度モデル 課題 新しい取り組み 構成図 A/Bテスト一覧 A/Bテスト詳細(概要) A/Bテスト詳細(結果) 解決された課題 これからの展望 まとめ A/Bテストの成熟度と課題 A/Bテスト成熟度モデル Kohavi, R., Tang, D., & Xu, Y. (2020).によるA/Bテスト成熟度モデルに照らすとLIFULL HOME'SのA/BテストはWalkフェーズへ移行を目指している段階です。 Walkフェーズへ移行するにあたり課題となったのは A/Bテスト設計の標準化 結果の信頼性の確立 の2点です。 table { border-collapse: collapse; } th { border: solid 1px #666666; color: #000000; background-color: #ff9999; } td { border: solid 1px #666666; color: #000000; background-color: #ffffff; } フェーズ 概要 Crawl A/Bテストで利用するユーザー識別子の発行と結果を集計するための基盤が整っている。 Walk 標準的な指標の定義ができている。 ユーザーのサンプル比率のミスマッチ(SRM)が起きないように仕組み化されている。 A/Bテスト結果の信頼性が確立されている。 Run 評価基準が合意され、システマチックな意思決定するプロセスが確立されている。 Fly テスト集計~プロダクトへのロールアウトまで全てのプロセスが自動化されている。 参照: Kohavi, R., Tang, D., & Xu, Y. (2020). Experimentation Platform and Culture. In Trustworthy Online Controlled Experiments: A Practical Guide to A/B Testing (pp. 58-78). Cambridge: Cambridge University Press 課題 EDDの導入を済ませ、次の課題は更なる標準化と結果の信頼性の確立となりました。 ここで課題となるのが集計・分析の過程です。 A/Bテストのプロセスを整理すると以下の図のようになります。 A/Bテストの各プロセス 1. テストの設計 の標準化についてはEDD(Experimental Design Doc)の導入により一定の改善が行われています。 EDD導入については以下のnoteをご覧下さい。 note.com 2. テストの実装 、 3. テストの実施 については半年ほど前にA/Bテスト用社内packageが開発され、新規マイクロサービスにおいてはA/Bテストを低コストで実装できるようになりました。 www.lifull.blog 4. テストの計測 については主にBigQueryに接続したGoogle スプレッドシートにおいて行われています。 このGoogle スプレッドシートのテンプレートが部署(賃貸・分譲 etc...)によって異なり、BigQueryに対するクエリやベイズ計算のロジックもそれぞれに実装されています。 スプレッドシートで表示している値は汎用性を持たせられており、EDDで判断指標として指定したGoal MetricsやGuardrail MetricsなどのMetrics以外にも多くの指標を取得しています。 また、この知見を蓄積するドキュメントはテストごとにConfluence(アトラシアン社製企業向けWiki)にまとめられていますが、Confluenceは検索性が低くInstitutional memoryの蓄積(組織全体が過去の経験から学んだことを、どれだけ覚えているか)という観点では質が低い状態です。 新しい取り組み 前項で挙げた課題を解決するために A/Bテストをはじめとしたコントロール実験を正確に測定し、意思決定を明確化すること 結果・分析を記録し、施策の知見を未来に向けて蓄積すること を目指したコントロール実験実行・分析基盤としてexp-libraryというA/Bテスト管理基盤を作成しました。 構成図 exp-library構成図 メトリクス定義の複雑な入力を受け付けるために、Next.jsを採用しました。 Python製の集計用バッチを定期実行してBigQueryからユーザーの行動データを取得し、集計・ベイズ計算を実行してBigQueryに保存しています。 A/Bテスト一覧 テスト一覧 登録されたA/Bテストの一覧です。 検索と様々な絞り込みが可能です。 後述するテスト情報の詳細の内容も含めて文字列検索をするため、Confluenceで感じていた過去の施策の検索しにくさを解消します。 A/Bテスト詳細(概要) A/Bテスト詳細画面 登録されたA/Bテストの詳細情報です。 こちらで計測のオン・オフが可能です。実施期間中のみ自動で計測する仕様にもできましたが、過去の施策の結果も収集するために、手動で切り替えられるようにしています。 こちらから施策のドキュメントにアクセス可能です。 A/Bテスト詳細(結果) A/Bテスト詳細(結果)画面 計測結果を表示しています。 解決された課題 今回作成した集計バッチで全てのA/Bテストを集計することで、全社で集計フローが統一されました。 また、これまでは部署ごとのスプレッドシートの中にあったクエリや計算ロジックがPythonのコードとしてGitHubで管理されることで、統計に知見のあるエンジニア・データサイエンティストが内容を精査・改善するハードルが下がりました。 これによりテストの計測について、一定の標準化が成され、結果の信頼性を担保に向けて検証が容易になりました。 これからの展望 現在このexp-libraryは既存のスプレッドシートから移行するべくβテストと称して社内の一部の施策担当者からFBを受け、改善を行っています。 今後はエンジニア・企画の工数を削減し、A/Bテストの回数を増やすべく、以下の機能実装を行っていきます。 A/B テスト勝敗判定の自動化 A/B テストの開始・終了の自動化、A/B寄せの自動化 Feature Flag 多腕バンディットアルゴリズムによるコントロール実験機能 まとめ exp-libraryの命名の由来は以下のとおりです。 市場調査の数を指数関数的(exponentially)に増やし、あらゆる実験(experiments)を収集し、経験(experience)を蓄積し、公開する。 「公共図書館の本質的な機能は、資料を求めるあらゆる人々やグループに対し、効率的かつ無料で資料を提供するとともに、住民の資料要求を増大させるのが目的である」 中小都市における公共図書館の運営(1963) 会社の資産とも言える施策の試行錯誤を正確に記録した資料を残し、今後に活かしていくことを目的に基盤を作成しました。 LIFULL HOME'SのA/Bテストをより良い状態にしていくために今後も改善をしていきます。 ともに良いプロダクト作りをしてくれる仲間を募集しています。 hrmos.co hrmos.co