こんにちは! LIFULLエンジニアの吉永です。 普段はLIFULL HOME'SのtoC向けCRMチームにてエンジニアリングマネージャをやっています。 本日はチームでGitのコミットメッセージ書式を Conventional Commits に準拠するようにしてから得た知見を紹介したいと思います。 コミットメッセージに書式を導入することでどんなメリットがあるのか?導入前後でどんな変化があったのか?今後の展望についてご興味のある方に参考になれば幸いです。 アジェンダ Conventional Commitsとは? チームで導入するにあたってどんな工夫をしたか? 自身が率先して規約に準拠する 定期的に自身のコミットメッセージを振り返る時間を設けた 実際に運用してみて得た知見を仲間にも共有する時間を設けた 導入してみてどうか? リリースノートの内容を分かりやすく自動生成できるようになった コミットの粒度が個人でばらつきにくくなる レビューをしやすくなった feat系の同じ文脈のコミットはレビュー完了後はまとめた方がよいかも 今後の展望 コードリーディングがしやすくなる(と思っている) コミットtypeやscopeの内訳を集計できるようになる まとめ Conventional Commitsとは? 人間と機械どちらから見ても読みやすい形式のコミットメッセージ規約です。 公式サイトに分かりやすく概要がまとまっていますので、良かったらこちらも参照してください。 www.conventionalcommits.org 規約と言われると、少し躊躇される方もいるかもしれませんがそこまで複雑な規約ではありません。 よって、導入するにあたってのハードルはさほど高くなく、気楽に始められると思います。 基本形は下記の形になります。 <type>[optional scope]: <description> [optional body] [optional footer(s)] optionalの部分は必須ではないので、必要最低限の規約を満たす為には <type> と <description> を記載すれば良いです。 例えばAという新機能を追加したコミットがあるとします。 その時のコミットメッセージは下記のようになります。 feat: A機能を追加 また、上記機能をリリース後一定期間経過後にA機能のバグを修正した場合は下記のようになります。 fix: A機能で数値に変換できない文字列を入力した場合に例外が発生するバグを修正 こんな感じで、人間から見てもそのコミットでどんな変更がコードに加えられたのかが分かりやすい規約になっています。 公式サイト上で定義されている <type> はfixとfeatのみなのですが、私達のチームではこれらに加えてAngularの規約も取り入れて運用しています。 github.com チームで導入するにあたってどんな工夫をしたか? 自身が率先して規約に準拠する まずは導入しようというチームに提案した私自身がきちんと規約を理解し、自身が行うコミットコメントを規約に準拠したものにしました。 定期的に自身のコミットメッセージを振り返る時間を設けた GitHubの検索APIを利用して、過去1週間以内のチームメンバーそれぞれのコミットコメント一覧を週一で開催しているチームの定例時間内で規約に準拠したコメントになっていたかを確認するようにしました。 最低でも1週間に一度は振り返る機会を設けたことで、徐々に規約に従うことが当たり前になっていく効果があったと今は思います。 実際に運用してみて得た知見を仲間にも共有する時間を設けた 得た知見もチームの定例で共有する時間を設けました。 このコミット <type> はrefactorかfixかchoreか分類に少し迷った、 <scope> をどこで切るか少し迷った、など実際に利用し始めて気づく疑問も数多くあったので、様々な事例を共有することで、次回からの指針になったり徐々に効率的になっていくと思っています。 導入してみてどうか? リリースノートの内容を分かりやすく自動生成できるようになった 私達のチームではリリース時に セマンティックバージョニング 形式に準拠したタグをインクリメントして自動生成していました。 約2年前に自前で作成したGitHub Actionsで行っていましたが、メジャー、マイナー、パッチをインクリメントする際のアルゴリズムがいまいちで、全ての場面においてチームメンバー全員が納得できるバージョニングになっていたかと言われると少し怪しい出来でした。 そんな中、チームのメンバーが下記のGitHub Actionsを探してきてくれ、Conventional Commitsに準拠していれば、タグのインクリメントとリリースノートも自動で生成してくれるという、既存のものよりも優れたものでした。 github.com 導入に当たってのハードルも低かったので早速採用して、運用を開始しました。 こちらのGitHub Actionsは個人的にも非常に気に入っており、Conventional Commitsに準拠することで得られる具体的なメリットとして、チーム内外にも共有しました。 コミットの粒度が個人でばらつきにくくなった Conventional Commitsを採用し、オプションである <scope> もなるべく含めようというローカルルールを採用したところ、コミットの粒度が個人でばらつきにくくなりました。 私達のチームのプロダクトではクリーンアーキテクチャを採用しているのですが、 <scope> にクリーンアーキテクチャの各レイヤー名( feat(repository):hoge とか feat(domain):fuga みたいな感じです)を入れて運用してみたところ、おのずと一つのコミットでの変更箇所が狭まったからだと思います。 また、レビューでもらった指摘を修正した後も、今まではついつい下記のようなコミットメッセージにしてしまっていました。 レビュー指摘修正 このコミット履歴は後でコードを読み返す時にはノイズにしかならないので、本来この変更を一緒に含めるべきコミットと統合すべきという意識にConventional Commits採用後は変わりました。 具体的には下記のようなコミット履歴になった場合は、 1 feat: A機能を追加 2 test: A機能のテストコードを追加 3 fix: A機能実装部分に対するレビュー指摘修正 下記のように1のコミットに3のコミットを統合するべきですね。 1 feat: A機能を追加 2 test: A機能のテストコードを追加 このようにコミット履歴をマージ前であれば、本来あるべき履歴へマージ前にリベースするということが自然と浸透しました。 レビューをしやすくなった コミットの粒度がばらつきにくくなったことで、レビューもコミット単位で行いやすくなり、この変更は何で行ったのか?をレビューアが理解しやすくなったと思います。 結果として、レビューがしやすくなったと感じる人が多く、今後も継続していこうと思える一つの要因になっています。 feat系の同じ文脈のコミットはレビュー完了後はまとめた方がよいかも レビューしやすくなったと感じる件と少し関りがあるのですが、私達のチームでは先述したようにクリーンアーキテクチャのレイヤーに沿った <scope> でコミットを分けて運用してました。 先日、Conventional Commitsを採用してから初めて大規模な実装を行い、上記の運用でコミット粒度を分けていたのでレビューは非常にやりやすかったです。 ただし、リリースノートにのるfeat一覧としてみると、正直このリリースで何の機能をリリースしたのか?が細分化されすぎているため、初見では分かりづらかったです。 コミット履歴としては下記のような感じになっていました。 feat(domain): A機能用のエンティティと変換処理を実装 test(domain): A機能用のエンティティの変換処理のテストを実装 feat(repository): A機能用のhogehogeデータを外部から取得する処理を実装 test(repository): A機能用のhogehogeデータを外部から取得する処理のテストを実装 refactor(repository): A機能用に追加した処理と既存処理とで共通化できる個所をまとめる feat(usecase): A機能用のインタラクターを実装 test(usecase): A機能用のインタラクターのテストを実装 feat(controller): A機能用のコントローラーを実装 test(controller): A機能用のコントローラーのテストを実装 コミット上ではコントローラー、リポジトリ、ユースケース、ドメインそれぞれ分けた方がレビューはしやすかったのですが、リリース後しばらくたってからコードリーディングをするときのコミット履歴としては下記が良いのではないか?とチームのメンバーと話していて、意見が出ました。 feat(api): A機能を追加 refactor(repository): A機能用に追加した処理と既存処理とで共通化できる個所をまとめる 結論、feat系はレビュー完了後は同じ機能に関するものであれば一つにまとめ、それら以外のものはそのまま個別のコミットのままで良いかもとなりました。 時間が経過して、後からコードリーディングする際のコミットコメントしても feat(api): A機能を追加 の方が、「このソースはA機能追加時に改修されたものなのか」と理解しやすく、その際にクリーンアーキテクチャのレイヤーの情報はいらないだろうからという理由です。 ※この辺りは、私達の出した結論が必ずしも正解とは限らないと思いますので、ケースバイケースかなと思いますが、一つの知見として参考になれば幸いです。 今後の展望 コードリーディングがしやすくなる(と思っている) Conventional Commitsに準拠することで、コミット粒度や形式がある程度まとまっていることから、時間が経過した後で該当個所のコードは何の作業でどんな理由が合って改修されたのか?をコミット履歴から情報を得やすくなり、結果としてコードリーディングがしやすくなると思われます。 また、自身だけでなく第三者にとっても同様の効果はあると思うので、効果を実感できる時期が来ることを今から楽しみにしています。 コミットtypeやscopeの内訳を集計できるようになる 既に少しだけ運用を開始したのですが、ある期間にチームで行った開発の割合は新機能開発系とリファクタリング系どんなバランスだったか?そのバランスは適切か?などをConventional Commitsに準拠していくことで定量的に集計することができるようになります。 今まではプロジェクト管理ツールへ日々の工数入力で行っていた集計をGitのコミット履歴の観点からも分析できるようになり、チームとしてより正確なステータスを把握しやすくなることを期待しています。 下のグラフは私達のチームの2023/06~2023/07のConventional Commitsの <type> を集計した結果です。 現時点では、この割合が健全な状態なのか?はまだあまり良く分かっていませんが、いずれデータが蓄積されて期間比較できるようになっていくことで、より活用の幅が広がっていくと思います。 まとめ Conventional Commitsについて、採用後しばらく運用してみてチームで得た知見について紹介しました。 チームで導入するには少しハードルが高いなど、それぞれ事情はバラバラかと思いますが、そのような状況の場合に私からお勧めするのは、まずは自分ひとりだけでもいいから準拠してみることだと思います。 私自身、実際に自分で採用してみて得たことをメンバーにも共有したり、Qiitaに個人として得た気づきをアウトプットしたりして理解を深めていったことで、良さに気づけた気がします。 qiita.com まずはスモールスタートでやってみることで得られることもあると思いますので、是非お試しください! 最後に、LIFULLでは共に成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
アクセシビリティ推進グループの中島です。 過去同グループの発信した記事の中で、弊社がJavaScriptライブラリとしてStimulusを採用していると何度か紹介させていただきました。 www.lifull.blog 今回はその中で、どんな粒度で、どんな機能のStimulusコントローラを書いているのか少しばかり紹介しようと思います。 (全て書くととても長くなってしまうのでvol.1としてますが、vol.2以降を書くかどうかは今の所わかりません。) button_controller.js 適用例 参考 disclosure_controller.js 適用例 参考 inlay_controller.js 適用例 参考 anchor_sinon_controller.js 適用例 tabs_controller.js 適用例 参考 最後に button_controller.js まずは最頻出のコントローラです。 このコントローラは37signalsから拝借したコントローラの一つです。 button_controller.jsの実装を見る export default class extends Controller { keyboardClick( event ) { if ( [ 'Enter' , ' ' ] .includes( event .key)) { event .preventDefault(); event .stopPropagation(); this .element.click(); } } } このコントローラは要素にボタンとしての機能を簡単に提供するために使っているものです。 ボタンは通常、スペースキーやエンターキーといったキーボード操作でも操作可能であるべきです。 button要素で実装していればこのキーボード操作は標準で提供されるので、こちらがコードを書く必要はないのですが長く運用されたサイトでは往々にしてdivやaタグで実装されたボタンが目立ちます。 そういったものを要素自体を差し替えずに正しくボタンのように振る舞わせるのに役立ちます。 適用例 <!-- before --> < div > click me </ div > <!-- after --> < div # フォーカス可能に tabindex = "0" # 支援技術にボタンであることを教える role = "button" data -controller= "button" # スペースキーとエンターキーをクリックイベントとして処理する data - action = "keydown->button#keyboardClick" > click me </ div > 参考 www.w3.org disclosure_controller.js 次によく使うコントローラはディスクロージャー(開閉UI)を実現するコントローラです。 disclosure_controller.jsの実装を見る export default class extends Controller { static classes = [ 'collapsed' ] ; toggle(evt) { evt?.preventDefault(); if ( this .isExpanded) { this .hide(); return ; } this .show(); } show() { this .control.classList.remove(... this .hiddenClasses); this .element.ariaExpanded = 'true' ; } hide() { this .control.classList.add(... this .hiddenClasses); this .element.ariaExpanded = 'false' ; } get hiddenClasses() { return this .hasCollapsedClass ? this .collapsedClasses : [ '!hidden' ] ; } get isExpanded() { return this .element.ariaExpanded === 'true' ; } get control() { return document .getElementById( this .element.getAttribute( 'aria-controls' ).trim() ); } } 最近ではdetails要素で実現可能なディスクロージャーですが、例によって過去に作られたディスクロージャーはdiv等で作られ、ディスクロージャーとしての機能要件を満たしてないものが多くあります。 ディスクロージャーはトリガーである要素がフォーカス可能で、ボタンとして振る舞い、視覚だけでなく支援技術等でも開閉状態を把握できるように作られるべきです。 このコントローラを使うとそういった部分を担保したディスクロージャーを簡単に実装することができます。 適用例 <!-- before --> < div > < p > ほげほげについて </ p > < div class = "!hidden" > ほげほげはふがふがのことで 一般的にぴよぴよとも 呼ばれています。 </ div > </ div > <!-- after --> < div > < p # フォーカス可能に tabindex = "0" # 支援技術にボタンであることを教える role = "button" # 関連要素のidを指定することで支援技術でジャンプ機能等を提供する aria-controls = "content" # 開閉情報を支援技術に公開する aria-expanded = "false" data -controller= "button disclosure" data - action = "keydown->button#keyboardClick click->disclosure#toggle" > ほげほげについて </ p > < div id = "content" class = "!hidden" > ほげほげはふがふがのことで一般的にぴよぴよとも呼ばれています。 </ div > </ div > 参考 www.w3.org inlay_controller.js 主にウェブで見かけるUIに「もっと見るボタン」を押したら、ボタンは消滅しつつ、隠れた要素が表示されるというものがあります。 このパターンはARIA Authoring Practices Guide等でベストプラクティスが紹介されているわけではないのですが、我々はそういった振る舞いをinlayとよび、対応するコントローラを用意しています。 inlay_controller.jsの実装を見る import { tabbable } from 'tabbable' ; export default class extends Controller { show() { this .nextContent.classList.remove( '!u-hidden' ); this .element.classList.add( '!u-hidden' ); this .firstTabbableItem.focus( { preventScroll: true } ); } get nextContent() { let control = this .element.getAttribute( 'aria-controls' ); return document .getElementById(control); } get firstTabbableItem() { return tabbable( this .nextContent) [ 0 ] || this .nextContent; } } このパターンは押したボタンそのものが消滅することで、フォーカスが失われる(bodyに戻ってしまう)問題に対処するため、表示されたコンテンツ、あるいはそのコンテンツ内の最初のフォーカス可能な要素にフォーカスを移すことが重要と考えています。 そういったことが考慮されてない古いコードをこのコントローラに差し替えることで簡単にキーボード操作の要件を満たすことができるようになります。 適用例 <!-- before --> < ul > < li >< a href = "..." > a </ a ></ li > < li >< a href = "..." > b </ a ></ li > < li >< a href = "..." > c </ a ></ li > </ ul > < div > もっと見る </ div > < ul class = "!hidden" > < li >< a href = "..." > d </ a ></ li > < li >< a href = "..." > e </ a ></ li > </ ul > <!-- after --> < ul > < li >< a href = "..." > a </ a ></ li > < li >< a href = "..." > b </ a ></ li > < li >< a href = "..." > c </ a ></ li > </ ul > < div # フォーカス可能に tabindex = "0" # 支援技術にボタンであることを教える role = "button" # 開閉可能なUIであることを支援技術に公開する aria-expanded = "false" aria-controls = "more-content" data -controller= "button inlay" data - action = "keydown->button#keyboardClick click->inlay#show" > もっと見る </ div > < ul class = "!hidden" id = "more-content" > < li >< a href = "..." > d </ a ></ li > < li >< a href = "..." > e </ a ></ li > </ ul > 参考 accessible-usable.net anchor_sinon_controller.js 主にサイトアナリティクスの文脈で流入元を特定する目的でリンクにパラメータを付与することがよくあります。 ただ、googlebot等のクロールバジェットの消費を抑制する観点で、「パラメータ付与はJavaScriptでクリック時に行ってください」ということが要件に盛り込まれることが稀にあります。 目的は違えどGoogleAnalyticsのLinker機能などが類似の動きをしますね。 そういった時のためにURLの差し替えをさっさと行えるコントローラを用意しています。 anchor-sinon_controller.jsの実装を見る export default class extends Controller { static values = { url: String } ; trick() { this .element.setAttribute( 'href' , this .urlValue); } } 私自身SEOの専門家ではないのでどの程度効果があるのかは詳しく分かってませんが、汎用的なURL差し替えを実現できるようになっています。 適用例 <!-- before --> < a href = "/path/to/page/?from=xxxx" > some page </ a > <!-- after --> < a href = "/path/to/page/" data -controller= "anchor-sinon" data - action = "click->anchor-sinon#trick" data -anchor-sinon- url - value = "/path/to/page/?from=xxxx" > some page </ a > tabs_controller.js 複数のコンテンツをタブ付きインタフェースとして表現するケースがたまにあります。(特にPCサイトに多いような気がします。) 弊社でも家賃相場情報を間取りタイプごとにグルーピングしてタブで切り替えて閲覧できるといった機能があったりしますが、これはそういったUIパターンに適合するコントローラです。 tabs_controller.jsの実装を見る export default class extends Controller { static targets = [ 'tab' , 'tabpanel' ] ; static values = { index: { default : 0, type: Number } } ; select(evt) { let tab = evt.currentTarget; let index = this .tabTargets.indexOf(tab); this .indexValue = index; } selectAt(index) { this .indexValue = index; } next(evt) { evt.preventDefault(); this .indexValue = this .indexValue === this .lastIndex ? this .indexValue : this .indexValue + 1; this .tabTargets [this .indexValue ] ?.focus( { preventScroll: true } ); } prev(evt) { evt.preventDefault(); this .indexValue = this .indexValue === 0 ? 0 : this .indexValue - 1; this .tabTargets [this .indexValue ] ?.focus( { preventScroll: true } ); } first(evt) { evt.preventDefault(); this .indexValue = 0; this .tabTargets [this .indexValue ] ?.focus( { preventScroll: true } ); } last(evt) { evt.preventDefault(); this .indexValue = this .lastIndex; this .tabTargets [this .indexValue ] ?.focus( { preventScroll: true } ); } get lastIndex() { return this .tabTargets.length - 1; } indexValueChanged(current, prev) { let tabs = this .tabTargets; let tabpanels = this .tabpanelTargets; tabs [ prev ] ?.setAttribute( 'aria-selected' , 'false' ); tabs [ prev ] ?.setAttribute( 'tabindex' , '-1' ); tabpanels [ prev ] ?.classList.add( '!hidden' ); tabs [ current ] ?.setAttribute( 'aria-selected' , 'true' ); tabs [ current ] ?.setAttribute( 'tabindex' , '0' ); tabpanels [ current ] ?.classList.remove( '!hidden' ); } } タブ付きインタフェースはタブシーケンス中に一つだけタブストップを持つといった要件や、左右キーでタブ切り替えができる、 Home/Endキーでタブの先頭、末尾切り替えができるといった要件が存在します。 そういったタブナビゲーションをこのコントローラを利用すれば簡単に実現することができます。 適用例 <!-- before --> < h2 > 〜区の家賃相場 </ h2 > < div > < ul > < li > 1人暮らし向け </ li > < li > 2人暮らし向け </ li > < li > ファミリー向け </ li > </ ul > < div > 1人暮らし向け物件の家賃相場 ...円 </ div > < div class = "!hidden" > 2人暮らし向け物件の家賃相場 ...円 </ div > < div class = "!hidden" > ファミリー向け物件の家賃相場 ...円 </ div > </ div > <!-- after --> < h2 id = "tab-label" > 〜区の家賃相場 </ h2 > < div data -controller= "tabs" > < ul role = "tablist" aria-labelledby = "tab-label" > < li id = "tab1" # 初期選択の要素だけタブシーケンスにタブストップを設定 tabindex = "0" role = "tab" # タブが選択中であることを支援技術に公開 aria-selected = "true" data -tabs- target = "tab" data - action = " keydown.left->tabs#prev keydown.right->tabs#next keydown.home->tabs#first keydown.end->tabs#last click->tabs#select" > 1人暮らし向け </ li > < li id = "tab2" tabindex = "-1" role = "tab" aria-selected = "false" data -tabs- target = "tab" data - action = " keydown.left->tabs#prev keydown.right->tabs#next keydown.home->tabs#first keydown.end->tabs#last click->tabs#select" > 2人暮らし向け </ li > < li id = "tab3" tabindex = "-1" role = "tab" aria-selected = "false" data -tabs- target = "tab" data - action = " keydown.left->tabs#prev keydown.right->tabs#next keydown.home->tabs#first keydown.end->tabs#last click->tabs#select" > ファミリー向け </ li > </ ul > < div role = "tabpanel" aria-labelledby = "tab1" data -tabs- target = "tabpanel" > 1人暮らし向け物件の家賃相場 ...円 </ div > < div role = "tabpanel" class = "!hidden" aria-labelledby = "tab2" data -tabs- target = "tabpanel" > 2人暮らし向け物件の家賃相場 ...円 </ div > < div role = "tabpanel" class = "!hidden" aria-labelledby = "tab3" data -tabs- target = "tabpanel" > ファミリー向け物件の家賃相場 ...円 </ div > </ div > 参考 www.w3.org 最後に 今回は記事の物量の関係上、よく使う5つのコントローラに絞って紹介しました。 他にもダイアログ関連のコントローラ、コンテンツの非同期読み込みコントローラ、コンボボックスを実現するコントローラ等、さまざまなコントローラがあるのでvol2があればそこで紹介しようと思います。 最後までお読みいただきありがとうございました。LIFULL では共に働く仲間を募集しています! hrmos.co hrmos.co
こんにちは。プロダクトエンジニアリング部の武井です。 普段はLIFULL HOME'Sの賃貸領域の開発をしています。 現在、LIFULL HOME'Sの賃貸領域ではシステムの基盤刷新を行っています。 詳細については 以前の記事 をご覧ください。 今回はこの基盤刷新に伴い、新たなABテスト実施システムを構築したので、その概要を紹介したいと思います。 LIFULL HOME'S におけるABテスト LIFULL HOME'Sでは、ユーザーにとってより使いやすいポータルサイトを目指し日々UI/UXの改善を行っています。 その効果測定の手段としてABテストを取り入れており、常時いくつものABテストを実施しています。 旧基盤でのABテストのしくみはシステム立ち上げ当初に実装されて以来、現在まで10年以上に渡って使われ続けています。 今回の基盤刷新に伴い、このABテストを実施するしくみを新基盤上に構築し直す必要がありました。 ABテストのシステムは旧基盤と同様に今後10年以上使われ続ける可能性があり、非常に影響度の大きいシステムになることが予想されます。 そのため今後の開発効率を左右する重要な開発プラットフォームを作るという意識で開発に臨みました。 インフラ層での振り分け システムの構築にあたって、A/Bのユーザーをどのように振り分けるかをまず考えました。 最初に出た案は、KubernetesにおけるIstioの加重ルーティングを利用したインフラ層での振り分けです。 IstioはKubernetes上のトラフィック管理などを担うservice meshです。LIFULL HOME'Sの多くのプロダクトはKubernetesの共通基盤上で動作しており、service meshとしてIstioを利用しています。 このIstioのVirtualServiceという機能を使うことでユーザーの振り分けができます。 たとえば、以下のようにmanifestを記述すると v2 のバージョンに 25% 、 v1 のバージョンに 75% のユーザーをそれぞれ流すことができます。 apiVersion : networking.istio.io/v1alpha3 kind : VirtualService metadata : name : reviews-route spec : hosts : - reviews.prod.svc.cluster.local http : - route : - destination : host : reviews.prod.svc.cluster.local subset : v2 weight : 25 - destination : host : reviews.prod.svc.cluster.local subset : v1 weight : 75 Istio VirtualService しかしこの方法では、複数のテストを同時に並行して実施する際、運用上の問題が予想されました。 たとえばテスト1とテスト2を同時並行すると、A/Bそれぞれの掛け合わせで以下の4種類のバージョンのPodが必要になります。 テスト1A * テスト2A テスト1A * テスト2B テスト1B * テスト2A テスト1B * テスト2B このようにテストの同時実施数を増やしていくと、O(2 N )で用意するべきPodの種類が増えていき、管理が非常に困難になることが予想されます。 さらに一つのテストについて振り分けのパターンがA/B/C...と3パターン以上になることもあり、こうなるとより複雑性が増します。 このような運用上の懸念から、Istioを用いたインフラ層での振り分けは断念しました。 アプリケーション層での振り分け 他にも複数の案を検討した結果、インフラ層での振り分けは行わず同一のバージョンのPodでリクエストを受けた後、アプリケーションの中でA/Bのユーザーを振り分けコードレベルで処理を分岐させる案に落ち着きました。 詳細は割愛しますが、コードのイメージは以下のようになります。 システム側で設定ファイルとユーザー情報の突き合わせを行い、A/Bテストの情報が集約された ab というオブジェクトを生成します。このオブジェクトが持つ isB() のようなメソッドを用いてコード上でパターン分岐を行う形になります。 if ( ab. get( TEST_1_ID ) .isB ()) { // テスト1でのBパターンの処理 ... return } // テスト1でのAパターンの処理 ... この場合、アプリケーションのバージョン自体は一つで済みますが、if文で分岐を行う必要があります。複数のテストを同時並行するとより分岐条件が増え、コードの複雑性が高くなりますが、この複雑性を現時点では許容することにしました。 ABテストを実施する以上、必ずどこかでユーザーを振り分けるための複雑性を引き受ける必要があります。この複雑性をインフラ層という離れた場所ではなく、アプリケーションのコードという我々が普段開発していて変更しやすい場所に持ってきたことは暫定的に良い選択だったと感じています。 使いやすいインタフェースの追求 新システムを構築する上で、このシステムを利用する際の開発者体験や使いやすさを意識してインタフェースをデザインしました。 以下は開発したシステムにおける設定ファイルの一例です。 このように設定を定義すると、アクセスしてきたユーザーを自動的に振り分けるシステムになっています。 タイトルや資料へのリンクなども含めて宣言的に定義することで一目してテスト内容を把握できるようにしています。 export const abTestConfig: AbTestConfig = { [ AB_TEST_IDS.sampleProject ] : { title: '【賃貸事業部】サンプルテスト' , specUrl: [ 'https://jira.jp/wiki/XXXXX' , 'https://docs.google.com/spreadsheets/YYYYY' , ] , startDatetime: '2023-08-01 11:00:00' , endDatetime: '2023-08-14 13:30:00' , classification: [ { patternValue: 'a' , weight: 50 , measurementValue: 'sample_test_a' , } , { patternValue: 'b' , weight: 50 , measurementValue: 'sample_test_b' , } , ] , beforePattern: 'a' , afterPattern: 'b' , } , } ; この設定はTypeScriptのObjectとして定義し、型を当てるようにしています。 また、この設定ファイルに対するlinterを実装し、細かい設定内容もチェックしています。 これらのチェック機構によって、PullRequest作成時点で設定漏れや間違いに気付けるようになっており、誤った設定でデプロイしてしまうことを防げます。 このほかにも、利用開始方法や注意点などを記載した詳細なドキュメントを作成したり、テスト実施状況を可視化する簡単なダッシュボードを作成したりしました。 開発工数の削減 このシステムを構築するにあたって、旧基盤の単純な模倣ではなく少しでも開発工数を削減することも意識しました。 なぜなら今回構築するシステムは今後長い間使われる可能性があり、わずかな違いでもレバレッジが効き、結果的に大きな効果をもたらすためです。 旧基盤でのABテスト実施システムを分析したところ、振り分け部分と分析部分が連動していないことが改善点として挙がりました。振り分け部分とはユーザーをA/Bのように振り分ける部分、分析部分は振り分けた後のユーザーがコンバージョンに至ったかなどの分析用メトリクスを送信する部分を指します。旧基盤ではこれらが連動しておらず、テストを実施するたびにメトリクスを送信する処理を書く必要がありました。 そこで新システムでは設定ファイルに分析用の項目 measurementValue を設け、これを読み取って自動的にメトリクスを送信する設計としました。 export const abTestConfig: AbTestConfig = { [ AB_TEST_IDS.sampleProject ] : { title: '【賃貸事業部】サンプルテスト' , ... classification: [ { patternValue: 'a' , weight: 50 , measurementValue: 'sample_test_a' , // この値を自動的に送信する } , { patternValue: 'b' , weight: 50 , measurementValue: 'sample_test_b' , // Bパターンのときはこちら } , ] , ... } , } ; この改善によって、実装や確認の工数削減、実装漏れの防止などにつながります。 まとめ 普段はユーザーが直接触る機能の開発が中心となるため、今回のように社内の開発者に向けたシステムの開発は新鮮で貴重な経験ができたと感じています。最近ではこのABテストの実運用が始まっており、前よりも使いやすい!という声をいただいています。 このように、LIFULL ではユーザーや社内の開発者など多様なステークホルダーの体験を考えたものづくりを行い、ともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは、AI戦略室の清田です。 2023年3月に岐阜で開催された DEIM 2023 に続き、6月に熊本で開催された人工知能学会全国大会(JSAI 2023)に参加いたしました。 www.ai-gakkai.or.jp 今年は、恒例の「不動産とAI」をテーマとした企画セッションにも関わりましたので、その内容も合わせて報告します。 生成AIブームがAI研究コミュニティにもたらした影響 今回のJSAI 2023は、過去最高となる3,566名もの参加者があり、大盛況でした。 2022年に相次いで登場した生成AI技術を用いたサービス(Stable Diffusionなどの絵画生成、ChatGPTなどの自然言語生成)の影響で、AI研究が改めて大きな注目を集めていることを感じます。 ディープラーニングの登場などを受けて10年前ほどから始まった 第3次AIブーム は、ある程度の落ち着きを見せていましたが、「冬の時代」を迎える前に、第4次ブームが始まったのかもしれません。 生成AI技術は、大きな期待を集めている一方、著作権の問題、情報の信頼性の問題など、さまざまなリスクが指摘されてもいます。 私がオブザーバーとして関わっている 人工知能学会 倫理委員会 でも、マスメディアからの取材依頼などが多数入っている状況を受けて議論を行い、「人工知能学会としての大規模生成モデルに対してのメッセージ」を公表しました。 www.ai-gakkai.or.jp 本大会では、「日本は生成AIを起爆剤にできるのか」をテーマとした緊急企画セッション、「アートにおいても敗北しつつある人間 〜人の美意識もAIにハックされるのか?〜」と題した倫理委員会主催セッションなどが実施され、多くの参加者を集めていました。 その様子は、NHKなどのメディアでも報道され、生成AIへの社会からの関心の高さを実感しました。 www3.nhk.or.jp 生成AI技術をめぐっては、大規模言語モデルなどの技術面だけでなく、倫理や法律、収益の配分、人の創造性など、さまざまな課題が入り交じっていて、正しい理解に基づいた議論がなかなか成立しづらいように思います。 正しい理解に基づいた生産的な議論を成り立たせる上で、学会というコミュニティの役割に、大きな期待が寄せられています。 こうした役割を果たす上で、生成AIの開発に関わる各企業がより主体的に関わることが求められているように感じます。 企画セッション「少子高齢化と「住まい」産業のDXを考える」 LIFULLでは、名古屋で開催されたJSAI 2017から、継続的に「不動産とAI」をテーマとしたセッションを、同じ関心を持つ大学や企業の方々と共同で開催しています。 今回は、「少子高齢化と「住まい」産業のDXを考える」と題した企画セッションを開催しました。 少⼦⾼齢化の進展に伴う「住まい」の課題に取り組まれている方々をお招きし、さまざまな事例を共有いただきました。 不動産、AIにかかわる方々だけでなく、これからの時代に求められている新たな「住まい」の価値を追求されている方や、人々の行動データをさまざまな社会課題の解決に活用する取り組みをされている方も交えた議論を行うことで、「不動産とAI」の研究領域をさらに広げることを目指しました。 sites.google.com LIDAR測量、簡易BIMによるリノベ・維持管理へのデータ活用 スターツ社の清水哲志様・城戸祐一様からは、既存建築物のリノベーションにかかる膨大な手間を、スマートフォンに搭載されたLiDAR(ライダー)機能を活用して作成された簡易BIMモデルを用いて削減する取り組みなどを発表いただきました。 (国土交通省の「令和4年度住宅生産技術イノベーション促進事業」として、LIFULLも開発に協力しています) xtech.nikkei.com リノベーション事業の最前線の話題 福岡を中心に築古ビルのリノベーションを多数手がけられているスペースRデザイン代表の吉原勝己様からは、「共感価値」によって、築数十年の築古ビルの資産価値を向上するという興味深い事例を発表いただきました。 www.reizensou.com www.space-r.net これらの事例は、本セッションに参加されていたAI研究者の方々からも驚きをもって受け止められました。 地⽅⾃治体における⾼齢社会デザインへのビッグデータ活⽤ソリューション事例 ヤフー社でビッグデータを活用したデータソリューション事業を担当されている大屋誠様からは、 DS.INSIGHT を用いた分析事例をご発表いただきました。 パネルディスカッション 皆さまからのご発表を受けて、以下のようなテーマで多岐にわたる議論を行いました。 既存ストックの価値を高めるためにAI技術が果たせる役割 2025年問題にどのように向き合うべきか 九州をフィールドとした協働の可能性 今回の議論は非常に刺激的で、お互いに接点の少なかった領域の人々どうしが対話を重ねることが、「住まい」産業の形を大きく変革するきっかけになるのではという大きな可能性を感じました。 来年に浜松で開催予定のJSAI 2024でも、引き続き「不動産とAI」をテーマとしたセッションを企画予定です。 多くの方々のご参加をお待ちしております! おわりに LIFULLでは、生成AI技術を活用したプロダクトの開発を加速するため、専門部署として ジェネレーティブAIプロダクト開発室を2023年5月に設置しました 。 www.lifull.blog AI戦略室も引き続き研究開発に取り組んでおり、今後はジェネレーティブAIプロダクト開発室とともに連携して生成AIの活用に取り組んでいく予定です。 ChatGPTを活用した「 AIホームズくん LINE版 」、ChatGPTプラグインなど、国内不動産ポータルとして初めてとなるサービスを続々とリリースしています。 lifull.com lifull.com 生成AI技術は強力な技術であるがために、ユーザーにとって多くの利便性をもたらす一方、使い方によってはユーザーの利益を損ねるリスクもあります。 AI戦略室では、『創造と革進で喜びを届ける』というチームビジョンを掲げ、AI技術シーズの創出と活用を通じて、多様な社会課題や事業課題の解決に挑戦しています。 今回のJSAI 2023への参加を通じて得られた生成AI技術をめぐるさまざまな課題を踏まえつつ、社会課題や事業課題の解決につながる活用のあり方を考えてまいりたいと思います。 AI戦略室では、共に成長しながら働く仲間を募集しています。ご興味をお持ちの方のご応募をお待ちしております! hrmos.co
KEELチーム の相原です。 今回は流行に乗ってLLM(Large Language Models)の話です。 とは言うもののLLMは単なる流行ではなく新たなパラダイムと言っていいでしょう。 解けるタスクの幅は未だ底が知れず、機械学習とは求められる能力も多少異なることからソフトウェアエンジニアである私の周りでも大きな変化が起きていると感じます。 LIFULLでもこの変化をコーポレートメッセージである「あらゆるLIFEを、FULLに。」の実現に繋げるべくジェネレーティブAIプロダクト開発室が新設され、 一発目としてLIFULL HOME'SのChatGPT Pluginをリリース しました。 さて、我々KEELチームはKubernetesベースの内製PaaSであるKEELを開発・運用するチームです。 www.lifull.blog 我々にはプラットフォームというレバレッジの効くソフトウェアを通して「あらゆるLIFEを、FULLに。」の実現にスケーラブルに貢献する責任があります。 これまでKEELでは コードジェネレータによるPaaS体験 を軸に、可観測性やセキュリティ, デリバリーパイプライン, MLOpsから各種データストア・認証基盤に加えてアプリケーションの参考実装の提供に至るまで必要なことはすべてやってきました。 LLMという新たなパラダイムでも同様にプラットフォーマーとしてやれることがあるはずです。 Platform Engineeringからのアプローチということで、社内でのLLM活用を促進するためにこれまでやってきたことを紹介します。 ベクトルデータベースの提供 Redis Qdrant Embeddings APIのキャッシュプロキシの提供 ChatGPT Retrieval Pluginの構築 コマンドラインツールでのLLM活用 Conventional Commits形式のコミットメッセージの自動生成 今後の展望 ベクトルデータベースの提供 ベクトルデータベースはLLMの文脈では長期記憶を実装するために利用されるデータストアで、個人的にこれから最も熱い領域の一つだと思っています。 LLMはモデルによって扱えるトークン数に限界があり、単体ではこのトークン数を超えて処理することはできません。 つまり、例えばChatGPTで長期のやり取りに応じた返答をさせたり、LLMに対して未知の大量の前提知識を与えてそれに基づいた回答をさせられないということです。 これを解決するために用いられる手法がベクトル表現を用いたSemantic searchです。 Semantic searchは情報検索の分野で知られる手法で、キーワードで検索するKeyword-based searchに対してベクトルによって表現された"意味"で検索します。 LLMの長期記憶は、あらかじめ長期に及んだやり取りや前提知識をベクトル表現に変換してベクトルデータベースに格納しておき、同様に変換したクエリからSemantic searchでそれらを取り出すことによって実現されます。 繰り返しLLMを呼ぶことで複雑なタスクを解く AutoGPT にも長期記憶が必要でありこの手法が用いられていました。 モデルに対する知識の追加はFine-tuningでも実現可能なはずですが、私の理解では過去の学習を忘却してしまうCatastrophic Forgettingなどが問題で期待する性能が出づらいという認識です。 そのため、LLMに複雑なタスクを解かせるための長期記憶を実装するにはベクトルデータベースは不可欠であり、プラットフォームとして提供することを決めました。 Redis これまで内製PaaSであるKEELではいくつかのデータストアをコードジェネレータ経由で利用できるようにしてきていて、Redisもその中の一つです。 www.lifull.blog Redisには RediSearch という全文検索に対応するモジュールがあり、これはSemantic searchにも対応しています。 そして、このRediSearchをはじめとした高品質なモジュールをバンドルした Redis Stack というパッケージがあったため、素直にこれに差し替えるだけで既存のRedisクラスタをSemantic searchに対応できそうだということでまずはRedisから始めました。 redis.io 提供していたRedisクラスタはRedis SentinelによるクラスタリングとHAProxyによるLeaderのService Discoveryによって実現されていて、書き込みがスケールしないことから主にキャッシュなどのRead Heavyなワークロード向けに提供しています。 LLMの長期記憶に関しても読み込みクエリが支配的であると考えられたためこれをRedis Stackに差し替えるだけで問題ないと判断しました。 しかし、ご存じの通りRedisは高速であることのトレードオフとしてすべてのデータをメモリに載せる設計です。 AutoGPTなどから利用されるデータの生存期間が明確な長期記憶としては問題ありませんが、ドメイン知識を記憶させて自社独自の回答をさせるためには膨大なメモリが必要となってしまいます。 取り扱うデータ量の増加によってこの問題が顕著になってきたため次の選択肢を探しました。 それがQdrantです。 Qdrant QdrantはSemantic searchを備えたRust製の検索エンジンです。 対抗馬としてはMilvus, Weaviate辺りでしょうか。 qdrant.tech QdrantはMilvus, Weaviateと比較して機能が少ない反面、構成が非常にシンプルでパフォーマンスに優れています。 前述の通り想定しているユースケースでは書き込みのスケールはそれほど必要ないため、動的なシャーディングや書き込みと読み込みのパスを分けるmicroservicesモードは不要でした。 (我々が得意なRustで書かれているので、構成がシンプルなこともあり何かあっても自分でどうにかしやすいというのもあります。) これもRedisと同様にコードジェネレータから利用できるようにして提供しました。 KEELにはコードジェネレータがあるため、Kubernetesではあるもののインタフェースが固まるまでは オペレーターパターン では提供しておらず、コードジェネレータのインプットとなるyamlにこう書くだけでQdrantクラスタが起動するようになっています。 spec : feature : qdrant : enabled : true replicas : 3 このタイミングで適切なダッシュボード・アラートから運用ドキュメントまでがコードジェネレータによって自動生成されているのでこの作業だけで既にProduction Readyです。 コミュニティからHelm chartも提供されていましたが、厳格な SecurityContext や TopologySpreadConstraint , Topology Aware Routing , mTLSなど我々がKubernetes Manifestsに要求する基準を満たすことが難しいため、基本的にHelm chartは利用しない方針でやっています。 Embeddings APIのキャッシュプロキシの提供 ここまででLLMの長期記憶を実装するために、データの生存期間が明確なユースケース向けにメモリ上で高速なSemantic searchを実現するRedisクラスタと、メモリに乗りきらない知識を記憶するためのディスクをバックエンドとしたQdrantクラスタを提供できました。 今後もユースケースに応じてサポートするデータストアを増やしていくことになるはずで、そうなるとデータストア間の移行コストが気になります。 ベクトル表現である Embeddings の生成には結局OpenAIのモデルを使うことになることが多く、愚直に新しいデータストアに再インデックスしてしまうと決して安くない金額がかかってしまいます。 そこでプラットフォームからのアプローチとして、OpenAI及びAzure OpenAI ServiceのEmbeddings APIのキャッシュを透過的に行うプロキシを開発しました。 キャッシュがなければUpstreamのAPIにリクエストしてその結果をキャッシュ、キャッシュがあればそのまま返すというよくあるやつです。 (この図はChatGPT PluginのShow Me Diagramsを利用して作りました。) OpenAIの公式クライアントである openai/openai-python などは OPENAI_API_BASE 環境変数でAPIの向き先を変更することが可能です。 インターフェースは揃えてあるので、これを利用して向き先をキャッシュプロキシに変えることでクライアントへの変更なしに透過的にキャッシュを挟むことができるといった具合です。 Embeddings APIのレスポンスはJSONなので素直にgzipしてオブジェクトストレージに保存するだけのシンプルなソフトウェアになりました。 Embeddingsを生成するために利用するモデルの名前がリクエストボディに入ってくるのでキャッシュのキーは普通にリクエストボディのハッシュ値でよくて、キャッシュのExpirationはオブジェクトストレージ側に任せてしまっています。 OpenAIとAzure OpenAI Serviceの差異は openai.util.api_key_to_header などでOpenAIの公式クライアントが吸収してくれているのでこの辺をそのまま利用すると楽ができます。 我々はObservability Platformも提供する内製PaaSのチームなので、(布教も兼ねて)このキャッシュプロキシにもちゃんとOpenTelemetryを入れてUpstreamに投げられたトークン数の監視と分散トレーシングをしました。 OpenAIはAPI Keyごとに利用料を追えないため、このレイヤでトークン数を監視することで細かく利用料を確認することができます。 これをプラットフォームから提供することで、単なるデータストア移行のコスト削減だけでなくサービスをまたいだEmbeddingsの共有みたいなところも狙っています。 ChatGPT Retrieval Pluginの構築 LLMの長期記憶のユースケースとしてすぐに思いつくのはやはり社内のQ&A Botでしょう。 社内のドメイン知識をもとにChatGPTが回答できるようになれば、いわゆる社内質問窓口の一部を代替できるはずです。 そのためのソフトウェアをOpenAIがChatGPT Pluginの発表と同じようなタイミングで公開しています。 openai/chatgpt-retrieval-plugin です。 これはベクトルデータベースをバックエンドとして文書のインデックスとSemantic searchをするChatGPT Pluginで、PDFのパースなどLangChainの Document Loader のような部分も内包していて、これさえあればすぐにChatGPTに社内ドキュメントをもとにした回答をさせることができます。 これを適当な場所で動かして、社内ドキュメントをインデックスするだけでやりたいことができそうです。 ただ、この手の誰のJob Descriptionにも書かれていないような仕事は往々にして進みが悪くなりがちです。 我々が開発する内製PaaSであるKEELを利用すればすぐにでも動かすことができるため、社内のQ&Aフォーラムを運営していてRetrievalに関心があった二宮の協力も得ながらプラットフォームの一環としてこの仕事を始めました。 www.lifull.blog 現在はQdrantをバックエンドにしたChatGPT Retrieval PluginがKEEL上で稼働しており、同様にKubernetesのCronJobで社内Q&Aフォーラムをはじめとした社内ドキュメントを定期的にインデックスするバッチプログラムが動いています。 それをSlack BotからLangChainの Retrieval QA 経由で呼びだして、社内のドメイン知識をもとに回答するChatGPTを実現しました。 残念ながら今のところはChatGPT Retrieval Pluginはforkして利用してしまっており、運用上で見つかったいくつかの課題はタイミングを見てパッチを送るつもりではあります。 QdrantのReplication FactorやShard数を外から与えることができない( datastore/providers/qdrant_datastore.py#L275 ) (Redisを代わりに使う場合)Connection PoolなしにRedisの接続を持ち回すため retry_on_error を書かないと接続先の入れ替わりなどに対応できない( datastore/providers/redis_datastore.py#L92 ) Chunk分割のロジックで日本語の文末が考慮されていない( services/chunks.py#65 ) Chunkのサイズは言語ごとのトークン数の消費具合を考慮して決定した方がよい( services/chunks.py#L15 ) などです。 コマンドラインツールでのLLM活用 一気に毛色が変わってコマンドラインツールでLLMを利用した機能を提供している話です。 我々はコードジェネレータをはじめ内製PaaSのKEELを利用するために便利な機能が詰まった keelctl というコマンドラインツールを提供しています。 keelctl self-update というコマンドで簡単に最新にバージョンアップすることができ、コードジェネレータである keelctl gen を実行するとKubernetes ManifestsからGitHub Actions, 運用ドキュメントまで最新のベストプラクティスが生成されるというようなソフトウェアです。 www.lifull.blog こうした機能を持つため大抵の開発者の手元には常に最新の keelctl が入っているような文化を作ることに成功しました。 これによりプラットフォーマーとしてKubernetesクラスタ経由での機能提供だけでなく、コマンドラインも握れているため開発者のローカルの環境にも影響力を持つことができています。 活用を促進するならまずは背中を見せようということで、その keelctl では keelctl llm というサブコマンドでLLMを利用した機能をいくつか提供してきました。 今回はそのうち keelctl llm conventional-commits というConventional Commits形式のコミットメッセージを自動生成する機能を紹介します。 Conventional Commits形式のコミットメッセージの自動生成 正直この辺の開発環境でのLLM活用を考えると大抵GitHub Copilotとバッティングすることになります。 しかしConventional Commitsは組織でルールを微調整したかったりするため自前でやる価値があると判断しました。 Conventional Commits とは人間と機械が読みやすく、意味のあるコミットメッセージにするための仕様です。 www.conventionalcommits.org 人間が手書きするには少し体力のいる仕様で、ルールも細かく定義されているためこれの自動生成はLLMが得意そうなタスクです。 プロンプトは後述しますが、トークン数節約のためLIFULLでは不要な制約を少し消しているのとコミットの型を明示しています。 自動生成の機能としては非常に単純で、あらかじめ与えておいたプロンプトをもとに、標準入力として受け取った git diff の出力結果からConventional Commits形式のコミットメッセージの候補を指定した数だけ生成し、Fuzzy Finderで良さそうなコミットメッセージを選択するとそれを標準出力するというものです。 このように使います。 $ git diff --cached | keelctl llm conventional-commits --select 3 | git commit -F - プロンプトの role: system はこんな感じです。 Please create an appropriate commit message for the given diff according to the following specifications. --- ## Specifications 1. Commits MUST be prefixed with a type, which consists of a noun, feat, fix, etc., followed by the OPTIONAL scope, and REQUIRED terminal colon and space. 2. A scope MUST be provided after a type. A scope MUST consist of a noun describing a section of the codebase surrounded by parenthesis, e.g., fix(parser): 3. A description MUST immediately follow the colon and space after the type/scope prefix. The description is a short summary of the code changes, e.g., fix: array parsing issue when multiple spaces were contained in string. 4. A longer commit body MAY be provided after the short description, providing additional contextual information about the code changes. The body MUST begin one blank line after the description. 5. A description MUST be kept within 72 characters. 6. The first letter of a description MUST be capitalized. 7. A longer commit body MUST be kept within 400 characters. 8. MUST not contain any footer. 9. The appropriate type/scope MUST be determined from the diff. 10. The commit message MUST be written in English. ## Available Types - feat: Addition of new features - fix: Bug fixes - docs: Documentation-only changes - style: Changes that do not affect the meaning of the code (whitespace, formatting, missing semicolons, etc.) - refactor: Code changes that do not modify existing functionality or add new functionality, such as changing variable or function names - perf: Performance improvements - test: Modifications or additions to existing tests - chore: Changes that are not important to developers, such as changes to the build process or library dependencies この機能を配布するにあたって、利用者の手元に OPENAI_API_KEY がないと使えないということは避けたいです。 API Keyの共有はセキュリティ的に論じるまでもないですし、それぞれがAPI Keyを発行することは利用のハードルが高すぎます。 そこで、我々が用意している認証基盤を利用することにしました。 内製PaaSのKEELではLIFULLで利用しているSSOのサービスをIdPとしたSAMLの認可プロキシを用意しています。 この認可プロキシのUpstreamとしてOpenAIのAPIを設定して、このプロキシのレイヤでOpenAIのAPI Keyをヘッダに入れることでOneLoginでログイン済みのユーザであれば OPENAI_API_KEY なしにOpenAIのAPIが利用できます。 認可プロキシなのでログも取れており、これもキャッシュプロキシの章で述べたOpenAIではAPI Keyごとに利用料を管理できない問題を解決できました。 ただしこの認可プロキシはCookieをもとに認可を行うため、Cookieを持たないコマンドラインからは素直に利用することができません。 こういう時にはよくある認証パターンを利用することができます。 コマンドラインツールが 0.0.0.0:0 でHTTPサーバとして待ち受ける( :0 で待ち受けるとランダムな空いているポートを割り当てることができます) コマンドラインツールが待ち受けているアドレスを redirect_url クエリ文字列に付与して <Authorization Proxy URL>/callback を xdg-open で開く( xdg-open はコマンドラインからブラウザを開くためのプロトコルです) Authorization Proxyで認可を行い必要に応じてSSOで認証する Authorization Proxyが認証済みのCookieの値をクエリ文字列に付与して redirect_url にリダイレクトする コマンドラインツールが受けたリクエストのクエリ文字列からCookieの値を取得する そのCookieを利用して認可プロキシにリクエストを投げる という流れです。 これにより、既に開発者の手元に入っているコマンドラインツールから一切の設定を必要とせずにLLMを利用した機能を提供することができました。 この機能は結構好評で、後続のリリースでConventional Commitsをもとにしたバグ発生率などのレポーティング機能も実装したこともあり、既に多くのチームでConventional Commitsが採用され始めています。 GitHub Copilotの隙間を縫っただけのような気もしますが、これで一つLLM活用の方向性を示すことができたと思います。 今後の展望 LLM活用促進に向けてPlatform Engineeringから行ってきたアプローチをいくつか紹介しました。 ですが、実際のところLLMのインパクトに対しては社内の活用はまだ不足していると感じています。 ジェネレーティブAIプロダクト開発室は新設されたものの、LLMは陳腐な言い方をすれば民主化されたAIであり、専任部署だけのものではないどころかソフトウェアエンジニアに限らず多くの人が活用してしかるべきです。 今後もプラットフォーマーとして活用の下支えをしながら一層LLMを「あらゆるLIFEを、FULLに。」の実現に繋げていこうと思います。 直近では組織にまだ活用のイメージが不足していると思っていて、 AI戦略室 と主要なアプリケーション開発者とともに、実際のアプリケーションでLLMを利用する生きた参考実装を用意しようと動いています。 個人的にはそろそろ(概念としての)AutoGPTによるアプリケーション実装の自動生成と真剣に向き合う頃合いかなとも思っています。 Platform EngineeringではこれまでSREを中心に価値あるプラクティスを組織に適用してきました。 これはプラットフォームがレバレッジの効くソフトウェアであるからで、LLMのような新たなパラダイムが出てきた時にそれを組織に適用する責任もプラットフォーマーにはあるはずです。 そして我々KEELチームはその結果として「あらゆるLIFEを、FULLに。」の実現にスケーラブルに貢献していくことを目指しています。 そんなKEELチームにもし興味を持っていただければ是非カジュアル面談しましょう! hrmos.co hrmos.co
こんにちは。エンジニアの渡邉です。普段はLIFULL HOME'Sの売買領域のエンジニアチームにて開発を担当しています。好きなGCPのサービスはCloudRunです。 今回は、LIFULL HOME'Sの物件画像を次世代画像フォーマット「WebP」形式に動的変換して配信できるようにした取り組みについて紹介します。 WebPとは WebP導入の背景 画像変換サーバの基盤刷新 主要なブラウザのWebPサポート 実現方法の検討 工夫した点 WebP対象外のブラウザからリクエストが来た場合に対する対応 CloudFrontのキャッシュ条件を変更 成果 パフォーマンスの改善 運用コストの軽減 全アプリケーションへの一括WebP対応 終わりに WebPとは WebP (ウェッピー)はGoogleがWebサイトの表示速度短縮を目的として開発した静止画像フォーマット画像形式のことを指します。 画質の劣化を最小限に抑え、画像サイズを軽くできます。表示速度改善によりエンドユーザーに好影響を与えるため、現在JPEGやPNGに変わる画像形式として注目されています。 WebP自体は2010年に仕様が公表され、多くのブラウザでサポートされています。 WebP導入の背景 サイト上で配信する画像をWebP形式に変換するしくみを導入する主な目的は、コストカットや表示速度改善、ユーザー体験向上のためです。 LIFULL HOME'SにおけるWebPの導入を長らく検討してきましたが、 画像変換サーバの基盤刷新 主要なブラウザのWebPサポート という2点において環境が変化したため、導入を進めることができました。 画像変換サーバの基盤刷新 LIFULL HOME'Sでは、物件画像のサイズやクオリティ、フォーマットなどを利用するアプリケーションに合わせて動的に調整する内製の「画像変換サーバ」を用いて画像を配信しています。 もともと私は画像の最適化に興味があり、LIFULL HOME'Sの画像を最適化し、最高の画像をユーザーに届けることを目標にしていました。 そのための地盤として、開発しやすいように画像変換サーバを新基盤に移行していました。 以前紹介した画像変換サーバの基盤刷新についてはこちらをご覧ください。 www.lifull.blog 主要なブラウザのWebPサポート WebP化を導入しようと考えていたときに抱えていた課題として、主要ブラウザの一部がWebPをサポートしていないことがありました。 当初WebP化を検討していた際にはInternet ExplorerとSafariにてWebP形式をサポートしていませんでした。 LIFULL HOME'Sを利用しているブラウザのうち、Internet ExplorerとSafariの利用率は無視できず、WebPを導入しても中途半端な成果になってしまうことが懸念だったのです。 しかしながら、現在Internet Explorerはサポートを終了し、SafariはWebPをサポートするようになり懸念点が解消しました。 そのため、WebP化の恩恵を強く受けられる算段が立ったので、導入に踏み切ることができるようになりました。 実現方法の検討 今回WebP配信化させるにあたって以下の二択の方法を考えていました。 すでに存在する画像ファイルを事前にWebP形式にしてそれを配信する方法 現在利用しているngx_small_lightを使用した画像変換サーバにて動的に変換させる方法 両者を比較したところ、今回は後者のngx_small_light側で動的に変換することを選択しました。 その理由としては次のような理由がありました。 ストレージに保存する画像のコストを下げたい 多様な環境からのリクエストに対応するため、配信する形式を動的に選択できるようにしたい 工夫した点 今回WebP化することを決めた上で、苦労したことがいくつかありましたので紹介します。 WebP対象外のブラウザからリクエストが来た場合に対する対応 LIFULL HOME'Sを利用されるユーザーは多種多様なため、Internet ExplorerなどのWebPが表示できないブラウザを利用されている可能性があります。 その場合、画像がまったく表示されないサイトになってしまうので、それを回避するためのしくみが必要でした。 今回どのように対応したかというと、Acceptヘッダの中身を見て変換するかを決定するプロセスを設けました。 ChromeなどのWebPを表示可能なブラウザの場合、以下のような image/webp をAcceptヘッダに持っています。 image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 この image/webp の有無によって元の画像をそのまま返すか、動的に変換したWebPを返すのかを選択しています。 Acceptヘッダに対応した画像フォーマットであればブラウザ側は問題なく表示できます。したがって、利用するアプリケーションはWebPが来る場合とそれ以外の画像形式が来る場合をそれぞれハンドリングする必要なく利用できます。 CloudFrontのキャッシュ条件を変更 LIFULL HOME'Sでは多くの画像を表示するため、負荷を軽減するためにCloudFrontによるCDNキャッシュを採用しています。 今までは画像を取得するURIに対して1:1になる形でCloudFrontにキャッシュできていました。しかしながら、今回の変更により、同一のURIであっても変換されたWebP形式か元の画像形式かの二択になってしまうことが予想されました。 もしWebP形式でキャッシュされていた場合に、Internet Explorerで表示しようとすると画像が表示されないといったことにつながります。それを回避すべくCloudFront側もAcceptヘッダの中身を確認したうえでキャッシュする機構に変更しています。 成果 苦労したこともありつつ、なんとかWebP対応を完了させたことで、いくつか大きく成果を出すことにつながりましたので紹介させていただきます。 パフォーマンスの改善 当初の目的にあった通り、画像の軽量化を行うことができました。 画像は平均して約20%ほどの軽量化に成功し、画像の表示スピードにも好影響を及ぼすことに成功しています。 運用コストの軽減 こちらのコストカットが今回だとかなり大きな成果としてつながりました。 もともと多くの画像を保有し表示することになるLIFULL HOME'Sでは大量の画像をCloudFrontにキャッシュしています。 多くの画像をキャッシュしているために高いコストを毎月かけて運用していましたが、WebP形式にしたことで画像サイズが20%軽減し、実績としてコストカットをすることにつながりました。 また、自社でWebP化を内製できたことで、もともと外部のサービスを利用する等の検討もありましたが、その必要がなくなったことも大きかったです。 全アプリケーションへの一括WebP対応 今回改修を加えた画像変換サーバは弊社の運営している多くのサービスが利用しているアプリケーションとして運用しています。 そのため、汎用的にWebPを返すしくみを作ることができたことによって、すべてのアプリケーションに対してWebPフォーマットでの画像を配信することに成功しました。 各アプリケーションで対応することなく、さまざまなメリットのあるWebPを一斉に適用できたことは、LIFULL HOME'S全体のパフォーマンス向上につながる成果となりました。 終わりに 今回はLIFULL HOME'Sにて画像最適化の文脈で多くのメリットがあるWebPフォーマットでの配信を実施でき、期待通りの成果を上げることができました。 もともと画像の最適化に興味があり、いつかLIFULL HOME'Sの画像をすべてWebPにするぞ!と意気込んでいた私としては感無量でした。 画像最適化はまだまだ多くの手法がありますので、これからも実践していければなと考えています。 弊社ではWebPを配信するにあたり画像変換サーバに対して手を加えるのが一番効果的であると判断しました。しかしながら、アプリケーションの特性によっては事前に変換しておく運用であったり、外部サービスを用いるといった手法も効果的であると思います。 WebP化することでの恩恵は大きいと思いますので、ぜひご検討してみてはいかがでしょうか。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは。AI戦略室 主席研究員の清田陽司です。 LIFULLが取り組んでいるさまざまな研究開発の課題を、より多くの社外の方々(とくに大学の研究者や学生)に共有することで、LIFULLだけではなし得ないより大きな研究成果につなげる、「産学連携」という活動を行っています。 実は、 LIFULL HOME'S 3D間取り というサービスも、産学連携の長年の取り組みの成果の一つです。 このたび、3月に開催された 第15回データ工学と情報マネジメントに関するフォーラム(通称 DEIM 2023) に、協賛企業の担当者、かつ「産学連携委員長」という役割で関わりましたので、その様子を報告します。 event.dbsj.org 開催会場の長良川国際会議場(岐阜県岐阜市) DEIMは、いわゆる「学会イベント」の一つで、 日本データベース学会 、 情報処理学会データベースシステム研究会 、 電子情報通信学会データ工学研究専門委員会 による共催です。 前身の「データ工学ワークショップ」から30年以上続いていて、例年600名以上の参加者を集めています。 LIFULLはもちろん、多くの企業がこのような学会イベントに協賛・参加しています。 この記事では、コロナ禍を経て4年ぶりに実現した現地開催の様子や、企業がこのような学会イベントに関わる意義について、お伝えしたいと思います。 コロナ禍と学会イベント 2020年初頭から続くコロナ禍は、多くの人が集まることに意義のある学会イベントに、大きな打撃を与えました。 2020年3月に開催された DEIM 2020 は、まさにコロナ禍の直撃を受けた学会イベントの一つでした。 DEIMは、例年合宿形式で開催され、参加者間の濃密な議論や交流が行われることを、大きな特色としていました。 私自身も毎年参加し、昼のさまざまなセッションでの発表や参加だけでなく、夜の美味しい料理やお酒を嗜みながらの多くの方々との交流も楽しみにしてきました。 しかし、新型コロナウイルス感染症が急激に拡大する中、このような濃密な交流をすること自体が許されない状況となり、実際に、多くの学会イベントが開催中止に追い込まれました。 DEIM 2020は、磐梯熱海温泉での開催が予定されていましたが、急遽現地開催から、当時ほとんど実績やノウハウのなかったオンライン会議システムによる開催に切り替えられました。 多くの学会イベントが中止に追い込まれる中、質疑応答を含む発表セッションをフルオンラインで実施できるシステムを短期間で構築し、フルオンライン開催を実現したDEIM 2020の挑戦は大きな注目を集め、メディアでも取り上げられました。 www3.nhk.or.jp www.asahi.com www.businessinsider.jp 私自身も協賛企業担当者としてDEIM 2020に参加していました。 オンライン開催のシステムを構築された関係者の方々の奮闘を目の当たりにし、大きな感銘を受けたことを鮮明に覚えています。 LIFULLでも、オンライン開催への切り替えを受けて、協賛を継続するか取りやめるかの判断を迫られましたが、このような危機的状況だからこそ継続して学会イベントの成功を支える、という決断に至りました。 いま振り返っても、この決断をしてとても良かったと思います。 国立情報学研究所(NII)に急遽設置されたDEIM 2020運営事務局 これに続く DEIM 2021 、 DEIM 2022 も、当初は現地(それぞれ磐梯熱海温泉、名古屋市)での開催が計画されましたが、コロナ禍での感染拡大のリスクが懸念されたため、いずれもフルオンラインでの開催を余儀なくされました。 オンラインでの学会イベントにいくつも参加してきて、オンライン開催にはメリット、デメリットの両方があることを感じてきました。 「参加者の幅が拡がる」ことは、オンライン学会イベントの良さの一つです。 参加費用、子育てや介護などの個別の事情により、これまで参加をあきらめてきた方々が多く参加し、議論や交流を楽しまれている様子を、私自身も数多く見ることができました。 より多様な方々が参加することで、議論の幅も拡がることは、オンライン学会イベントならではのメリットだと思います。 DEIM 2022では、過去最大となる1376名の方々が参加し、大盛況でした。 一方で、オンライン学会イベントには、「参加者間の交流が減ってしまう」という課題もあります。 現地開催の学会イベントでは、休憩時間中に顔なじみの方とたまたま出会っての雑談など、数多くの交流機会がありますが、オンライン開催ではどうしても減ってしまいます。 より多くの交流機会をもつことがメリットとなる協賛企業にとっても、交流機会が減ってしまうのは大きな悩みでした。 「直列ハイブリッド開催」という挑戦 オンライン開催と現地開催の良いとこ取りをする「ハイブリッド開催」は、すでにいくつかの学会イベントで取り入れられています。 私自身も、 人工知能学会全国大会(JSAI 2022、京都) など、いくつかのハイブリッド開催の学会イベントに参加し、その良さを実感しました。 一方で、ハイブリッドのイベント開催は本当に大変です。 現地参加者とオンライン参加者がスムーズに交流できるようなシステムは大がかりになりますし、トラブルが起きたときに対応できるスタッフも多数必要となります。 そこで、DEIM 2023では、3年間続いた完全オンライン開催の経験を踏まえて、新たに「直列ハイブリッド」という開催形式に挑戦しました。 具体的には、最初の3日間(3月5日〜7日)に一般発表セッションを完全オンライン開催で実施し、その後(3月8日〜9日)に現地会場(岐阜市)に集まってインタラクティブセッション(ポスター発表)などの交流イベントを実施することになりました。 このように、オンライン開催と現地開催を時間的に分ける(これを「直列ハイブリッド」と呼ぶことになりました)ことで、ハイブリッド開催の「コストや労力がかかる」というデメリットを最小化しながら、オンラインイベントだけ参加したい方々、現地で多くの参加者と交流したい方々の、両方のニーズを満たすことを目指しました。 結果として、直列ハイブリッド開催という形式は大成功だったという感想をもちました。 オンラインでの一般発表セッションで、より多くの発表をオンラインで視聴した上で、現地のインタラクティブセッションで、興味のある研究内容について、発表者とポスター前でじっくりと議論する、という、新たな学会イベントの形が実現できたように思います。 開催期間が5日間と長くなってしまうのが課題ですが、「学会の目的は何か」という原点に立ち返り、時代の変化に合わせてイベントのあり方を柔軟に変えていくことは、とても大切なことだと感じました。 「直列ハイブリッド」という前例のない開催形態に挑戦された実行委員長の鈴木優先生(岐阜大学)を代表とする幹事団の皆さまに、心からの敬意を表したいと思います。 この写真は、現地にて開催されたネットワーキングセッションの様子です。 Open Space Technology(OST) という枠組みで、参加者が話したいテーマをシェアし、それに興味をもつ人々が自由に集まって対話するという時間となり、大変盛り上がりました。 産学連携委員長としてのお仕事 今回のDEIM 2023では、産学連携委員長という役割を担当しました。 企業向け協賛プログラムの枠組みを設計するとともに、協賛企業各社と、大学の研究者を中心とする委員の方々とをつなぎ、産学連携をより密接にするという役割です。 今回は、「直列ハイブリッド」という開催形態のもとで、どのような協賛プログラムにすると多くの方に価値を感じていただけるか、幹事団や協賛企業の担当者など、多くの方々と相談しながら模索しました。 その結果、「4年ぶりの現地開催の機会を生かして、企業ブースに多くの参加者が集まる仕掛け」、および「協賛企業各社からの技術報告発表を、一般発表と同じオンラインセッションの時間に組み込むことで、参加者からより多くの質問が集まる仕掛け」に注力した協賛プログラムとすることになりました。 非常に有り難いことに、今回も19社もの企業から協賛をいただくことができ、充実した協賛プログラムを参加者の方々にご提供することができました。 とくに、最終日の常設ブースは、各社のブースに多数の参加者が集まり、大変盛況でした。 私自身も、LIFULLのブースにポスターを出して不動産分野でのAI関連の研究テーマなどをお伝えし、さまざまな反響をいただきました。 何よりも、参加者の方々が4年ぶりの現地での交流を楽しまれている様子が感じられ、主催者としても大変嬉しかったです。 協賛いただいた各社の皆さま、技術報告発表や常設ブースにて交流いただいた参加者の皆さまに、心より御礼申し上げます。 常設ブースの様子 オンライン会議用に参加者に配布されたバーチャル背景 企業が学会イベントに関わる意義 多くの企業がこのような学会イベントに積極的に関わっているのはなぜでしょうか? 優秀な学生の採用につなげたいという目的は、すべての企業が共通してもっています。 しかし、学会イベントを単に自社の魅力をアピールする場として活用するだけでは、効果も薄く、また長続きもしないように感じています。 学会イベントでは、学生さんだけでなく、大学の研究者の方々、他社の研究者や人事担当者など、さまざまな方々と交流することができます。 大学の先生方とお話しすることで、いまの学生が関心をもっているテーマなどについて詳しく知ることができます。 他社の方々とお話しすることで、他社がどのような考え方をもとに研究開発を進めているかなどについて、理解を深めることができます。 学会イベントの参加者との交流を深める上で非常に大切なのが、学会イベントの価値を高めるために、自社としてどのような貢献ができるのかという視点です。 LIFULLは、 利他主義 を社是として掲げています。 目の前にいる人をHAPPYにすることで自分もHAPPYになれる という考え方を、私達は学会イベントに関わる上でも大切にしています。 最後になりますが、LIFULLでは共に成長しながら働く仲間を募っております。 AI戦略室では シニアデータサイエンティスト を募集中です。 hrmos.co カジュアル面談もありますのでご興味ある方は是非ご応募ください! hrmos.co 今後も、LIFULLでの産学連携活動について本ブログで発信していきます。どうぞよろしくお願いいたします!
社内でChatGPTの普及のためハッカソンを開催しました こんにちは。クリエイターの日運営委員の花岡です。 4/20にLIFULLでChatGPTハッカソンを実施したので、その模様について報告します。 近年、ChatGPTによる技術革新はめざましいものがあります。 LIFULLのサービスとしては、先日ChatGPTを活用したAIホームズくんbeta LINE版をリリースをしています!! lifull.com このような動きの中で、社内向けとしてもChatGPTの可能性を探るべく、ハッカソンを開催しました。 急な開催ではあったものの、22名の方にご参加いただきました! 今回のハッカソンの狙いは以下の通りです。 ChatGPTを使ったハッカソンを実施することで、新しいアイデアの種にしてもらう。 LIFULLでの、ChatGPTへの関心を高めてもらう。 エンジニア・非エンジニアに限らずChatGPTを社内で使ってもらうことにより、ChatGPTという技術に慣れ親しんでもらう。 社内にプロンプトのノウハウを蓄積する。 今回は入門編ということで、エンジニア以外にも広く参加してもらう形式をとりました。 ハッカソンの概要 今回は、ChatGPTが盛り上がってまだ日が浅いことや、エンジニア以外の参加者も多いことからハンズオン、ハッカソンの二部形式を取ることにしました。 また、今回のハッカソンでは弊社のAI戦略室で作成いただいたChatGPTツールを使用しました。 第一部: ハンズオン LLMの概要について ChatGPTと社内向けツールの説明 第二部: ハッカソン ルール説明 ハッカソン 成果発表 表彰 第一部 ハンズオン LLMの概要説明 ハンズオンの前半では、弊社の加藤さんにLLMの概要説明をしてもらいました。 LLM概要説明 ChatGPTと社内向けツールの説明 今回のハッカソンでは、弊社AI戦略室で作成した社内向けツールを用いてハッカソンを行いました。 なお、社内ツールは以下の目的で作成されました。 プロンプトを入力しやすいGUIを作成してOpenAIのAPIを手軽に使えるようにすること。 プロンプト(コンテキスト)を共有できるようにしてノウハウが横展開されやすいようにすること。 後半では、谷山さんにChatGPTや社内ツールの使い方について説明してもらいました。 ChatGPTについて 社内ツールについて 第二部 ハッカソン 今回のハッカソンでは 「LIFEをFULLにするモノ」 をテーマにプロンプトを作成してもらいました。 先ほど説明した社内ツールを使用して2時間ほどの時間でテーマに沿ったプロンプトを作成してもらいました。 表彰作品 今回のハッカソンでは、投票による「ハッカソン優秀賞」と、弊社のCTOの長沢さん、取締役の山田さんに選んでもらう「長沢賞」「山田賞」の2つの特別賞を用意しました。 ハッカソン優秀賞 武田 裕子「発注したい」 〜選定理由〜 実際に運用できると思った。結構良い感じにほかのタスクにも適用できそう。 こんなに詳しく段階的に正確な返しができるようになるとは、と感動しました 業務支援で使えそうだから。社内のchatbotで使いたい etc.. 発注したい 長沢賞 二宮 健「タスク内容相談くん」 〜選定理由〜 どれも使ってみましたが、一番自然かつ使えそうな答えが帰ってきました、 タスクを進める際にどの観点を気を付ければ良いのか、はっきりさせておいたほうが良いところはないか?など、経験の浅い人にも良いメンターになりそうなためです。 タスク内容相談くん 山田賞 羽賀 崇史「振返り精度UP」 〜選定理由〜 対話botの基本的な使い方と言えるかもしれないが、実用的であり使い続けることで人間の成長にもつながるAIとの良い関係が作れそうと感じたため。 振り返り精度UP ここでは紹介しきれませんでしたが、表彰作品以外にもユニークですばらしい作品がたくさんありました。 ハッカソンを振り返って 今回のハッカソンの時間は短めだったにもかかわらず、ユニークでおもしろいプロンプトがたくさん出てきました。 また、エンジニア以外にもたくさんの方に参加いただき、ChatGPTを使ってどのようなサービスを作っていくかの発想の手がかりになったと感じています。 今後社会課題の解決に向けて、ChatGPTの大きな可能性を感じることができるハッカソンになりました。 最後に LIFULLでは、冒頭で紹介したAIホームズくんを始め、ChatGPTやLLMのサービス適用を広げるべく積極的に取り組みが進められています。 また、今回のハッカソン以外にも 熱海ハッカソン などさまざまなイベントを開催しています。 LIFULLに興味のある方、ぜひ一緒に働きませんか。 よろしければこちらのページをご覧ください。 hrmos.co
こんにちは、フロントエンドエンジニアの嶌田です。 アクセシビリティは今まで以上に大きな関心を寄せられるトピックになってきたように思います。個人で関心がある人、企業のなかで周りを巻き込み推進しようとしている人、すでに組織全体での取組みに変わりつつある企業など、状況は様々だと思います。弊社はというと、内側からの推進活動は広がりを見せつつも、まだ組織一丸となった取組みには至っていない、といったところです。 そんな状況の私たちですが、社外のアクセシビリティを推進する同志たちに、ほんの少しでも力を分け与えられたらと思い、このたび「LIFULLアクセシビリティガイドライン」を公開しました。取組み状況が様々ある中でどのように活かしていけるか、まずは一度ご覧いただければ幸いです! lifull.github.io アクセシビリティとは? アクセシビリティとは、高齢者や障害者を含むできるだけ多くの人々に対して、プロダクトやサービスを利用可能にすることです。アクセシビリティを高めると、利用者の母数が増えるだけではなく、けがや病気、一時的に不利を負った状況でもプロダクトやサービスが使いやすくなり、ユーザー体験の向上につながります。 LIFULLアクセシビリティガイドラインとは? LIFULLアクセシビリティガイドラインは、LIFULLの全てのプロダクトやサービスのアクセシビリティを高めていくために策定されたものです。 LIFULLのプロダクトに関わる全ての人を対象にしていますが、内容は普遍的で、多くのデジタルプロダクトやサービスの制作現場において活用できるものになっています。 LIFULLアクセシビリティガイドラインの特徴 コンセプトは「 自分がいまやるべきことがわかるガイドライン 」です。もう少し具体的にすると、次のような特徴を持つように編成されています。 工程に応じた項目 ガイドラインの項目は工程(≒職種)ごとに大きく分けられています。 デザイン コンテンツに関するものや、UIやインタラクション、ビジュアル表現についてのガイドラインがまとめられています。 実装 HTMLやCSS、JavaScriptの実装方法に関するガイドラインがまとめられています。 明確な優先度 重要度、コスト、LIFULLの制作事情を総合的に判断した「レベル」と呼ばれる優先度を導入しています。レベルは3段階に分かれており、ひとまずこのレベルに従って対応を進めていくように制作者に求めています。 レベル1…必ず達成 ユーザーに大きな影響があり、どのサービスでも必ず達成したい重要なタスク。 レベル2…可能な限り達成 レベル1に次いで重要で、できるだけ達成してほしいタスク。 レベル3…できれば考慮 できれば考慮してもらいたいタスク。 レベル3までのガイドラインにすべて対応すると、WCAGのA, AAの基準に(おおむね)対応できるようになっています。 わかりやすい記述 初学者でも理解できるように、図表や例を使って具体的に記述しています。ガイドラインが必要とされる背景や、恩恵を受けるユーザーについても簡潔に記載しています。 項目によっては、厳密な正しさや網羅性を備えていないものもありますが、その役割は別のリソースを参照してもらうということで、本ガイドラインは簡潔さを保っていく方針としています。 関連リソースへのアクセス 理解の促進に役立つリソースや、関連するリソースへのリンクを設けています。 なぜ新しいアクセシビリティガイドライン? W3Cが勧告しているウェブコンテンツアクセシビリティガイドライン(WCAG)をはじめ、freeeやサイバーエージェントといったアクセシビリティで先行している企業が一般公開している質の高いガイドラインがすでに存在しています。それでも私たちは独自のアクセシビリティガイドラインを作成・公開する動機がありました。 主な理由は以下の通りです。 WCAGは難しすぎる WCAGは人類の叡智ともいうべき素晴らしいガイドラインですが、抽象度が高く、知識や経験がないと理解することが難しいです。アクセシビリティ専門でやっていない現場の人全員に、これを読んで理解することを求めるのはハードルが高いと思いました。 「今何をすればいいのか」がすぐわかるガイドラインがない 現場で働く人たちが理解しやすく、実践しやすいガイドラインが求められていると考えました。先行する企業発のガイドラインはその観点では私たちが求めるガイドラインとは少し違ったものでした。 借り物のガイドラインを使い続けることには限界がある WCAGを除けば、企業発のガイドラインはその企業としての方針や現場感が反映されたものですし、そうあるべきだと思います。取組みの初手として既存のガイドラインをそのまま取り入れることは良い判断な一方で、現場からのフィードバックを受けて改良を加えられるようにするには新しいガイドラインを作るのがよさそうだと思いました。 制作裏話 工程ごとに分かれたガイドラインを見てピンときた方がいるかもしれませんが、LIFULLアクセシビリティガイドラインは、 IBMのEqual Access Toolkit に大きく影響されています。現場に寄り添うガイドラインを作りたいと思っていろいろ見ていた時に目につき、コレほしかったやつ~~と思い、真似させてもらいました。 このガイドラインはもともと、社内のドキュメントツールでひっそりと公開されていたものでした。すでに述べてきたような理由や、社外に公開することで社内にも改めて本気度を示すというような副次的な効果も見込んで、公開することにしました。 ガイドラインのデザインは、社内のアクセシビリティ推進ワーキンググループのメンバーである狩野さんが手がけました。挿絵のディレクションもしていただき、ガイドラインに魅力が加わっています。 狩野さんは先日、LIFULLのアクセシビリティへの取組みについてのブログ記事も書いてくれています。こちらもぜひお読みください! note.com LIFULLアクセシビリティガイドラインの今後 IBMのEqual Access Toolkitのように、ガイドラインをより実用的で日常的に使いやすいものにしていきたいと考えています。現在はデザイン、実装についてのガイドラインしか含まれませんが、たとえばプロジェクト計画の初期段階からアクセシビリティを導入していくためのガイドが作れるかもしれません。目標レベルにあわせたチェックリストを自動的に生成する仕組みを作っても便利そうです。 また肝に銘じなければいけないのは、ガイドラインを作成し、メンテナンスすることだけが目的ではないということです。メンバーの問題に常に向き合い、それを解決するツールとして機能し続けることが重要であることを忘れずにおきたいです。 フィードバックください LIFULLアクセシビリティガイドラインはGitHubでオープンソースソフトウェアとして公開しています。誤字脱字や内容の妥当性、わかりやすさや事例の推薦など、さまざまな観点からのフィードバックを歓迎しています! github.com お読みいただきありがとうございました。LIFULL では共に働く仲間を募集しています! hrmos.co hrmos.co
こんにちは。LIFULL ネイティブアプリエンジニアの佐藤麗奈です。 業務では LIFULL HOME'SのiOSアプリ (以下、LHアプリ)の開発を担当しています。 私が新卒で入社してから、早くも1年が経ちました。 今回は、LIFULLに入社してから今日までの歩みを振り返ってみたいと思います。 これからエンジニアとして働くけれど、未経験でもやっていけるか心配だなぁと感じているような方々の背中を押せる記事になれば幸いです。 配属前 情報系の学部を卒業しましたが、正直得意な言語も目立った成果物もない状態でした。 そのため、入社前はうまくやっていけるのか漠然とした不安を感じることもありましたが、人事の方が親身に相談に乗ってくれたり、同期との助け合いもあり1人で抱え込まずに不安を解消していくことができました。 入社直後は、約2週間の全体研修があり、4月半ばからは1ヶ月半のエンジニア研修がありました。 エンジニア研修では、Webアプリケーション開発の基礎の講義を受講した後に個人開発演習を行いました。 Webアプリの個人開発では、要件定義・設計・実装・テスト仕様書の作成・テスト実施の一連の流れを初めて1人で行いました。 期間は2週間と短い上、1ヶ月間の講義で学んだことを振り返りながら頭をフル回転させる毎日で、なかなかハードな日々でした。 しかし、講師の方や同期のサポートのおかげで、Webアプリをなんとか形にすることができました。 配属後 5~6月 エンジニア研修が終わり、5月末からLHアプリチームに配属されました。 初めはどんな業務にアサインされるのかドキドキしていると、まさかのネイティブアプリ演習課題を与えられました。 課題に取り組み始めてから1週間後の1次レビュー会では、Swiftの文法や処理の流れの理解不足を指摘され作り直しの宣告…。 課題の発表会は1週間後でしたが、 要件を細かく分割して考えること 調べたり教えてもらった内容を再度自分で整理して理解すること ソースコード上には自分が理解できたコードだけを書くこと の3点を徹底して、なんとか課題の要件を満たすものを作り上げることができました。 実際の業務ではネイティブアプリを1から作る機会はほとんどないため、非常に有意義な時間となりました。 また、課題と並行してアプリチームのミーティングにも参加していたのですが、最初は苦労しました。 なぜなら、ビジネス用語や不動産業界用語、社内用語など、ネイティブアプリの知識に収まらないさまざまな用語が飛び交っていたからです。 そこで、私と一緒にアプリチームに配属された同期と協力してアプリチーム用の単語帳を作成し、先輩方に教えていただいたことや調べたことをまとめていきました。 この単語帳は今でも大切なお守りで、自分たちのチームに後から加わった方々の役にも立っているとの声も届いて嬉しい限りです! 7~9月 7月頭からは、ついにLHアプリの開発が始まりました。 コード数、ファイル数、アプリの機能数など、とにかく全てが想像以上に多い!!! これだけの規模のアプリを作れるようになるにはどんなスキルを身につけていく必要があるのか、iOSアプリ開発のスキルロードマップを確認すると9割5分が未知の世界…。 大学の研究時にSwiftでカメラアプリを作成していましたが、そこで得た知識だけでは「Swiftでアプリ開発ができます!」とは到底言えないことを痛感しました。 この時期は、主にUIに関する不具合対応を行なっていました。 不具合の原因の調査をしつつ、アプリがどのように動いているかを知るために、アーキテクチャやライフサイクルの勉強をしたり、Xcodeのデバッグ機能を活用しながら処理を追いかけたりしました(今でも必死に継続中)。 また、App Storeに申請してアプリをリリースする作業も経験しました。 自分が作ったものが世の中に公開され、住まい探しをしているあらゆる人に使ってもらえているという実感が持て、とても嬉しかったです。 10~12月 この頃から、UIの開発がメインの施策にもアサインされるようになりました。 LHアプリチームでは、1つの施策につき、エンジニアが1人、企画が1人、デザイナーが1人がアサインされることが多いです。 エンジニアの主な担当は、実装・テスト仕様書の作成・テスト実施ですが、仕様作成の段階から三職種で意見を出し合いながら1つのプロダクトを作り上げていきます。 困った時には、施策の担当者だけではなくアプリチーム全体で相談し合っています。 先輩方にサポートいただきながら、期日内に仕様を満たす機能を作りあげる経験を積むことができました。 マイページ画面の担当したUIの一部↓ 左:Before、右:After ひょこっと顔を出しているホームズくんがかわいい❤️ 左:Before、右:After LIFULL引越しへ遷移できるようにしたり、機能ごとの見た目を刷新しました また、アプリチームでは職種関係なくアイディアを出して施策化できる機会もあります。 12月には、自らアイディアを起案し、リリースまで実現しました。 具体的には、条件を設定する際の「路線・駅から探す」画面内などにリセットボタンを設置しました。 このアイディアは、実際に自分で物件を探していて、駅をひとつずつ選択解除していくことに使いづらさを感じていたため発案しました。 発案後にすぐにリリースまで実施でき、自分の住まい探しにも大活躍しました! 画面右上にリセットボタンを設置 一度に画面内すべての選択状態を解除できるようにしました 1〜3月 年明けからは、さらに業務の幅が広がりました。 アプリ内で利用しているSDKやFrameworkに触れる機会があったり、ABテストを行う施策を担当したり、不動産会社への問い合わせに関わる施策に取り組みました。 これまでの業務で経験を積んできたおかげで、一人でできることが増えてきました。 また、今までは実装のレビューをしてもらう側でしたが、少しずつ他の方の実装のレビューにも挑戦しています。 初めは何をどのように確認すれば良いかがわからず、必ず見るべきポイントや余計な影響が出ないように意識すべき点などを先輩方に教えていただきながら取り組んでいます。 確認するポイントがわかってきたことで、実装のセルフレビューの精度向上にもつながっています。 さらに、現在のLHアプリではUIのほとんどがStoryboardで作成されていますが、SwiftUIの導入検証に挑戦する機会もいただいています。 自分の伸ばしたいスキルや目指すキャリアをもとに、上司と相談しながら次に何の業務を担当するかが決まるので、仕事のモチベーションも上がります! その他 メインの業務以外にも、iOSアプリエンジニア内で設計に関する参考書の輪読会や同期のエンジニアとセキュリティ・ネットワークに関する勉強会を行いました。 業務だけでは理解が追いつけなかったことの理解が深まったり、学んだ知識を業務にすぐに役立てられたりしています。 まとめ 入社からの1年間をざっくりと振り返ってみました。 これからエンジニアとして働くことに不安を感じている方々は、少しは安心できたでしょうか? 開発経験はほとんどありませんでしたが、iOSアプリ開発の基礎を習得するところからLHアプリの実装ができるようになり、さらに施策を1人で担当できるまでに成長しました。 ネイティブアプリエンジニアとしての経験を着実に積んでいる今なら、「iOSアプリ作ってます」「Swift書いてます」と言えます! アプリ開発を含む様々なことを勉強する機会と挑戦をサポートしてくださったLHアプリチームの皆さんには、深く感謝しています。 新卒2年目のこれからは、これまでよりも難易度の高い業務を担当していきます! エンジニアとしてさらに成長し、LHアプリの成長に貢献できるよう精進して参ります。 最後に、LIFULLでは共に成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは。エンジニアの北島です。普段は LIFULL HOME'S の売却査定領域 でエンジニアリングを担当しています。 今回は既存アプリケーションのパフォーマンス改善に、フロントエンドの観点から取り組んだ話をします。 経緯 弊社のサービスで プライスマップ という AI 査定による不動産価格を地図上で一気に見られるサービスがあります。 このサービスは 2015 年にローンチされ、当時は最新の技術を利用していたものの現在は老朽化が進んでいました。 私どもの部署では昨年このサービスの運用主幹となり、昨年度はバックエンドを弊社で運用しているコンテナオーケストレーション基盤 keel に載せ替え、インフラ改善を中心に取り組んで参りました。 今回は既存のアプリケーションのフロントエンドパフォーマンス改善を、Lighthouse を用いて行った話をしていこうと思います。 現状確認 このプライスマップにおいて、Google が検索エンジンにおけるサービスの評価指標としても採用している CoreWebVitals の各評価指標にも改善の余地がありました。 Google が無料で提供している Web サイトを分析・診断するための、Chrome 拡張機能 Lighthouse を使用し手元で診断してみたところ、 特にモバイルで低い評価でした。 引継ぎの時にサーバサイドのパフォーマンスチューニングを行っていたこともあり、サーバサイドレスポンスタイムに大きな問題はなく、フロントエンドの方に改善の余地がありました。 フロントエンドパフォーマンス改善に取り組んだ経験がなかったのですが、SEO 観点での指摘ということで事業的なインパクトも期待できるので、今回着手することに決めました。 具体的な対応内容 対応事項の整理 lighthouseスコア(改善前) Lighthouse のスコアを確認し、どれくらいの水準なのかを把握するために自社のほかのサービスと比べましたが、それらと比べても低い水準であることを確認しました。 経験の少ない自分としては情報は多い方がありがたく、自社のほかのサービスのスコアの方が上ということは自分にないノウハウが社内にあるはずだと考え、社内のエンジニアに help を出し情報を集めました。 その甲斐もあって、Lighthouse に直接指摘されている事項以外にも、パフォーマンス改善点を把握できました。 実際に対応した内容は以下の通りです。 text compression JavaScript の遅延読み込み css の@import 廃止 画像の Webp 化 text compression これが改善貢献度としては一番大きいものとなりました。 サーバからクライアントに静的ファイルを送信する際に圧縮した方が良いというものです。 静的なファイルなのできればアプリケーションから返すのではなく Contents Delivery Network を用いて実現したかったのですが。今回はアプリケーションから返す時に使用されるミドルウェアで gzip するという対応になりました。 一度用意すればあとはすべてのコンテンツが圧縮されて返されるので、それほど大がかりな対応なく効率的にパフォーマンスを改善できた点が良かったと思います。 JavaScript の遅延読み込み これは一番時間がかかった作業になります。 対応内容としては JavaScript の読み込みを遅延するというものです。 async/defer 属性を付与することによって対応すればよいのですが、遅延されていない既存のページをリグレッションなく遅延させるのは、一筋縄ではいきませんでした。 既存のページは html 読み込み時にインラインスクリプトでJavaScript も読み込まれているため、単純にJavaScriptファイルを遅延読み込みしてしまうと動作しないコードでした。 下記は該当のインラインスクリプトの一例です。 < script > namespace ( "PriceMap.constants.MAP_CONFIG" ) .zoom = 16 ; namespace ( "PriceMap.constants.MAP_CONFIG" ) .center = { lat: 35 . 4477777777778 , lng: 139 . 6425 , } ; </ script > lat と lng に実数を指定していますが、これらはテンプレートエンジンでは変数が指定されている箇所です。 テンプレートエンジンで変換する処理ですので、遅延読み込みすれば解決するような状態ではありませんでした。 まず最初に単に遅延読み込みを試しますが、js エラーが出ました。undefined のエラーだったのですが、まさに読み込み順の関係です。 テンプレートエンジン内に JavaScript が実装されていたので、こちらに関しても他ファイルに切り出したうえで遅延読み込みさせる必要がありました。 またテンプレートエンジン内に実装されていたコードはサーバサイドでの処理結果に応じて動的に JavaScript を作成する記述でした。 そのため、単にファイルとして切り出すのも難しい状況でした。 最終的には、3 段階で変更しました。 動的に変更になる、JavaScript 連携したいデータを data 属性で HTML に定義 インラインスクリプトを撤廃し、js ファイルに移築する JavaScript を遅延読み込み これらの変更はデータの渡り方や JavaScript の実行順が変更となる対応でしたので、テスト観点が多い作業となりました。 フロントエンドパフォーマンス改善の中で一番作業量が多かったかなと思います。 しかし作業の過程で既存の処理に関する理解が深まったので、ほかのリファクタリングも同時に行えたので、結果的には良い改善が行えたかなと考えています。 css の@import 廃止 これは社内の有識者からアドバイスいただいた項目で、css における@import が低速なので避けましょうということでした。 実際にこの記述はリポジトリ内には存在せず、アセットパイプラインを通して作成される記述のようでした。 結局 bootswatch-rails という gem が生成するものだと判明したのですが、ここまで見つけるのにかなり苦労しました。 生成される@import の行をソースコードに含む GitHub リポジトリのうち、プライスマップが使っているかもしれない gem をあたって見つけました。 原因が見つかった後はこの gem がどこでなぜ使われているのかを調査し、代替手段を模索していきました。 最終的にはこの gem を使用する必要はないということが分かり、使用しないように変更したうえでリグレッションテストを行うことで、無事脱却できました。 画像の Webp 化 静止画の配信方法に関しての指摘を、Webp による配信に変更することで解消しました。 サーバサイドで配信する前に png や JPEG などの画像を Webp に変換して配信するしくみを導入する方法もありますが、今回は直接元の画像を Webp に変更する方法を採りました。 変更には ffmpeg を使用し、リポジトリ内の特定の拡張子のファイルに対して一括で変換するスクリプトを使用しました。 #! /bin/sh array=`find src/app/assets/images/ -name *.png` for item in $array; do echo "ffmpeg -i ${item} ${item%.*}.webp" ffmpeg -i ${item} ${item%.*}.webp echo "rm ${item}" rm ${item} done この方法だとファイルの拡張子が変わるので、参照するコードに関しても置換する必要があります。 これは sed コマンドで一括置換を行い、コミットの際に 1 つ 1 つ差分を確認して進めていきました。 成果 最終的なパフォーマンス改善後のスコアはこのようになりました。 lighthouseスコア 少し分かりづらいですが、赤字の重要度の高い指摘について処理が短くなっており、改善していることがわかります。 一方指摘すべてを解消したわけではないので、改善の余地はまだまだありそうです。 良かったこと 指摘項目の解消を目指してリファクタリングするので実装方針を立てやすく、効果も見えやすいという点が非常に良かったです。 また指摘項目の解消を通してフロントエンドパフォーマンスの知識が身に着くので、自身の成長にもつながりました。 たいへんだったこと 既存の正常に動作しているシステムのリファクタリングですので、当たり前ですが既存のシステムの理解が重要でした。 中途半端な対応ですと既存システムがまったく動かなかったり、動くように対応しても細かい部分でリグレッションが起きたりなど、試行錯誤の連続でした。 その過程で既存のあまり詳細に意識していなかったところの構造理解が進んだり、細かい部分のサイト仕様を把握できたので学びの方が大きいのですが、それはそれとして作業としてはたいへんでした。 感想 lighthouse のパフォーマンス改善は、事業的な改善が見込め、エンジニアの成長にもつながると良いこと尽くめの印象で、ぜひ多くのエンジニアに経験してほしい体験だなと感じました。 フロントエンドエンジニアでなくとも、指摘の内容自体は広く一般的に分かりやすく説明されているものばかりですので、着手しやすいのではと思います。 まとめ 今回はフロントエンドのパフォーマンス改善に関して行ったことや、良かったこと、たいへんだったことを共有させていただきました。 もともと影響の多い text compression の指摘のみを解消すれば良い依頼だったのですが、指摘事項を確認するとほかにも改善できそうな点がいくつか見つかりました。 工数はかかりますが、いっそほかの指摘事項も対応できないか調査と対応をしても良いですか?と上長に確認を取ったところ、快く OK してもらえたのでこの作業が行えました。 対応しているときは仕様の決まっていないリファクタリングを行っている状態で、業務というよりは研究といった感触で楽しく進めることができました。 最終的にサイトのパフォーマンス評価も大幅に改善され企画サイドにも感謝されましたし、着手できてよかったなと考えています。 LIFULL ではこのようにともに成長していける仲間を募集しています。興味がわいた方はぜひともこちらのページもご覧ください。 hrmos.co hrmos.co
プロダクトエンジニアリング部の小林です。 2022年4月に新卒として入社し、アプリケーションエンジニアとしてバックエンドからフロントエンドまで幅広く開発を行っています。 この記事では、配属されたチームで取り組んだリファクタリングをどのように行ったか、その舞台裏について紹介します。 プロジェクトの技術選定と背景 技術選定 Clean Architecture x Next.js x TypeScript アプリケーション実行基盤: 内製ライブラリ「KEEL」 開発からリリースまで 理想の状態 リリース時の状態 リファクタ開始 リファクタの方針と準備 リファクタの手順 まとめ プロジェクトの技術選定と背景 技術選定 配属されたチームでは新規のtoC向けプロダクトの開発が始まっていました。 技術仕様は以下のとおりです。 アーキテクチャ: Clean Architecture フレームワーク: Next.js x TypeScript アプリケーション実行基盤: 内製ライブラリ「KEEL」 Clean Architecture x Next.js x TypeScript LIFULLでは3年前にリアーキテクティングプロジェクトが発足し、ソフトウェアアーキテクチャのベースにClean Architecture、言語にTypeScriptを採用し、新たなAPI(Backend For Frontend)を開発してきました。 新規開発でも基本的に同じ思想に基づいて設計を行います。 www.lifull.blog しかし今回はLIFULL HOME'Sの中心である物件情報を提供するシステムをリアーキテクティングする開発ではありません。 さらにモノレポで開発を行いたい事情があったため、Next.jsのAPI Routeで構築できるAPIをBackend For Frontendとして利用することにしました。 システム構成図 社内で先行してClean Architecture x TypeScriptでモノレポ開発をしているチームがあるため、多分に参考としています。 www.lifull.blog アプリケーション実行基盤: 内製ライブラリ「KEEL」 全社で利用している内製ライブラリ「KEEL」を利用しています。開発者がアプリケーション開発に集中できる素晴らしい基盤です。 詳細は下記の記事から御覧ください。 www.lifull.blog 開発からリリースまで チームではClean Architecture x TypeScriptなBFFの開発は初めてでした。 チャレンジングな開発としてアーキテクトグループによるレクチャーを受けながら動き出しており、配属から3ヵ月で最初のリリースが予定されていました。 私は開発経験こそあるものの、機械学習やゲーム開発がメインだったため、Web系の言語は入社してから触れています。 TypeScriptとClean Architecture、どちらも初めての勉強の毎日でしたが、エンドポイントの一部を開発する中で理解を深めることができました。 1つのエンドポイントを層と機能で分割することで開発タスクを分割できるのもClean Architectureの良いところですね。 さて、無事にリリースを迎え一安心かと思いきや… 私はリリース直前となったころ、大きな問題に気付きます。 「このプロダクト、Clean Architectureになっていない…!?」 理想の状態 理想の状態 Clean Architectureは層で役割を分け、依存関係を内向きにすることで外部に依存しない状態を理想とするものです。 Next.jsや外のAPIに依存するのは下の図で言うところの緑の層までにとどめ、ビジネスロジックは外部に依存しないように設計することで、APIやDB、フレームワークの仕様変更への対処が簡便になります。 なおかつドメイン知識を集約したクラスを作り、値オブジェクトとして利用することで入ってくる値をバリデーションする役割も担います。 リリース時の状態 リリース時の状態 一方、勉強しながら手を動かして実装していく中でアーキテクチャへの理解を深めることができ、その結果認識したプロダクトの実態は以下の通りです。 層ごとにファイルは分かれている ドメイン知識が集約されておらず、GatewayやUseCaseに分散している Next.jsの型に強く依存している (Next.jsのRequest型をGatewayまでトンネルしている) 社内APIに強く依存している (社内APIからのレスポンスをkeyとprimitiveな値をそのままにPresenterまでトンネルしている) つまり、ファイル数が多く複雑であるのに外部に強く依存したClean Architectureとは呼べないシステムになっていました。 簡単に言えば現状の社内APIを使うことを前提としたコードになっていて、いつの日か 社内APIが新しいものに置き換わったとき フロントを司るNext.jsが開発終了・破壊的変更をしたとき にこのプロダクトは7割方のコードを書き換える必要がある作りになっていました。 リファクタ開始 プロダクトの状態に気付いたものの、動作は正常であり、リリース予定日が目の前であったためそのままリリースすることになりました。 その前提で上長に状態と対策案を伝え、リファクタに工数を割くことにGOサインをもらいました。 リファクタの方針と準備 まずリファクタの方針と優先順位を定めました。 社内APIに対する依存を切る Next.jsとの依存を切る ドメインクラスを育成する基盤を作る その次に準備を行い、チームやアーキテクトグループの協力を仰ぎました。 アーキテクトグループとの相談 作業内容の切り分け、順序の設定、チケット化 チケットのレビュー (影響範囲を小さくしながら細かい粒度でアジャイル的に進めるため、確認は必須と考えました。) リファクタ実行 リファクタの手順 社内APIへの依存を切る (レスポンスを型変換する層を作る) APIからの戻り値に型をつけたInterfaceを作る RepositoryにUseCase層で使う値のInterfaceを作る APIから受け取った値をRepositoryに詰め直す処理を作る このとき、詰め替え元の方として1で作った型を使い、詰替え先の方として2で作成した型を使う Next.jsへの依存を切る ドメインを固める 以上の手順でリファクタを行いました。 私が学びながらの開発でアーキテクチャの理解が進んだからと言っても開発の経験は少ないので、まずはより良い状態を目指すためにアーキテクトグループの方にモブプログラミングをしてもらいました。 モブプログラミングの中でClean Architectureに頻出のTypeScriptの技法やリファクタ技法を学びました。 社内APIにアクセスするInteractorからGatewayまでの流れの一つをモブプログラミングとしてリファクタしたうえで、その学びを元に残りのコードを自らリファクタしました。 またリファクタと並行して ユニットテストの導入・API統合テストの導入 OpenAPI / Swaggerの実装 ESLintの設定の最適化 を行い、プロダクトの健全化を図りました。全体を通して開発者の心理的安全性がかなり高まったと感じています。 まとめ 今回、リファクタをすることで、Clean Architectureに関する知識を深めることができ、TypeScriptの型に対する理解が深まりました。 またリファクタで得た問題点と解決策や知見をチームに共有することで、並行していた開発が進んでいた新機能の品質も向上しました。 Clean Architectureに限らず、大規模なリファクタを行う際には 知見のあるスペシャリストに事前相談する チームに随時情報共有を行い、負債を作らないチームを目指す 自分の理解度が低い場合にはモブプログラミングも有効 ということを学べたことが最大の成果です。 今後も高品質で開発者体験の良いプロダクトを育てながら、ユーザーに価値を届ける基盤としていきたいと考えています。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
エンジニアの内藤です。LIFULL HOME'Sの売買領域を支えるエンジニアチームのマネジメントを担当しています。 弊社では「ネガポジミーティング」と称し、課題を出し合う「ネガティブミーティング」と課題の解決策を決める「ポジティブミーティング」をセットで実施しています。 前回「 ネガティブミーティング 」を実施し、今回はその対となる「ポジティブミーティング」を企画し実施したので、そちらを紹介したいと思います。 ネガポジミーティングとは ネガティブミーティングとは ポジティブミーティングとは ワークの流れ やってみた結果 まとめ ネガポジミーティングとは ネガティブミーティングとは ネガティブミーティングは課題だけを挙げるミーティングで解決策などは議論しない 課題を出すことだけに集中するため、普段だと言いづらい反対意見や代替意見も思い切って伝えることができる ポジティブミーティングとは ネガティブミーティングで出た課題の解決策を決めるミーティング 出た意見の良し悪しは評価せず、まずは提案自体に感謝する 実行する解決策を決めたら「誰が」「なにを」「いつまでに」を決める ワークの流れ 今回のワークでは前回と同じチームに分かれ、以下の流れに沿ってポジティブミーティングを実施しました。 ネガティブミーティングで出た課題から解決したい課題を選択する 解決案を出し尽くす 出た解決策をブラッシュアップして、具体的に「なにをするのか」決める 解決策やそこに至った過程を全体に共有 本来は「誰が」「いつまでに」も決めるべきなのですが、確保した時間では足りなそうでしたので解決策を決めるところまで実施することにしました。 ネガティブミーティングでは悲観的にならず笑顔で終えられるしくみとして、チーム内で最も共感された課題を川柳(または短歌)にまとめるような工夫をしました。 ポジティブミーティングではそのような配慮は不要だと考え、目的である課題解決に向けた実現性のある解決策が立案されることに注力することにしました。 具体的には自分たちで実行できる手段・実行するのに現実的なコストであり、すぐに動きだせる解決策が出せるように工夫をしました。 そこでロジックツリーを利用して課題の要素を分解しながら掘り下げ、その各要素に対する部分的な解決策を出すことで多くの解決案を出す方法で実施してみました。 やってみた結果 40分をワークの時間に充てましたが、ロジックツリーを利用して20ぐらいの要素に細分化するチームなどもあり解決策を出し尽くすには少し時間が足りなかったと思いました。 しかし、ブラッシュアップされた解決策はしっかりとエンジニア特有の視点で捉えられた実現性のある解決策となっていました。 例えば、「期間終了後のABテストが放置されがち」という課題に対して「AB実装時に本実装(AはrevertPR,Bの実装PR)を用意する」という解決策となりました。 「期間が空きすぎない方が実装が頭に残っているので本実装等の対応も早い」などエンジニアでないと気付き難い点も考慮した上での解決策の選択で、非常に有益なワークになったと感じました。 また、ロジックツリーによる要素の分解では、「本実装が計画に入っていない」「企画のモチベーション」など解決策が他者任せになりそうな要素もありましたが、期待通り自分たちで実行できる部分にフォーカスした解決策が選択されていました。 ロジックツリーの例 その他にも「俗人化しているプロダクトの開発効率が悪い」という課題に対して「知見共有会」「readmeを充実化」「できるところの自動化」などエンジニアらしい解決策が出ていました。 まとめ ロジックツリーを利用して課題を細分化することで、狙い通り自分たちで実行できる手段・実行するのに現実的なコストのすぐに動きだせる解決策を出せたワークになったと思います。 今回は解決策を決めるところまででしたが、「誰が」「いつまでに」も決めて実行していくことで課題を解決しより良い組織や職場環境にしていきたいと思います。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは。私はLIFULLで LIFULL HOME'S不動産査定 と ホームズマンション売却 の開発をしている新卒二年目の韓国人の開発者、ジョン ヨンソクです。入社当初に比べて、日本語によるコミュニケーションには慣れてきましたが、一方で成長が止まったのではないかと感じています。 そこで、これまでのやり方を振り返り、コミュニケーションに対する工夫や言語的な困難を克服するための方法を考え、さらなる成長を目指す話を共有したいと思います。 振り返り 基準を定める 捨てる要素 敬語 不安 重要な要素 相手を配慮 「結論 → 理由 → 事例」話法 課題 言語化能力の向上 日本語の専門用語を習得 まとめ PR 外国籍のお部屋探し 仲間募集 振り返り 日本語で仕事をするようになってから、ある程度の時間が経ち、徐々に慣れてきたと感じます。 年数が経過するにつれて、業務範囲が自然と広がり、より高度なコミュニケーション能力が求められる場面が増えてきました。また、今期からの状況の変化もあり、自身のコミュニケーション能力についてあらためて振り返ってみると、成長しているのかどうか疑問を感じました。 今までは、外国人としてコミュニケーション能力を上げるために、分からない専門用語とカタカナ語を暗記することに優先順位を置いてきました。入社当初は、この方法によってコミュニケーション能力が向上していることを感じられました。 しかし、このやり方のせいで私の中でコミュニケーションにおいて重要なことは完璧な日本語になっていました。チャットを送る際には、間違った日本語を使っていないかという心配で、正しい日本語を求めて調べることに時間を使うことが多かったです。結果的に円滑なコミュニケーションになっていないことに気付き、円滑なコミュニケーションになるための基準を定めました。 基準を定める 捨てる要素 敬語 敬語は、ある程度理解はしているつもりですが、正しい使い方に自信が持てないことが多くあります。 そのため、正確に区別して使用することは非常に難しく、文章を作るのに倍以上の時間がかかり、使用する際に不安を感じることがあります。 しかし、大切なのは円滑なコミュニケーションです。外国人という立場を活かし、少し間違っても良いという気持ちで、すばやく回答することを優先することにしました。 敬語は、現在持っている言語能力を十分に活用するために捨てる要素であり、この問題は今後解決すべき課題です。 不安 日本語を間違って使っているかもしれないという不安があります。この不安をなくすため、完璧な文章を作ろうとする傾向があります。チャットで何か文章を送信する際にも、何度も調べたり修正したりして時間がかかってしまうことがあります。 しかし、口頭で話すときは、不安を感じる暇もなく会話が流れます。口頭での会話のように、チャットでも不安を感じる暇を与えずに、いったん文章を送信することを意識することにしました。 一方で、内容がめちゃくちゃになることもありますが、それでも不安をなくすことが目的ですので、この問題も今後解決すべき課題です。 重要な要素 相手を配慮 捨てる要素が2つあるため、つい不適切な表現をしてしまうことがあります。そのため、お互いに気持ちよいコミュニケーションのためには、相手に配慮する心構えが最も重要だと考えます。 たとえば、コードレビューの際には、相手のコードを尊重し、ネガティブなコメントは避けるように心がけます。また、修正が必要な場合は、命令形ではなく提案形で伝えるようにしています。"いかがでしょうか"という表現をよく使います。 一方で、相手を配慮することが重要ですが、やり過ぎると不明瞭な表現になってしまうことがあります。明確な伝え方が必要な場合もありますので、過去の私の表現を振り返り、良い例を探して改善していく努力が欠かせません。 「結論 → 理由 → 事例」話法 この話法は、文書やプレゼンテーションなどで重要な内容を効果的に伝えるために役立ちます。 まず、結論を述べることで聞く人が主要な内容をすばやく理解でき、その後に理由や背景を説明することで聞く人がなぜその結論が導かれたのかを理解できます。 最後に、具体例を挙げて内容を具体化することで、聞く人がより明確に理解できます。 ただし、この方法が常に最適なわけではありません。たとえば、長い話をする場合には、順番に話す方が適しているかもしれません。また、状況によっては詳細が必要な場合もありますので、そのような場合には、より詳細を含めて話す方が適切かもしれません。 したがって、これらの方法は状況に応じて適切に使用する必要があります。しかし、通常の状況では、この方法がコミュニケーションに効果的であることが多いため、私もこの方法で話すことが多いです。 課題 コミュニケーションにおいて基準を定めて、実行しました。結果、チャットでのコミュニケーションは以前に比べて迷いも少なく、スムーズになったと感じています。 ただし、リアルタイムで反応が必要な会議などのコミュニケーションにおいては、理解が遅れたり、発言する機会を逸したりするなどの課題が残っています。これらは今後克服しなければならない課題であり、そのために2つの方向性を考えました。 言語化能力の向上 1つ目は言語化を練習することです。 私の意思を迅速に整理し、明確に伝える能力を身につけるためには、言語表現の練習が必要だと考えました。 私は個人ブログを開設し、韓国語で何でも書く練習を始めました。言語表現の練習が目的であれば、母国語であっても問題ないと思います。もちろん、慣れてくると日本語で作文することもできるようになるでしょう。 日本語の専門用語を習得 1年間で、技術的に足りない部分をAWSとLinux関連の勉強で補い、資格を取得しました。試験はすべて日本語で受けました。理由は、日本語の専門用語が理解できない時も多い一方、どのように勉強すれば良いか悩んでいたため、この機会に習得できると考えました。 業務に関連する勉強をする際には、日本語で並行して勉強することで、専門用語の習得につながると考えました。次に取り組むアクションとして、年に1冊の専門書籍を日本語で読んで文章で整理することを考えています。 まとめ 外国人である私が日本語によるコミュニケーションに慣れるため、これまでのやり方を振り返り、コミュニケーションへの工夫をしました。 具体的には、完璧な日本語にこだわらず、相手に配慮しながらも明確な表現をすることを心がけ、迅速な回答をすることを優先するようにしました。 私が定めた基準を実行するためには、自分が置かれている環境も重要だと考えます。LIFULLでは、率直で建設的なコミュニケーションを重視し、誰かが成長のための行動に対しては支援を惜しまない企業文化があります。 何かを実行する際に躊躇することなく、行動できる企業文化は 実際の業務 を通じて実感できました。そのため、本当は欠かせない要素たちを捨て、行動するなどの取り組みが試しやすかったと思います。 今後も、言語化能力の向上と日本語の専門用語の習得を目指し、今後克服すべき課題として取り組んでいくつもりです。 PR 外国籍のお部屋探し 一人で部屋を探す際、外国籍であることが理由で何度か断られた経験があります。外国籍などの普通でないバックグラウンドがあると、偏見を持たれることがあるのだと感じました。 そんな方々に向けて、さまざまなバックグラウンドの方に理解があり“ありのまま”住まい探しの相談ができる不動産会社検索サイト「LIFULL HOME'S FRIENDLY DOOR」を紹介します。 お部屋探しの際は、ぜひご利用ください。 actionforall.homes.co.jp 仲間募集 最後に、LIFULL では一緒に働く仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは。エンジニアの渡邉です。普段はLIFULL HOME'Sの売買領域のエンジニアチームにて開発を担当しています。好きなApache SolrのAPIはJSON Facet APIです。 今回はLIFULL HOME'Sの画像変換サーバの構成を見直し、アプリケーションの再構築並びに基盤刷新を行い、コスト面、性能面、運用面での改善を行った話をさせていただきます。 画像変換サーバの特性 開発における課題 課題1: 開発しづらいアプリケーション 課題2: インフラリソースの最適化の余地の向上 実際にやったこと 実際の画像変換サーバを読み解く コンテナベースに置き換える 実行基盤をEC2からKubernetesに変更する 負荷テストの実施 スケール戦略の変更 カナリアリリースの実施 成果 開発速度の向上 運用コストの軽減 レスポンス速度の向上 既存の構成・知識の集約 まとめ 画像変換サーバの特性 弊社ではLIFULL HOME'Sを始め、さまざまなアプリケーションから画像変換サーバを利用しています。 今回移行した画像変換サーバは、nginx用の動的画像モジュールの ngx_small_light を採用した自作のアプリケーションとなっています。 主な役割について簡単に説明すると、呼び出し元のアプリケーションが画像を取得する際に画像変換サーバは画像のサイズと画質を動的に変換し、アプリケーションに返却する役割を担っています。 常に数千RPSを受け取り、繁忙期にはさらに多くのリクエストが増えます。 開発における課題 もともと画像変換サーバを移行するきっかけになったのは私がLIFULL HOME'Sの画像の最適化を行いたいと思ったのがきっかけでした。 ユーザーに対して品質のよい画像を届けたり優れた体験のために読み込み速度を改善したいというモチベーションで改善を行いたいと思っていました。 LIFULL HOME'Sでは物件の内観や外観の画像を始め、ほとんどのページで画像を画像変換サーバから取得しています。 LIFULL HOME'Sのような不動産情報ポータルサイトにおける画像の重要性は非常に高く、画像配信を行うアプリケーションに機能改修を行うことでサイト全体の大幅なパワーアップを行うことができます。 しかしながらそれを実現するにあたりさまざまな課題を感じ、今のまま改修をするのは効率が良くないと思い、開発体験の向上を行う手段を講じることにしました。 開発体験の向上を行うにあたり、システム刷新をするかアプリケーションの新基盤刷新を行うかを天秤に取ったところ、画像変換サーバを利用するアプリケーションが多いことから影響範囲の広さと実装コストなどさまざまな要因から検討を重ねた結果、今回はアプリケーションの新基盤移行を行うことにしました。 課題1: 開発しづらいアプリケーション 既存の画像変換サーバは、アプリケーション開発者にとって改修しづらいアプリケーションになっていました。 その要因は大きく分けて以下の2点です。 運用をしているチームがインフラ開発チームであること アプリケーションの検証環境を気軽に用意できないこと 画像変換サーバは大量のインスタンスにより動作するアプリケーションであり、インフラチームの管轄として運用されていました。 そのためアプリケーション開発者には制限が多く、 検証サーバの作成やデプロイなどにも手間がかかり画像変換サーバを改修することが容易ではありませんでした。 また、開発にはインフラチームに検証環境を用意してもらう必要があり、すぐに検証を行うことができないことも開発速度を遅くしていました。 したがって、開発者が手元のマシンで気軽にアプリケーションを立ち上げるしくみを用意する必要がありました。 課題2: インフラリソースの最適化の余地の向上 画像変換サーバに対して圧縮率の変更やフォーマットのWebP化などの機能改修を行いたいモチベーションがあるのはアプリケーション開発者にあることが多いです。 また、インフラリソースなどの変更に対する権限もアプリケーションの特性を把握しやすいアプリケーション開発者が持つことによるメリットは多いと思います。 しかしながら、現状の組織運用体制ではアプリケーション開発者に検証と調整を気軽に行うことができませんでした。 したがって、アプリケーション開発者に自由にインフラリソースを変更する権限を与えられるようにすることが必要だと考えていました。 また、もともとの画像変換サーバはEC2インスタンスを非常に多く起動させるアプリケーションであることからコスト面での課題を抱えていました。 弊社のアプリケーションのほとんどが画像変換サーバを利用していることから、サーバがダウンしてしまうことに対するリスクは非常に大きいです。 そのため唐突なスパイクリクエストが来ても問題なくアプリケーションが動作できるだけの運用台数を用意する必要がありました。 大きめのEC2インスタンスを数十台で運用し、高コストでの運用が続いていました。 実際にやったこと 上記に挙げた課題を解決するためにやったことをセクションベースで記載していきます。 実際の画像変換サーバを読み解く 画像変換サーバを刷新することを決めたものの、その状況は芳しくないものでした。 というのも古いアプリケーションであり機能改修が少なかったこともあり、実際の運用やアプリケーションについての特性を正しく把握している人が社内に存在していませんでした。 したがって実際のデプロイフローやnginxの内容から何をしているアプリケーションなのかやデプロイに必要な構成要素を洗い出す必要がありました。 まずデプロイ内容やインフラ構成については、既存の画像変換サーバはリポジトリ管理等もされておらず、リリースに必要なものが各所に点在している状況でした。 ですので、当時デプロイ運用で利用されていたAnsible playbookとCloudFormationの内容を読み解き、デプロイに必要な要素を一つ一つ洗い出しつつまとめていきました。 またアプリケーションを理解している人もいない状況でした。 そこで、インフラチームに既存と同一のインスタンスを準備してもらった上で、nginxの内容を読み解きつつ必要なモジュールの確認や挙動の確認を進めました。 コンテナベースに置き換える 大まかな構成要素が洗い出されましたので、アプリケーションを構成し直しました。 もともと課題にあげた通り、開発者が気軽に検証環境を手元に用意できないという課題を解決するために、コンテナ化をすることにしました。 基本的なアプリケーションの構成要素やデプロイに必要なものはAnsible playbook等の内容に記載されていたので、それにしたがってコンテナ化を進めていきました。 そのタイミングで利用しているソフトウェアやBase Imageなども極力アップデートしています。 実行基盤をEC2からKubernetesに変更する 課題2に挙げた通り、アプリケーション開発者がインフラリソースを変更できるようにしたいという希望がありました。 それを実現する具体的なアプローチとして、実行基盤をKubernetesに移行することとしました。 弊社では全社的にインフラ環境をKEELという独自のKubernetesベースのアプリケーション実行基盤に移行していくことになっています。 KEELについてはこちらのエントリに記載されています。 https://www.lifull.blog/entry/2020/12/02/000000 もともとEC2で運用をされていたアプリケーションをKubernetes環境であるKEELに移行することは今回の課題であるアプリケーション開発者がインフラリソースを容易に調整をできることや検証環境を容易に作成できる点に対する明確かつ有効的なアプローチであることからKEELの実行基盤への移行を決定し、既存アプリケーションのコンテナ化を実施したうえで、KEEL環境へデプロイする形にしました。 負荷テストの実施 コンテナ化とKubernetes移行が完成したタイミングで負荷検証を実施しています。 既存の画像変換サーバが大量にリクエストされるアプリケーションであり、さまざまなアプリケーションから使われている都合上、アプリケーションの切り替えには無停止での入れ替えをする必要がありました。 したがって、既存のリクエストがさばけるように長期間にかけて、多種多様のリクエストを想定したシナリオを用意し安定してさばくことができるリソースの調整を行いました。 具体的には以下の手順にで負荷検証を実施しました。 1台のpodがさばける限界のリクエスト量を測定 一台あたりのスループットを確認 1.で求めた最大のリクエスト量の2倍を2台のpodでさばくことができるかを検証 podが増えたことで線形にスループットが増えることを検証 スケールアウトする閾値の検証 今回はcpuに負荷のかかるアプリケーションであったため、2.で実行したリクエスト量を安定してさばける台数とスケールアウトするタイミングを決定 実際に必要なpodの数を検証 3.でもとめたリクエスト量を安定してさばくことができるpodの台数から実際のリクエストを受け取る場合に必要な台数を求めて必要なpodの台数を決定 実際にプロダクトインしたケースを踏まえてsoakテスト(長時間のリクエストテスト)の実施 長時間リクエストを受け続けてもアプリケーションに問題がないことを確認 スケール戦略の変更 今回の移行時にスケールアアップ+スケールアウトの両軸で運用していた画像変換サーバをスケールアウトに寄せる形で運用しなおしました。 Kubernetes環境になったことで大きく変わったことがいくつかありますが、その一つがスケールアウトの時間が大幅に削減したことでした。 具体的な数字を挙げると、既存の画像変換サーバがスケールアウトするのには1分から2分必要だったところKubernetes環境化にすることで、15秒ほどにまで削減することが可能になりました。 それにより、スケールアウトに対する比重を非常に高く持つことができるようになりました。 したがって、サーバ1台に対してリソースの余裕を持たせる割合が少なくなり、サーバ1台あたりのリソースを削減できることができました。 また、既存の画像変換サーバは大きめのインスタンスをある程度準備している状況を常態化させる形にしていました。 したがって、朝方などのリクエストの少ない時間にリソースが余る状況になってしまっていたことも多々ありました。 そこで、一台あたりのリソースを極限に少なくし大量のサーバでさばく戦略に変えることで、リクエストの数に応じたリソース調整を行える形にすることでリソースに対する無駄を排除することにしました。 カナリアリリースの実施 今回の画像変換サーバは数多くのアプリケーションから利用されているアプリケーションであるため、無停止での移行をする必要があったので、それを行う手法としてカナリアリリースを実施しています。 約二週間の期間をかけて徐々に割合を上げていく形で切り替えていきました。 成果 上記に挙げたやったことを実施したことでいくつか成果につながることができましたので紹介します。 開発速度の向上 コンテナ化やKubernetes環境に移行したことで、自前で開発用のサーバを気軽に用意したり検証できるようになったので、今までに比べて格段に開発しやすくなりました。 そのわかりやすい例として、もともと私が基盤移行をする前に画像変換サーバに対して行いたかった改修の一つとして、画像の画質変更をするというものがありました。 内容としてはそこまで工数がかかるものではなかったのですが、既存の画像変換サーバに機能改修をすると他グループの作業にも依存するため検証環境の構築待ちなども発生しおおよそ2週間ほど時間を要してしまうことになるところ、検証まで含めて一日でリリースできるようになりました。 非常に検証が行いやすくなったことやアプリケーション開発者側にデプロイの権限が移ったことにより開発速度の圧倒的な向上を行うことができたのは機能開発に対するコストの兼ね合いからかなり大きな成果でした。 運用コストの軽減 nginxのチューニングやスケールアウト戦略に変更したことで非常にリソースを抑えることに成功しました。 リソースを縮小させた主な要因は以下のようなことが挙げられます。 pod一台あたりのリソースの縮小化 スケールアップ戦略からスケールアウト戦略への変更 一台あたりがさばけるリクエストの数を増加させる その結果、もともと大きめのEC2インスタンスを数十台で運用していたため高コストで運用していた画像変換サーバを約1/3のコストで運用することに成功しました。 レスポンス速度の向上 アプリケーションの構成を入れ替えの実施や、nginx-exporterを導入したうえでworker数の最適化を行うなどの改善をしたことにより、レスポンス速度にも大きな改善が見られました。 具体的な数値としては、平均レスポンスタイムは230msec → 110msecほどまでに短縮でき、アプリケーション全体のパフォーマンスを向上させることにつながっています。 既存の構成・知識の集約 GitHubリポジトリを今回新規に用意し、リリースに必要なものをすべて集約しました。 画像サーバはもともとリポジトリ管理されているアプリケーションではありませんでした。 したがって、アプリケーションの構成に必要なファイルやスクリプトが一ヵ所に集約されておらず、リリースに必要なものがすぐにはわからないという状態でした。 それを今回のタイミングでリポジトリ管理したことにより見るべき場所を単一化できたほか、機能改修のバージョニングを行うことができたので開発者目線で運用しやすいプロダクトになりました。 まとめ 今回はさまざまな要因から開発しづらいアプリケーションだった画像変換サーバを再構築並びに新規実行基盤に移し替えたお話をさせていただきました。 レガシーなシステムであったり、運用体制であったりと開発工数や調査工数が嵩んでしまうことというのはいたってめづらしい話しではないかと思います。 そのような場合にはたいへんだなーで終わらせず現状の課題を洗い出し、エンジニア的アプローチで改善をしてみることで、未来へつながるアプリケーションにできるかもしれません。 今回はその手法として基盤刷新をするというものでしたが、基盤刷新に限った話ではないと思いますので、さまざまなアプローチを視野に入れてアプリケーションの改善してはいかがでしょうか。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは!クリエイターの日運営委員の工藤です。 社内で熱海をフィールドとしたハッカソンを開催しました。 ハッカソンとは、ハックとマラソンを組み合わせた造語で1日から数日でエンジニアやデザイナー、プランナーでサービスのアイデアを考え開発するイベントです。 LIFULLは社会課題の解決を目指している企業であるため、実際に現地の課題を見に行ってサービスを考えるということでより課題に目を向けてサービスの開発ができるようになると考え開催しました。 今回LIFULLが運営しているLivingAnywhere Commons(LAC)が熱海に拠点があるため熱海をフィールドとしてハッカソンを行いました。テーマは「LAC熱海をさらに盛り上げるには」で、このLACの熱海拠点を盛り上げるサービスを各チームに分かれて考え開発しました。 livinganywherecommons.com ハッカソンの流れ 今回のハッカソンは1泊2日で行いました。 熱海に集合してそこからみんなでLAC熱海に移動し、熱海の現状や課題などを聞きました。 その後、チームに分かれて熱海の街のフィールドワークを行いました。実際に熱海の街を歩いたり、LAC熱海のコミュニティマネージャーに質問したり実際にある課題を調べ深堀りしていきました。 各チーム解決したい課題が見えてきたら徐々に開発に移っていきました。この写真のようなLAC熱海の宿泊施設やコワーキングスペースなどで各チームに分かれて開発しています。 成果発表 最後に2日間で開発したサービスの発表を行いました。 各チームが開発したものを簡単に紹介します。 まずはチーム「熱海の空」の「KYOU〜経由地〜」です。 こちらのチームは企画1名、エンジニア3名のチームです このサービスは、LAC周辺にいる利用者とお店の位置を表示することにより、LACユーザーと地元の方々を繋げることを目的としたものです。このアイデアは、LACで配布されている「まちあるきマップ」という地図を使って地元のお店を訪れる際、その地図をきっかけにコミュニケーションが生まれることからインスパイアされています。 続いてチーム「ムシメガネ」の「musubi」です。 こちらのチームは企画1名、デザイナー1名、エンジニア2名のチームです。 このサービスは、LAC熱海にチェックインする際にプロフィールと興味あること、話したいことを登録することで同じくLAC熱海に泊まってるユーザと話す約束ができるサービスです。これは、LAC熱海はコミュニケーションを大事にしているものの建物が3つあるためコミュニケーションが取りにくい課題に注目しました。 さいごに どちらのチームもエンジニアだけでなく、様々な職種でチームが構成されそれそれの得意分野をいかしてサービスの開発を行いました。 実際に課題のある現場を見て、生の声を聞くことでよりユーザのことを考えたサービスの開発ができたと思います。これは普段の業務でもとても重要なことなので今後の業務にもいかせたらと思います。 LIFULLでは今後もこのような社会課題などに向き合ったサービスの開発を続けていきます。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
はじめまして、テクノロジー本部の布川です。そろそろ入社して一年が過ぎようとしています。 本記事ではLIFULL HOME'Sのページ高速化PJの一環として行われた、マイクロサービス間のhttp keepaliveを自前で有効化する取り組みについてお話しさせていただきます。 同じように高速化を行おうとしている方々の参考になればとても幸いです。 背景と目的 プラットフォームGと高速化について 2021年6月にGoogleがWebサイトの性能指標としてCore Web Vitalsを導入してから、サイトの読み込みや操作に対する応答の速度、レイアウトの安定性といった指標がSEOに影響を与えるようになりました。私の所属するプラットフォームGの高速化チームは、LIFULL HOME'Sの主要なページのパフォーマンスを監視しながら、主にサイトの読み込み速度の改善に取り組んでいます。 サイトの読み込み速度の改善方法は、大きくフロントエンドからのアプローチとバックエンドからのアプローチの二種類に分かれます。特に後者について挙げられる手段は、システムの構成や使われている言語やフレームワークなどのさまざまな条件に左右されるため多岐に渡りますが、どれも「できるだけ早くブラウザにレスポンスを返す」ことに繋がっています。 今回の話は、バックエンドからのアプローチに寄ったものになります。 LIFULLにおけるパフォーマンス改善に向けた取り組み 弊社の提供する不動産ポータルサイトであるLIFULL HOME'Sは、KEELと呼ばれるkubernetesベースのアプリケーション実行基盤の上で連携する複数のコンポーネントによって構成されています。システムを構成する要素が複数ある場合はボトルネックの特定が難しくなるため、横断的なパフォーマンス改善が難しくなってしまう側面がありますが、KEELがLIFULL HOME'Sに向けて提供しているログの横断検索機能やリクエストの分散トレーシング機能を活用することで、効率の良い改善活動を行うことができています。 www.lifull.blog KEELの上ではLIFULL HOME'Sに直接関係するサービスの他にもたくさんのアプリケーションが動いており、その一つ一つにパフォーマンス改善に関わる機能以外にも、自動デプロイフローやサービスメッシュ、セキュリティなど運用上必要になるさまざまなものが提供されています。 ボトルネックの発見 KEELでは、サービスメッシュとして導入しているistioが各マイクロサービスに付加するistio-proxyによって、サービスが通信時に必要とするトラフィックの管理などに関する機能を提供しています。しかしリクエストを直列に処理するモデルの関係上、一部のマイクロサービスではパフォーマンスの観点からistio-proxyの付与を無効にして、直接相手のサーバとやり取りしていることがあります。 LIFULL HOME'Sではフロントサーバの裏でAPIサーバやページ毎のリソースを集約するBFFが動いていますが、現状これらのサーバではistio-proxyが無効化されています。そのためこれらのサーバ間で通信を行う際は、リクエストのたびに接続を張り直したりリトライ処理が正しく行われていなかったりといった問題があり、特に都度接続を張り直すことに関してはサイトの読み込み速度の低下に繋がっていることが考えられました。そこで、httpクライアントの実装やサーバ側の設定を適切に行うことで、istio-proxyの無効化により失われていた機能のうちhttp keepaliveやリトライ機能を自前で有効化することにしました。 課題と対応 マイクロサービス間のhttp keepaliveを自前で有効化する(=リクエスト/レスポンスのConnectionヘッダにkeep-aliveを指定する)にあたり、解決すべき課題がいくつかありました。 内外のコンポーネントへの影響調査 コネクションプールの作成 スケーリング時の対応 その中でも主な課題について、それぞれの詳細と行った対応について説明していきます。 内外のコンポーネントへの影響調査 http keepaliveを有効化するとクライアント-サーバ間で一度張られたtcp接続は一定期間保持されるようになりますが、この時ある時間帯に張られる接続の数は減少するものの、あるタイミングで同時に張られる接続の数は増加します。そのため、サーバが同時管理する接続数が現状からどの程度増え、それが許容できる範囲なのか否かを調査しました。 その結果、APIサーバで一つのpodに対して同時に張られる可能性のある接続の数と比較して、それぞれのpodにいるnginxが管理できる接続数に十分余裕があることを確認しました。 また、APIサーバに直接接続してくる他のサービスのhttpクライアントが、APIサーバ側のhttp keepaliveが有効な場合は接続を使い回すようになるのか、その際接続が切れたら繋ぎ直すなどの必要な処理を正しく行えているのかといった他サービスの調査も念の為行いました。 実際、istio-proxyを無効にしてAPIサーバに直接アクセスしてくるマイクロサービスは他に2つほどあり、それぞれのhttpクライアントはnode(v14), python(v3.7)のものを使っていましたが、以下のように問題がないことを確認しました。 node(v14): デフォルトでkeepalive無効( 参考 ) python(v3.7): デフォルトでkeepalive有効、切断時のハンドリングは正しく行われている( 参考 ) コネクションプールの作成 先ほどの図のようにBFFにおいては一つのpodの中で複数のpassanger worker processが動いており、それぞれのworker processがフロントエンドサーバから受け取った一つのリクエストを処理しています。このworker processがスレッド並列でAPIサーバにリクエストを投げる際に張る接続を、http keepaliveによって一定期間保持して使い回そうとしています。 この時、あるworker processがAPIサーバに対して逐次的なリクエストしか行わないのであれば、worker process毎に保持する接続は一つで事足ります。しかし、worker processがAPIサーバに対してスレッド並列的にリクエストを投げる場合、worker process毎に複数の接続をスレッドセーフな状態で保持する必要があります。 これについては、worker process毎に一つのコネクションプールを保持し、リクエストを投げる際はプール内の使用可能な接続を使い回せるようにすることで解決しました。BFF上で動くアプリケーションはrubyによって記述されていますが、rubyの標準的なhttpクライアントであるnet/httpはコネクションプールの機構を持たないため、新しく Gem を導入して実装を行いました。 スケーリング時の対応 現在のkubernetesの運用では、事前に定義したHorizontal Autoscaling Policyに基づいて15秒毎にスケールするか否かの判断を行います。この時、APIサーバのスケールインによって接続先のホストがいなくなった場合やスケールアウトによって接続可能なホストが増えた場合のハンドリングを、BFF上のアプリケーションで正しく行う必要がありました。 スケールイン: APIサーバ側のpod数が減少するとBFF側で保持しているいくつかの接続では通信相手がいなくなるため、繋ぎ直しを行う必要があります。net/httpはあるtcp接続を使い回すにあたり自分もしくは相手からの終了処理によってソケットが閉じられていた際は次回使用時に自動的に再接続を行うため、この仕様とkubernetesのルーティング機能によってまだ生きているAPIサーバのpodと接続を張り直すことができました。 スケールアウト: APIサーバ側のpod数が増加しても、BFF側で新しく増えたpodに対して一部の接続を張り直すよう能動的に行動を起こすことはできません。また、kubernetesのデフォルトの通信機構ではkube-proxyによってラウンドロビンなリクエストの振り分けが行われているため、そのままでは新しく生成されたpodへの接続数が少なく偏ってしまいます。この問題についてはAPIサーバ側でkeepalive timeoutをスケール頻度である15秒に調整することで、BFFが定期的に接続を張り直せるようにしました。こうすることによって定期的な接続のバランシングが行われ、APIサーバの一つのpodあたりの接続数はほぼ均等に保たれます。 改善の様子 以上の取り組みによってistio-proxyが無効なマイクロサービス間のhttp keepaliveを有効化することができ、以下のように接続の張り直しがリクエストの度に行われなくなりました。 これによって、特に多くのリソースを要求するページにおけるサーバーレスポンスタイムが以下のように改善しました。 上のグラフは開発環境で各条件ごとに調査した結果ですが、本番環境でも同ページのリリース前日とリリース翌日の24時間のサーバーレスポンスタイムの平均を比較すると、34ms程の改善が見られました。程度に差はあれど裏側でリソースを要求するLIFULL HOME'Sの全ページが今回の施策の恩恵を受けることになります。 また、直接的なパフォーマンスの改善の他にも、active connection数の低減などといった効果が見られました。 まとめ マイクロサービス間のhttp keepaliveを自前で有効化する取り組みについて、接続を使い回すことに伴ういろいろな問題に対処しながら実装を行い、パフォーマンスの改善に繋げたお話でした。ここまで読んでいただきありがとうございました。 今回の施策によって生まれた改善は小さいように見えますが、このような小さな改善を積み重ねる過程でノウハウを蓄積させ、次の改善やパフォーマンスの劣化防止に繋げていくことが重要だと感じます。また、タスクの中ではkubernetesがいかに多くのことをやってくれているかや、インフラやミドルウェアに対する理解が高速化の文脈でも重要になってくることを感じました。 社内の様々な人に助けてもらいながらリリースに漕ぎ着けたこちらのタスクですが、今後は今回の経験と反省をもとに更なるパフォーマンス改善に取り組みSEOに強く良い体験のできるサイトに近付けて行ければと思います。 最後に、LIFULLでは共に成長できるような仲間を募っています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
フロントエンドエンジニアの嶌田です。株式会社 LIFULL でプロダクトのアクセシビリティ向上をミッションとして活動しています。 本日は、不動産・住宅情報の総合サービスである LIFULL HOME'S のスマートフォンサイト において、過去半年間で実施したアクセシビリティ向上施策をご紹介します。ご紹介する施策のうちいくつかは、内容を掘り下げて実装コードを交えて解説をしていきます。 それでは、早速アクセシビリティ向上のために実施した施策を見ていきましょう。 ボタンを正しくボタンにする 追加コンテンツを読み込む機能のフォーカス管理 チェックボックスに適切な名前を付ける カルーセルをアクセシブルにする スクリーンリーダーによる検索結果の件数の読み上げ そのほかの改善点 おわりに ボタンを正しくボタンにする ウェブサイトにおいて、ユーザーがアクションを実行するためにボタンが用いられます。ボタンは通常 button 要素を使って実装するのが望ましいですが、適切ではない要素(例えば a 要素、 span 要素、 p 要素など)を使って実装された箇所が数多くありました。これでは キーボード操作ができず、スクリーンリーダーにも「ボタン」であることが伝わりません 。そこで、私たちはこれらの要素を正しくボタンにし、キーボード操作が可能になるよう改善を行いました。 p 要素、 dt 要素、 a 要素、 span 要素など、様々な要素で実装されたボタンたち button 要素に差し替えるのが王道です。とはいえ、 button 要素がデフォルトで持っているスタイルのリセットや、要素セレクタに依存した JS の処理の存在を考慮すると、要素の書き換えによって予期しない不具合を呼び込んでしまうかもしれません。ノーリスクかつ最小工数を目指したかった私たちは、WAI-ARIA を使って役割を上書きすることにしました。 次のような HTML があったとします。 < p class = "load-more" > もっと見る </ p > p 要素はデフォルトで paragraph の役割を持っています 1 。ボタンではありません。ボタンであることを示すために、 role 属性を使って役割を上書きします。 < p class = "load-more" role = "button" > もっと見る </ p > 次に、キーボードで操作できるようにします。このボタンはマウスクリックで機能が実行されるようになっていますが、キーボードフォーカスが当たらないためキーボードのみで操作できません。 tabindex="0" を設定すると、通常のインタラクティブ要素( a 要素やフォーム関連要素など)のようにフォーカスを受取るようになります。 < p class = "load-more" role = "button" tabindex = "0" > もっと見る </ p > これで問題が解決したと思いきや、キーボード操作を試すと、元々の機能が実行されないことに気付きます。 button 要素や a 要素でないと、Enter キーや Space キーが押されたときに click イベントが発火しないことが原因です。JavaScript を使ってここの穴を埋めてあげましょう。コード例 2 は Stimulus です。 < p class = "load-more" role = "button" tabindex = "0" data -controller= "button" data - action = "keydown->button#keyboardClick" > もっと見る </ p > // button_controller.js import { Controller } from '@hotwired/stimulus' ; export default class extends Controller { keyboardClick( event ) { if ( [ 'Enter' , ' ' ] .includes( event .key)) { event .preventDefault(); event .stopPropagation(); this .element.click(); } } } 以上の対応により、p 要素がボタンとしての意味と振る舞いを持つようになりました。さらに、HTML の構造が変わらないため、スタイルの調整は不要であり、既存のイベントハンドラを修正する必要もありません。 追加コンテンツを読み込む機能のフォーカス管理 LIFULL HOME'S では、クリックにより追加コンテンツが表示される UI パターンがよく使われています。このパターンでは、ボタンをクリックするとボタンが消え、追加コンテンツが表示されます。このボタンが消える特徴は、ディスクロージャー 3 と呼ばれる UI パターンとはやや異なります。 ボタンをクリックするとボタンは消え、代わりに追加のコンテンツが表示される この部分にアクセシビリティ上の問題がありました。 ボタンを押すと同時にボタンが消え、同時にキーボードフォーカスが失われてしまう のです。フォーカスの位置がわからなくなり、キーボード操作を行うユーザーにとって不便な状況が生じます。この問題を解決するために、 ボタンをクリックしたときに表示されるコンテンツにフォーカスを当てる ように改善を行いました。 次の HTML コードを想定します。追加で読み込まれるコンテンツには hidden クラスが付いています。CSS で .hidden { display: none } が指定されていると想定してください。 < div > < a href = "..." > 敷金礼金0(ゼロ・なし)の物件 </ a > < a href = "..." > 新築・築浅物件 </ a > </ div > < button class = "load-more" type = "button" > もっと見る </ button > < div class = "hidden" > < a href = "..." > 二人暮らし物件 </ a > < a href = "..." > タワーマンション(高層マンション) </ a > </ div > 追加コンテンツの表示には hidden クラスを取り除き、ボタンを非表示にするには hidden クラスを付与します。 キーボード操作の場合、元々フォーカスがあったボタンが消えてしまいます。そこで、追加で表示されるコンテンツにフォーカスを移動してあげることで、キーボード操作のユーザーが操作箇所を見失わないようにします。 これらの観点を実装すると以下のようになります。まず HTML に、 aria-controls 属性を使って、ボタンの操作対象となる要素を関連付けます 4 。 < div > < a href = "..." > 敷金礼金0(ゼロ・なし)の物件 </ a > < a href = "..." > 新築・築浅物件 </ a > </ div > < button class = "load-more" type = "button" aria-controls = "next-content" data -controller= "inlay" data - action = "inlay#show" > もっと見る </ button > < div id = "next-content" class = "hidden" > < a href = "..." > 二人暮らし物件 </ a > < a href = "..." > タワーマンション(高層マンション) </ a > </ div > JavaScript のコードは次のようになります。ボタンをクリックすると show メソッドが呼ばれます。やや複雑ですが、 追加で表示される最初のタブ可能な要素にフォーカスを当てる という処理が読み取れるでしょうか? また、コード内で使用されている tabbable 関数は、パラメーターに渡された要素内の「タブ可能な」要素を列挙し、配列として返す npm パッケージです 5 。 // inlay_controller.js import { Controller } from '@hotwired/stimulus' ; import { tabbable } from 'tabbable' ; export default class extends Controller { show() { this .nextContent.classList.remove( 'hidden' ); this .element.classList.add( 'hidden' ); this .firstTabbableItem?.focus(); } get nextContent() { let control = this .element.getAttribute( 'aria-controls' ); return document .getElementById(control); } get firstTabbableItem() { return tabbable( this .nextContent) [ 0 ] ; } } チェックボックスに適切な名前を付ける 物件の一覧ページでは、物件を複数選び、まとめて不動産屋に問い合わせたり、お気に入りに追加したりできる機能が提供されています。そのために各物件にはチェックボックスが設けられています。しかし、このチェックボックスには適切な名前が設定されておらず、スクリーンリーダーで利用するときに「クリック可能 チェックボックス チェックなし」としか読み上げられず、 何のチェックボックスなのかわからない 状況が発生しています。 スクリーンリーダーでチェックボックスにフォーカスを当てると「クリック可能 チェックボックス チェックなし」と読み上げられる HTML を見ると、チェックボックスが単体で置かれているだけで、ラベルと紐づいていないことがわかります。 < div class = "room" > < input type = "checkbox" > < div class = "spec" > < p class = "photo noImage" > No Image </ p > < p class = "spec" > -/3LDK/120.00m² 8.5万円 管理費:12,000円 敷金1ヶ月/礼金1ヶ月 </ p > </ div > </ div > この問題を解決する難しさは、チェックボックスが建物全体ではなく各部屋を対象としているため、簡潔で明確なラベルが存在しないことです。適切な名前を設定するためには、適切な場所を探し出し、それを名前として使用する必要があります。 解決策として、 部屋の間取り・面積・賃料などを表すテキスト部分をラベルとして使用 しました。チェックボックスとラベルは離れた場所にあるため、 label 要素の for 属性を使うか、チェックボックスに aria-labelledby 属性をつけることで名前を指定します。今回は事情 6 があって、 aria-labelledby を用いました。 < div class = "room" > < input type = "checkbox" aria-labelledby = "room-0123" > < div class = "spec" > < p class = "photo noImage" > No Image </ p > < p id = "room-0123" class = "spec" > -/3LDK/120.00m² 8.5万円 管理費:12,000円 敷金1ヶ月/礼金1ヶ月 </ p > </ div > </ div > この対応により、スクリーンリーダーによる読み上げは「クリック可能 -/3LDK/120.00m² 8.5万円 管理費:12,000円 敷金 1ヶ月/ 礼金 1ヶ月 チェックボックス チェックなし」となりました。正直なところ、この読み上げ方で完全に不便なく利用できるかは疑問が残りますが、何もない状態よりは改善されているでしょう。 カルーセルをアクセシブルにする カルーセルはアクセシビリティ上の問題が起きやすい UI です。スライダーなどとも呼ばれたりします。LIFULL HOME'S のトップページにもドドンとカルーセルが置かれています。カルーセルに起因する問題はいろいろとありますが、中でも致命的な問題につながるのは「自動再生」です。一定時間ごとにスライドが切り替わる機能です。 3秒ごとにスライドが切り替わるカルーセル LIFULL HOME'S のカルーセルも自動再生つきのものでした。自動再生するカルーセルは WCAG の達成基準「 2.2.2 一時停止、停止、非表示 」に違反しており、 動きのあるコンテンツの動きを追うのが苦手な人や、切り替わるスピードに合わせて文字を読み切れない人は利用が難しいでしょう。動きに気を取られて注意が維持できなくなる人は、ページのほかの部分が利用できなくなることすらあり得ます。 代表的な解決策は「一時停止ボタンを設ける」というものです。自動再生はするものの、一時停止ボタンを押せば動きは止まるため、自分のペースでスライドを見ていけるようになります。 今回、私たちは「 自動再生せず、アクセスごとにスライドをランダムに並び替える 」というアプローチをとりました。自動再生を導入するモチベーションは、複数の優先度の高いコンテンツをユーザーに提示することです。ランダムに並び替えて表示することでも同様の結果は得られるだろうと考えました。 自動再生されなくなったカルーセル うれしかった副次的効果として、もともと後ろのほうに追いやられていたスライドのクリック率が向上しました。カルーセルのスライドは、最初に表示されるスライドの優先度が高いという「大まかな」傾向はある一方、後のほうのスライドの優先度が低いとは必ずしも言えません。スライドをランダムに並び替えると、これまで埋もれていたスライドがユーザーに届くようになる可能性もあり、ランダム化は意外と多くのユースケースで有効な手法かもしれないと思いました。 ちなみに、スライドを前後に送るボタンもコントラストを高くし、視認性が向上しています。 スクリーンリーダーによる検索結果の件数の読み上げ LIFULL HOME'S では、物件を探す際に検索条件を指定することができます。現在の条件で検索した場合のヒット件数が、画面下部にリアルタイムで表示されます。 検索条件を変更すると、該当物件の件数がリアルタイムに更新される スクリーンリーダーの利用者は、このような 操作と離れた場所で変化が起こったことに気づくことができません 。件数を知るためには、フォームを送信して物件一覧ページまで遷移するか、検索条件の最下部にあるボタンの横まで読み進めなければなりません。 これを解決するために、 該当物件件数の部分をライブリージョンとしてマークアップする ことで、件数の変化がスクリーンリーダーで読み上げられるようになります。 <!-- これまで:変化がスクリーンリーダーで読み上げられない --> < p class = "itemTxt" > 該当物件 < br > < span class = "num" > 1,366 </ span > 件 </ p > <!-- 修正後:変化がスクリーンリーダーで読み上げられる --> < p class = "itemTxt" role = "status" > 該当物件 < br > < span class = "num" > 1,366 </ span > 件 </ p > role="status" という属性が追加されています。この属性によって物件数の部分が「ライブリージョン」となり、内容の変化は逐次スクリーンリーダーによって読み上げられるようになります。類似の属性として role="alert" もあり、こちらはより緊急度の高いフィードバックのために用います 7 。 さて、件数の表示はもう少しだけ複雑な仕様がありました。検索条件が変更されたあと、件数の表示が即座に行われるわけではないという点です。設定された条件の該当物件数がいくつあるかサーバーに問い合わせを行いますが、その間は件数の表示が「…」に変わるのです。 この表示になっている間はスクリーンリーダーで読み上げさせないようにしたいです。そこで、読み込みが行われている間は aria-busy 属性を true に設定することで、読み込み中はテキスト読み上げが行われないようにしました。 < p class = "itemTxt" role = "status" aria-busy = "true" > 該当物件 < br > < span class = "num" > ... </ span > 件 </ p > 以上の対応で、スクリーンリーダーを使用しているユーザーも、該当物件数を確認しながら、自由に検索条件を調整できるようになりました。 そのほかの改善点 さらに、事前に実施していたテストによって明らかになった多くの問題点も修正しています。 名前の付いていないボタンに名前を設定する 名前がなかったボタンに適切な名前を設定しました。スクリーンリーダー利用者もボタンの機能を理解しやすくなりました。 モーダルダイアログのフォーカス管理、キーボード操作対応 キーボード操作だけでモーダルダイアログを利用できるようになりました。 ピンチアウトでズームできるようにする これまでスマートフォンサイトはビューポートの設定( user-scalable=no )によってピンチアウトでのズームが制限されていましたが、この制約を解除しました。 アイコンやテキストのコントラスト改善 一部のアイコンやテキストのコントラストが不足していたため、十分なコントラストが得られるように色を調整しました 8 。 おわりに 本記事では、ウェブアクセシビリティ向上に向けた取り組みとして、いくつかの改善策について解説しました。解説した内容は多くのウェブサイトやサービスで共通するものだと思います。この内容を参考に、ぜひあなたのプロダクトのアクセシビリティ改善に役立ててください。 また、キーボードや支援技術ユーザーの皆さんには是非サイトを使ってみていただき、感想をお聞かせください。ご意見をもとに、さらにアクセスしやすいウェブサイトにしていきたい気持ちです! ここ半年くらいをかけて、LIFULL HOME'S のスマートフォンサイトのアクセシビリティを改善してきました。デスクトップサイトは次の半年で改善を加えていく予定です。テスクトップサイトは特に歴史が長く、コアなファンも多いと聞きます。これからも、一人でも多くのユーザーが快適にサービスを利用できるように品質向上に努めてまいります。 お読みいただきありがとうございました。LIFULL では共に働く仲間を募集しています! hrmos.co hrmos.co ARIA in HTML 日本語訳 (momdo.github.io) ↩ HEY のプロダクションコードから拝借したものです。 参考 ↩ ソシオメディア | ディスクロージャー (sociomedia.co.jp) ↩ aria-controls 属性である必要は必ずしもありません。操作対象の要素を特定できる仕組みとして data 属性を用いてもいいでしょう。 ↩ tabbable - npm (npmjs.com) ↩ 部屋のスペック部分が ul 要素でマークアップされていたため、 label 要素で括ることができなかったことが理由です。 ↩ role="alert" は role="status" よりも高い緊急度を持つ領域のマークアップに向いています。 role="status" の領域での変化は適度なタイミングで読み上げられる一方、 role="alert" は即座に読み上げられることが期待されます。 ↩ LIFULL のブランドカラー #ED6103 は白色 #FFFFFF と組み合わせて用いられますが、この2色間のコントラストはテキストに対して不足(3.32:1)しています。社内的には APCA のコントラスト計算方式を推奨しており、問題ない(59.7)としています。 ↩
プロダクトエンジニアリング部の千葉です。 2022年に新卒で入社して、 売却査定領域 の開発に携わっています。 この記事では、2022年10月から取り組んでいる売却査定CRO(Conversion Rate Optimization)チームの施策について紹介していきます。 CROチーム取り組み紹介 ABテスト施策実施の背景 直面した壁 高速に回るPDCA よかったこと 大変だったこと ユーザーファーストGとの連携 エラーメッセージの見せ方改善の施策化 まとめ CROチーム取り組み紹介 売却査定CROチームでは主に集客用のランディングページ、マンションの売却を検討する人のための棟情報ページなどの最適化のためにABテストの施策を行っています。 2023年3月末までに目指すCVRを定め、その目標達成のために複数のABテスト施策を実施→A寄せまたはB寄せ→改善→新たなABテスト施策の実施といったPDCAを回しています。 ABテスト施策実施の背景 集客用のランディングページは事業インパクトが大きいのですが、直帰率・離脱率が高く改善幅も大きいという課題を抱えていました。そのため、これらのページでチャット式の入力フォームを導入するという施策を実施することになりました。 直面した壁 しかし、一部の媒体ではポリシー違反の懸念があることがわかりました。その結果、集客用の一部のランディングページにのみ、既存の入力フォームとは大きく変わったチャット式の入力フォームが導入されることになりました。 ほかのページでは 既存の入力フォームをより簡潔にしてボタン一つで次ページに遷移できるようにする ファーストビューの印象を変えるために背景色を調整する といったABテスト施策を実施することになりました。 高速に回るPDCA よかったこと 2022年10月からのチーム制の導入によりCRO施策に専念することになりました。 そのため、企画の方のABテスト範囲拡大・縮小や寄せの判断から技術側の開発→リリースまでの時間が短縮され、高速にPDCAを回すことができ、6ヶ月という短い期間の中でも多くの施策を打つことができました。 週1回の定例などでチームでの認識が取れていたことによって、テスト結果のジャッジから次のアクションへ高速に移ることができたことが大きな要因だと思います。 また、CRO施策に専念することにより培われたノウハウを活かすことで、速い開発スピードの中でも不具合をだすことなく進めることができました。 大変だったこと 高速にPDCAが回ることで多くの施策が打てる一方で、大変になるのはB本実装です。 ABテスト施策の実装時には既存のものをAとして新規にBのものが作られます。 ABテストを実施しAパターンに戻ることになった場合には、ABテスト実装分をリバート(差し戻し)すればよいのですが、Bパターンになることになった場合には ABテスト関連のために実装した設定ファイル、効果測定用の処理を全て撤去しなければいけない ABテスト用の設定ファイルでテスト対象を絞り込んでいた場合には、その設定を考慮してロジックを実装しなければいけない ABテストを実施した内容のうち、一部の内容だけを本実装する場合に、本実装の要不要の判断が難しい といった工数がかかってしまいます。 そのため、ABテストの実施の際には高速に開発を行うことができますが、B本実装になると工数がかかってしまい大変でした。 今後は、 ABテスト時にB本実装を見据えた設定ファイルに依存しすぎない実装 ブランチを分けてABテスト実装箇所のみをわかりやすくする などといった改善をしていきたいと考えています。 ユーザーファーストGとの連携 ユーザーファーストGの方に協力していただき、ABテストを実施しているページの課題点・懸念点などを洗い出していただきました。目的としては以下の2つです。 今後の修正対応の方向性を検討する際の判断材料にする ユーザビリティ観点での優位性/深刻度の高い箇所の把握 開発している中で見慣れてしまい見落としてしまっているような部分を、第三者目線からご指摘いただくことで、新たな改善点に気がつくことも多かったです。 エラーメッセージの見せ方改善の施策化 施策の一部として、私自身がかねてから改善したいと思っていた、エラーフォームメッセージの見せ方の改善を施策化することもできました。 所属しているウェブアクセシビリティ推進WGの活動を通して深めた知見から、入力フォームのエラーメッセージと背景の明度差が近く、エラーメッセージが見づらいことに気がつきました。そのことから、利用者にとってエラーの箇所の特定が困難になっている可能性があることを提示し、実際にエラーメッセージを見やすくする修正を行い、リリースすることができました。 エラーメッセージ(before) エラーメッセージ(after) まとめ 売却査定CRO(Conversion Rate Optimization)チームの取り組みについて紹介しました。 売却査定では2022年10月から始まったチーム制ですが、この短期間の中でも多くの施策を打つことができ、よかった点も改善点も含めていろいろな結果をだすことができました。 最後に、LIFULLではともに成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
テクノロジー本部の yoshikawa です。 普段の業務では全社データ基盤の開発や技術検証、ビジネスサイドのデータ活用支援を行っています。 本記事では、Google CloudのDataplex(主にData CatalogとData Lineage)を活用したデータマネジメント及びその支援に関する事例を紹介します。 また関連して、データ開発の質とスピードを高めるべくCloud Composerとdbt(dbt Core)を採用し、DWH(データウェアハウス)/DM(データマート)構築基盤を刷新する取り組みについても紹介します。 全社データ基盤の概観 全社データ基盤ができるまで、そしてこれから データマネジメントの必要性 データマネジメントの領域 データマネジメントをエンジニアとして支援する データスチュワードの発足 Dataplexの活用 データのカタログ化と用語集 データリネージの活用 DWH/DM構築基盤の刷新 まとめ 全社データ基盤の概観 LIFULLの全社データ基盤はBigQuery(Google Cloud)を中心としつつETL処理にStep Functions(AWS)などを利用し構成されています。 全社データ基盤には基幹サービスであるLIFULL HOME'S上のログデータや地理情報マスターデータなどのデータソース、それらを事業・用途別に集計・加工したDWH(データウェアハウス)/DM(データマート)など、様々な出自・用途のデータが集約されています。 記事執筆時点での規模は容量にして数百TB、テーブル数にして1万以上と大量のデータが蓄積されており、今もなおデータ量が増加しています。 そうした大量のデータはBI経由で各部署・プロジェクトの指標として定期的に参照されたり、施策の効果測定や検証などアドホック分析、機械学習などに活用されています。 全社データ基盤ができるまで、そしてこれから 数年前、今の全社データ基盤が構築される前はデータ活用に関して下記のような課題がありました。 クラウドサービスやDBにデータが散在(サイロ化)している セキュリティの観点からデータを抽出可能な人が限定されている データを使う人(アナリスト、企画、etc)と、データを抽出できる人(エンジニア)が別なので、手戻りが頻発しデータを使うまでの効率が悪い そうした課題を解決すべく今の全社データ基盤が構築され、BigQueryへのデータ集約やIAMによる権限管理によって課題は解決されてきました。 (全社データ基盤が構築されるまでの沿革は過去のブログからご覧になれます。) www.lifull.blog 利用実績の増加を遂げつつデータを蓄積し続けている全社データ基盤ですが、これからはデータを蓄積し誰でも利用できるようにするだけでなく、データの活用を通じ価値提供を促すことで、利益という形でも貢献できる基盤へと成長していくことが重要となります。 データマネジメントの必要性 現在、全社データ基盤が安定稼働している中でデータ活用に関して下記のような課題があります。 データを利活用するステークホルダーの増加・多様化、およびそれに伴う要望の複雑化・多様化(人の問題) データを生成する/参照するシステムの増加・複雑化、およびそれに伴う全体統制の難化(システムの問題) データ量の増加・管理の複雑化に伴うデータ品質維持の問題(データの問題) 全社データ基盤が構築される前と比べると業務プロセスの一部分としてデータを活用する際の問題だけではなく、データを扱う人や組織のリテラシーやガバナンス、中長期的にデータ活用を継続していく上での品質の維持や管理コストに関する課題が顕在化しています。 そのため、システムの一つとしてデータ基盤を開発し発展させるだけでなく、人・組織の問題へと越境することで上手くデータ活用をし続ける取り組み→データマネジメントが今の全社データ基盤で必要とされています。 データマネジメントの領域 データマネジメントには多様な領域が存在するとされています。 データマネジメントの知識体系をまとめた書籍として『DMBOK』が有名ですが、データガバナンスを中心とした「DAMAホイール図」と呼ばれる図にデータマネジメントに関する知識領域が定義されています。 (詳細は書籍かデータマネジメント協会 日本支部のホームページをご覧ください) www.dama-japan.org 全社データ基盤においても、データセキュリティやドキュメントとコンテンツ管理、メタデータ(カタログ化)をはじめとした領域でデータマネジメントの取り組みが行われています。 それらの取り組みについて以降で紹介していきます。 データマネジメントをエンジニアとして支援する データマネジメントはデータの生成や基盤に携わるデータエンジニアだけではなく、CDOやデータ基盤・統括に関連するマネージャー、データを活用するアナリストやデータサイエンティストなど多様なステークホルダーと連携し進めていきます。 筆者はデータマネジメントに必要とされる技術(Google Cloudのサービスが中心)についての検証や、ビジネス職(データスチュワード)への技術的な支援・提案といった形で携わっています。 データスチュワードの発足 先の課題として述べたようにステークホルダーの増加や多様化により、データエンジニアだけでデータに関する要望に応えることが難しくなりつつあります。 そこでLIFULLでは昨年末からデータ整備やデータ活用に関する相談窓口を担う役割としてデータスチュワードが発足しています。 Dataplexの活用 冒頭でお伝えしたように、全社データ基盤はGoogle CloudとAWS両方を利用して構成されています。 データ利用者にとって中心となるのはBigQueryであるため、データに関する情報(メタデータ、データ品質、セキュリティレベル、etc)はGoogle Cloudに集約するという流れになりつつあります。 データマネジメントの文脈において重要となるサービスはDataplexです。 cloud.google.com DataplexにはLake、Zone、Assetという論理的な概念を使い、各ストレージ(データレイク、DWH、DM)上のデータをドメイン別に一元的に管理できる機能をはじめ、カタログ機能(これまでのData Catalog)や、データリネージ機能など、データガバナンスおよびデータマネジメントの観点で有用な機能が存在しています。 多様な機能が存在していますが、中長期的なデータマネジメントに貢献するか、および現在のデータスチュワードのユースケースに貢献するかを念頭にいくつかの機能を利用しています。 データのカタログ化と用語集 昨年の夏からデータカタログを利用し始め、現在ではデータスチュワードによるカタログ化(=タグ付けやドキュメンテーション)が進行しています。 中長期的な目標として全てのデータに対してのカタログ化を視野に入れていますが、現在は新規作成されたデータや戦略上重要とされるデータを優先的にカタログ化しています。 カタログ化において注目している機能は用語集(Dataplex business glossaries)です。(記事執筆時点ではpreview版です) cloud.google.com 利用イメージは下記のGoogle Cloud Blogをご覧いただけると良いかと思います。 cloud.google.com 文字通り、事業/ビジネスに関する用語集を作成できる機能ですが、タグテンプレートのようにデータと関連づけを行うこともできます。 全社データ基盤では、Dataplex business glossariesが利用可能になる前からドメイン知識などのビジネス用語とデータとの関連づけを構想していました。 当時はタグテンプレートでの実現を予定していましたが、Dataplex business glossarieはタグテンプレートと比べ以下の点で利点があり、データ整備実施者とデータ利用者の両方にとってメリットがあると考えています。 データに対する用語の関連付けや、用語間の関連付けがGUI上で容易に行える=整備が楽 Data Catalogの検索結果上のスキーマタブで、内容を簡単に把握できる(下図を参照)=欲しい情報が見つけやすい カラムに関連付けられた用語(Business Terms).タグ(Column Tags)はクリックして内容を確認できる一方、用語なら内容が一目でわかる まだpreview段階の機能ではありますが、データソース(BigQuery)と近いところにデータの仕様(Dataplex business glossaries)を蓄積させ、データ利活用者およびデータスチュワードの認知負荷を軽減し、データ利活用の体験を向上させていければ理想的だと考えています。 データリネージの活用 筆者は昨年夏のデータカタログの利用開始と同時期にデータリネージの実現についても検証しています。 その結果として、Audit LogsとINFORMATION_SCHEMAから取得可能なテーブル定義とジョブ実行情報を元データとし、Streamlitとpyvisを用いて可視化するデータリネージアプリを実装しました。 詳細は過去のブログよりご覧いただけます。 www.lifull.blog データリネージアプリは半年程度運用し、その間に下記のようなユースケースが生まれました。 施策によるデータソース定義変更の影響範囲調査 不要データセット削除による影響範囲調査 そんな中、Google Cloudから公式のデータリネージ機能がアナウンスされ利用できるようになりました。(記事執筆時点ではpreview版の機能を含みます) cloud.google.com 機能の詳細は上記公式ドキュメントの通りですが、使用感としてはこれまでに生じた影響範囲というユースケースに対しては対応できると考えており、BigQueryのタブからもアクセスできることから、アドホックな分析でSQLを書きながら関連するデータがないか探索するといった用途にも適していそうです。 記事執筆時点では機能面の充足や費用面への影響を中心に検証を行っており、本番環境への適用に向けて準備をしています。 カスタマイズ性という観点では、自前で容易したデータリネージアプリが勝る場面もありそうですが、先のDataplex business glossariesとあわせ、データに関する情報をGoogle Cloudへと集約することでSSoT(Single Source of Truth)を構築できる良い機会と考えています。 DWH/DM構築基盤の刷新 こちらはデータマネジメントというよりエンジニアリング寄りの内容ですが、5年以上にわたって稼働しているDWH/DM構築基盤の刷新も進行しています。 現状のETL処理基盤はデータパイプライン構築にLuigi、ワークフロー実行環境にStep Functions(AWS)などを採用して構成されています。 現行の全社データ基盤の構成図 当時からデータエンジニアリングに関するツールや知見は進化し、開発速度や開発者体験を向上させる仕組みが整ってきたことから、現行の構成からワークフローの実行および管理の基盤としてCloud Composer、SQL開発およびデータモデリング用にdbt(dbt Core)を採用した基盤へのリプレイスの検証が開始しました。 cloud.google.com www.getdbt.com その他の採用技術の候補としてArgo Workflows(on GKE)やDataformも検討しましたが、 マネージドサービスの恩恵を受けながらも、開発者体験を上げつつデータ開発を効率化できるようComposerとdbtという組み合わせを採用しました。 記事執筆時点では一部処理での検証を実施している段階ですが、下記のようなメリットを感じています。 SQLとワークフロー実行設定が分離されるようになり、SQLにデータ・テーブルに関する仕様が集約されるようになった dbtがSQLの依存関係を解析してくれるため、SQL実行順序の制御が手軽になった スキーマ定義やワークフロー実行制御において手動実行していた開発フローが自動化された まとめ LIFULLの全社データ基盤ではGoogle Cloudを活用しデータマネジメントおよびその支援を進めています。 本記事ではその中でもデータカタログ(Dataplex business glossaries)を利用したカタログ化、データリネージ機能を採用した影響範囲調査の負荷軽減、開発者体験・開発速度の向上を目的とした既存ETL処理基盤の刷新についてお伝えしました。 本記事では紹介しきれなかったデータマネジメントに関連する取り組み(データ品質、データセキュリティなど)も多くあるため、後続のクリエイターズブログでお伝えできればと思います。 最後に、LIFULLではエンジニアを募集しています。カジュアル面談のご応募や現在募集中の職種については下記をご覧ください。 hrmos.co hrmos.co