テクノロジー本部の相馬です。好きな Web API は Window.requestAnimationFrame() です。 私が現在所属しているグループでは、弊社のメイン事業である LIFULL HOME'S における開発効率の改善などを行っています。 私はフロントエンドの開発環境の改善などを主に担当しております。 今回は、LIFULL HOME'S の JavaScript 開発環境のビルドを、esbuild を使ってビルドしたところ、当社比で 55 倍高速になった事例について紹介いたします。 目次 サマリ Before/After はじめに 従来の開発環境について 開発環境のインスタンススペック不十分問題 npm run dev 遅すぎる問題 esbuild とは? 開発中大変だったところ/ちょっとハックしたところ esbuild に当時 watch option がなかった トップレベル this のスコープ問題 UMD ライクで実行環境毎に global object を判断するライブラリのコードが壊れる おわりに サマリ 本改修以前、バンドラー には Rollup を使い、transpiler は IE11 むけに設定 本改修時点で 401 本のバンドルファイル を作成(歴史的経緯) 本改修以前、 npm run dev コマンドから全てのバンドルの warmup が終わるまで 220sec(m4.4xlarge) 使用メモリ 3.7GB/process 本改修以後、 npm run dev コマンドから esbuild で全てのバンドルの warmup が終わるまで 4sec(m4.4xlarge) 使用メモリ 685MB/process Before/After Before After バンドラー Rollup esbuild time 220 sec 🐢 4 sec 🚀 memory 3.7 GB 685 MB はじめに LIFULL HOME'S は弊社でもっとも開発が盛んなアプリケーション(Repository)です。 Commit 数や Contributor 数は弊社でもっとも多く、また開発の歴史も比較的に長い(10 年)部類に入ります。 これまでも「フロントエンドの開発環境の改善」という文脈で、何度か記事を書いているので、お時間ある際に読んでいただけると喜びます、私が。 9 年を超えて開発が続く LIFULL HOME'S の Web フロントエンド開発環境の改善 - LIFULL Creators Blog Node.js で Twig のプリプロセッサーを作って言語の機能拡張をしてみた話 - LIFULL Creators Blog 従来の開発環境について 以前の記事で解説させていただいた「近代化」により、フロントエンドにもそれなりにモダンな開発環境が整いました。 近代化を行って全てがハッピーエンドだったかというとそうではなく、新しいモノを導入することで部分的に不都合が生じている箇所もありました。 開発環境のインスタンススペック不十分問題 npm run dev 遅すぎる問題 開発環境のインスタンススペック不十分問題 現在 LIFULL HOME'S の開発環境は「一つの開発サーバに個人の開発マシンから ssh して開発する」という方式をとっているため、高価な処理を行うようなプロセスが開発環境で必要な場合、開発マシン上では「 ログインユーザ(開発中のユーザ)*高価な処理 」だけ該当マシン上でリソースが必要になってしまいます。 Rollup を導入することで、JS の依存関係が明確になり様々なツールの利用が可能になった反面、当然バンドルや minify といった比較的に高価な操作が必要になってしまいました。 それまでのインスタンスタイプでは不十分で、インスタンスタイプの変更を余儀なくされたことが記憶にある限りで 2,3 回あります。 npm run dev 遅すぎる問題 それまでは歴史的経緯により 401 本のバンドルファイルを直列で作成 していたため、220sec(m4.4xlarge) ほど warmup に時間がかかっており、開発時の問題の種となっていました。 JavaScript(Node.js) はご存知の通りシングルスレッドの実行モデルであるため、こういった AST をトラバースしたり文字列の置換など、高価な計算処理を苦手とします。 The Node.js Event Lvop, Timers, and process.nextTick() | Node.js Node.js だけで高価な計算を処理したい場合、 jest-worker などで worker-thread を駆使してパラレルで実行することで実時間の短縮などが可能です。 Rollup にも JS API が存在しているので、やろうと思えばできそうですが、シンプルにそこまでやっていませんでした。 そんなことを思っている中、JS バンドル界を揺るがす期待の新星として esbuild が颯爽と登場しました。 初期から動向を見守り続け、プロダクションでいけそうなことを目視で確認したので、esbuild にトライしてみようと奮い立ちました。 esbuild とは? Figma の CTO である Evan 氏が作成した、Go 製の JS バンドラーです。JS といっていますが、デフォルト(特に設定不要)で TypeScript のトランスパイル/バンドルも、 tsconfig.json をよしなに解釈し、実行してくれます。 esbuild - An extremely fast JavaScript bundler An extremely fast JavaScript bundler ↑ と評されている通り、既存の JS バンドラー(webpack/rollup/parcel)と比べてベンチマークのスコアで 10-100 倍ほど高速です。 Snowpack や Vite といったモダンなバンドラーにも採用されているバンドラーで、非常に高速であることで知られています。 ネタバレ(というかすでに上でも書いてるのでバレか怪しいですが)になりますが、当社比でも Rollup から esbuild への移行で 55 倍程度高速 になりました。 なぜこんな速度が出せているのかというと、簡単にまとめると並行処理とメモリ効率が高度に配慮された設計でフルスクラッチで記述されたライブラリだからという感じです。 例えば、JS のバンドラーはコードの parser/generator は 3rd party のライブラリに依存しており、また transpiler や minifier もそれぞれ別のライブラリがプラグインとして機能を追加で提供している形が多いと思います。 それぞれのライブラリはそれぞれの主たる目的があり、全てのライブラリがパフォーマンスを最優先としているわけではありません。 esbuild は read/parse/traverse/compile/minify/generate 全ての機能を備えているため、ライブラリ間の I/O 調整のためだけな不要なデータ構造のコピーや多重に AST を捜査するオーバーヘッドなどを極力抑えることで、その圧倒的なパフォーマンスを実現しています。 詳しく知りたい方は下記リンク先の記事をご覧ください。より詳細をご確認いただけます。 Why is esbuild fast? - esbuild FAQ また、esbuild は ES6(ES2015) 以降のコードしか生成できないという特徴があります。モダンな構文のみをサポートすることで、速度を保っているという側面もあります。よって IE 対応などが必須な場合 esbuild は利用できません。(バンドル後のコードを再度 Babel にかけるなどの対応は可能ですが、esbuild 単体での機能完遂はできないという意味です。) Lowering for ES5? · Issue #297 · evanw/esbuild LIFULL HOME'S では依然として IE11 をサポートしています。ですが開発環境では、開発の時は開発者はみんなモダンなブラウザを使っているはずだということを担保に、とりあえず開発環境にだけ入れてみようということで、作業に取り掛かることにしました。 開発中大変だったところ/ちょっとハックしたところ esbuild に当時 watch option がなかった 作業を開始したタイミングでは、esbuild はバンドラーとしてはそこまで多くの機能を持っていませんでした。CLI や JS API からワンショットでバンドルできるだけでもありがたかったのですが、ファイルの変更差分を監視し、変更があったタイミングで再度 build が走る、いわゆる watch モードが存在しなかったので、この辺は chokidar などで watch モードをラップするような処理を書いて満足して PR を温めていたところ、公式から watch オプションが登場したのでこのコードは日の目を見ることがなくて安堵しています。 トップレベル this のスコープ問題 esbuild はその名の通り ESM(ECMAScript Module) 向けのモジュールバンドラーであるため、context 関連で patch をあてる必要がありました。 ESM ではトップレベル(global context)の this は undefined と解釈されます。 そのため、esbuild も当然同じ振る舞いをします。 Bug: global this is optimized to void 0, which will lead to runtime errors. · Issue #1225 · evanw/esbuild しかし、古くから動いてる我々のコードは ESM を前提としておらず、一部のファイルでは以下の例のようにトップレベルの this が window であることを前提にしたコードが含まれていることがわかりました。 ( function (win) { // do something } )( this ); こういったコードが軒並み undefined になってしまい、そのままでは動かないコードが多くありました。 そこで トップレベルの context を window で bind する関数を injection するプラグイン を書きました。 esbuild - Plugins プラグインのコードの肝となる箇所を一部抜粋しますが、こんな感じで必要なファイルに対して const banner = "(function () { " ; const footer = " \n }).bind(window)();" ; // ........... // なんやかんやあって // ........... build.onLoad( { filter: /.*/ } , async (args) => { const path = args.path; const fileBody = await fsp.readFile(path, "utf-8" ); if (!needWindowCtxWrap(fileBody, path)) { return { contents: fileBody, loader: "js" , } ; } const contents = `$ { banner } $ { fileBody } $ { footer } `; return { contents, loader: "js" , } ; } ); このプラグインを挟むことで、先程のコードは ( function () { ( function (win) { // do something } )( this ); } .bind( window )()); となり、これによって「プラグイン 適応前にトップレベルであった context」は window であることが確定します。これによって暗黙(?)の変換は行われず、期待通り window を解釈できるようになりました。 また、一応 esbuild にも Rollup でいうところの context オプション のような --define というオプションがあり、これによって this の値を書き換えられそうなのですが、同時は this に window を指定することはできませんでした。 これに関して、issue で起票しており、現在は window の指定ができるようになっているそうですが、こちらは指定せず上記のプラグインを引き続き利用しています。 [feat] optional global context setting in ESM #1361 UMD ライクで実行環境毎に global object を判断するライブラリのコードが壊れる LIFULL HOME'S のコード群の中のライブラリには、歴史的な理由で npm からではなく内部的にソースコードを抱えているものもいくつか存在しており、その中には UMD ライクな形式の宣言が多くありました。 UMD は、実行環境によってモジュールをどのようにエクスポートするかをユニーバサルに決定する機構です。 決定ロジックに exports 変数の存在確認が含まれており、もし存在する場合は Node 環境だと解釈されてしまいます。 私たちの扱いたいコードはブラウザ向けのコードだったのですが、esbuild でバンドルする際に挿入されるコードに exports 変数が含まれているため、誤って解釈されるという問題が発生しました。 これについても プラグイン によって解消済みで、dummy の引数をトップレベル IIFE に対して付与することで擬似的に全スコープにおいてそれらの変数が undefined となるべく(つまり window が正しく判断されるべく)対応しました。 const banner = "(function (module, exports, require) { " ; const footer = " \n }).bind(window)();" ; このように exports の変数が存在する場合、esbuild としては exports オブジェクトを injection することができず、名前の衝突を避けることができます。 LIFULL HOME'S のコードは全てブラウザでの実行が期待されているため、このように別環境で悪さをしそうな変数は軒並み強制的に全スコープで undefined とすることでことなきを得ています。 おわりに 速度とパフォーマンスは圧倒的に正義であることを esbuild で再認識させてもらいました。 esbuild は プラグイン の拡張性も高く、Rollup など既存のバンドラーで複雑なビルドを組んだ経験があり、バンドラーの特性や特有の挙動などに知見がある場合、ドキュメントを読めばこちらも難なく利用できると思います。 幸い、LIFULL HOME'S の既存の JS ビルドはそこまで複雑ではなかったので、今回は比較的シンプルに収まりましたが、プロジェクトによってはプラグインなどいくつか用意する必要があるかもしれませんが、その労力に見合うだけの見返りが得られると思いますので、esbuild、おすすめです! 最後に、LIFULL では一緒に働く仲間を募集しています。よろしければこちらも合わせてご覧ください。 【エンジニア】募集求人一覧 | 株式会社 LIFULL 【エンジニア】カジュアル面談 | 株式会社 LIFULL
みなさんこんにちは。 品質改善推進ユニットQAグループでQAエンジニアをしている飯泉です。 今回は技術的な話からちょっと離れているのですが、 社内向けのサービスを広めるために工夫した話をしたいと思います。 新しいツール導入やアイデア浸透で苦労した経験がある方には面白い話かもしれません。 お時間があれば是非読んでみてください。 結論 『FEARLESS CHANGE アジャイルに効く アイデアを組織に広めるための48パターン』を参考にいくつかアプローチを行った結果、サービスの利用・相談が増えました。 新しいサービスを作る時は、計画時からどう広めるか・使ってもらうようにするか考えるべきであることが今回得られた教訓です。 QAグループが展開するサービス QAグループではLIFULLが展開するプロダクトの品質を向上させることを目的に社内の開発・企画メンバー向けに様々なサービスを提供しています。 他の記事でも紹介していますので興味があれば読んでみてください。 本番障害からテストのヒントを抽出して活用する - LIFULL Creators Blog 新卒エンジニアのテストワークショップではテストを考えられるようになってもらっている - LIFULL Creators Blog プロジェクトに直接的に関わらないQAのアプローチ - LIFULL Creators Blog LIFULLのQAの取り組みについて - LIFULL Creators Blog 新しいサービスをなかなか使ってもらえない 皆さんも、新しいツールの導入やアイデアの浸透が中々進まずに苦労した経験はありませんか? QAグループでも、色々考えて新しいサービスを作って社内へ公開・告知するのですが、最初は全然使ってもらえません。 この課題はQAグループの長年の悩みで、最近も新しくサービスを作りましたが、作って間も無い頃は依頼が来なくて困っていました。 サービスを浸透させる方法を考えた チームメンバーからの提案で、アイデアや方法論を組織に広めるアプローチが書かれた本 『FEARLESS CHANGE アジャイルに効く アイデアを組織に広めるための48パターン』 を参考に、QAグループが展開するサービスに適したパターンをピックアップしてから具体的なアイデアを考えました。 工夫した結果 実行して一番効果があったのは、「質問フォームを設けてそこで気軽に相談を投稿できるようにする」でした。 気軽な相談・質問が切っ掛けでサービスを利用してくれる機会が増えたのかもしれません。 ただ宣伝するだけでは無く、サービスの利用シーンや条件を相談・質問できる場があるとイメージがつきやすくなり、 サービスの利用に繋がるということがわかりました。
検索エンジンチームの寺井です。21卒新卒エンジニアです。 気づけば入社から半年、本配属からは約4ヶ月が経過して、入社後最初の半期が終了しました。 ちょうど節目のいい機会なので、主に配属されてから今までやってきたことを振り返りつつ、使用した技術やもっと事前に勉強しておけば良かったことなどを書いていこうかなと思います。 今後エンジニアとして入社を考えているみなさんや、初心者エンジニアのお仲間さんがちょっと気づきを得られる記事を目指します。 ※とは言いつつ今回はテクニカルな話はほとんど出てきませんのでゆるい読み物として読んでいただけたらと思います。 入社までの話 学生時代は認知心理学の分野に強化学習を取り入れた研究をやっていました。 当時のエンジニア的な能力はPythonを利用したシミュレーションやデータ解析を研究で扱っていたのと、競技プログラミングを少しやっていた(緑)くらいで、何もできないというわけではないけど…でもコード書くのは楽しい!!という感じでした。 また、Webアプリの開発経験はほとんどありませんでした。やばいですね。 (LIFULLの新卒エンジニアは約1ヶ月半の研修で全員が基礎の基礎から学んでWebアプリを作って発表するという鬼カリキュラムになっているので未経験でもばっちり学べて安心です!) 配属後にやってきたこと そんなエンジニア超初心者の私ですが、新卒エンジニア研修を無事修了して5月末に検索エンジンチームへ配属されました。今までやってきた仕事の内容は主に以下の通りです。 検索エンジンSolr周りの機能の改善 内部機能で使われるコンポーネントのバージョンアップ 障害が発生しづらくなるような仕組みの追加 Solrの新バージョンの検証 変更箇所の調査 新バージョンで動作やパフォーマンスに問題ないかをテスト 弊社サービスの「LIFULL HOME'S」のメイン機能である物件検索は全文検索エンジンのSolrを利用しています。検索エンジンチームではユーザーの皆様のより良い物件検索や検索体験の向上を目指して、Solrを最新のバージョンに保ったり、Solr関連の機能を使いやすい形にメンテナンスすることなどに日々取り組んでいます。 業務で使用した技術をいくつか挙げると、 Solr: オープンソースの全文検索エンジンです AWS関連: 様々なリソースを活用してSolrの構築から自動化まであらゆることに活用しています mitamae: サーバをプロビジョニング(初期設定みたいなこと)するのに使います goss: YAMLでテストを記述してサーバ構築のテストをすることができます Node.js: AWS上のLambdaの中身を記述しています mocha: JS用のテストフレームワークで自動テストに使われます 細かく挙げるとキリがないのですが、この辺りのものは使用しました。 入社するまでにこれらを利用した経験はほとんどありませんでしたので業務中に苦しみながら学びました。人は痛みを伴うとしっかりと記憶に刻まれるんだなと実感しました(先輩エンジニアのみなさんに手取り足取り教えていただき、毎日大変お世話になっております)。 やっておいた方が良かったと思ったこと AWSに触れておくこと 真っ先に思いついたのがこれです。先ほど使用した技術をいくつか挙げましたが、その中でも特にやっておくべきだったなと感じています。 理由としては、AWSは独自の用語がたくさん出てきて謎が謎を呼ぶ状態になるので、0→1の部分をしっかりと押さえていない状態でいきなりサービス運用に携わろうとしても迷子になってしまうからです。 そんな0→1の部分を自分のペースでしっかりと押さえるためにはもちろん実際に触って何か作ってみることが一番いい経験になると思うのですが、そうじゃなくてもAWSにあるサービスについて理解しようとしてみるだけでも最初の一歩としては十分だと思います。 事前にざっくりとサービスの概要を知っているだけでも、配属されてからEC2…?CloudFormationって何…?何もわからないわ…って泣いちゃうことは減ることでしょう。また、ざっくりと知っていれば頼れる先輩エンジニアのみなさんに質問する時にも有利です(何がわからないのかがわからない問題を低減)。 その入門としておすすめしたいのがAWSが毎月無料で開催(2021年9月現在)している「AWSome Day Online Conference」です。 aws.amazon.com 約3時間で、全く知識がない状態からある程度手広くAWSのサービスを学ぶことができます。 ちょっとサービスを触ったことがある初学者の方にも、知識を整理し直せるという意味でおすすめです。 私は配属当初、業務の一環として上長から許可をいただいて参加をしてきました。 確かに実際に業務で使われている運用のレベルと比較すると非常に初歩の内容しか取り上げられていないのですが、それでもどこから手をつけていいか分からないほどの初心者だった私には学びの多い経験でした。 ちなみに同期エンジニアや別企業へ入った学生時代の友人たちも同じような苦しみを味わっているみたいなので、今までAWSを避けてきたエンジニア志望の方々は早めにアクションを起こすのがおすすめです。スタートダッシュで差をつけよう。 一人で何か動くものを作ってみること 業務では基本的なプログラミングスキルに加え、通信に関する知識やサーバ構築に関すること、さらにLinuxに関する知識やその他諸々が基礎的な知識として使われます。 新卒として関わらせてもらっている業務では、知識レベルとしては基本情報技術者で出てくる程度の内容のものが多いのですが、実際に自分で手を動かしてシステムを触ったことがほとんどないと、暗記しただけの知識だけでは実務で活用していくのはなかなか難しいです。 それらを手っ取り早く身に付ける方法としては自分で何か1つ、動くシステムを作成することが良いのではないかと思います。 私はエンジニア研修中に初めて一人でアプリを作り上げましたが、想像以上に基本的な情報スキルを幅広く要求されることが印象的でした。 大変なことも多かったのですが得られるものも多く、エンジニアとしてのレベルが一段階上がる非常に良い経験になりました。 しかし同時に、時間のある学生の内にもっと開発経験をしておけば良かったとも思いました。 なので、一人で動くものを作った経験があるということは大きなアドバンテージになると保証します。ぜひ取り組んでいただきたいです。スタートダッシュで差をつけよう。 逆にやっていてよかったと思ったこと 1つのプログラミング言語についてある程度複雑なコードを書いた経験があったこと プログラミング言語は使い回しが効くとはよく言われますが本当にその通りでした。 言語によって多少の表記の違いや実装概念の違いはありますが、逆に言えばそこだけ押さえてしまえばあとは汎用的なプログラミングスキルで何とか食らいつくことができました(万事解決できたわけではないです)。 また、これも大体の企業で共通していると思いますが、業務で利用するコードはかなり莫大な量です。 1つの機能を見るだけで何百行も追ったりするといったことが多々あるので、やはりある程度は複雑なコードを書いた経験が欲しいところです。 とはいえあまり身構える必要もないと思っていて、長めのコードを見たときに強烈な嫌悪感を抱くことがないくらいに、プログラミングに触れて慣れていればいいんじゃないかと思っています。 そのためにもなるべく日常的にプログラミングに触れる機会を増やしていくことが大事だと思います。 例えば、ゲーム感覚で楽しめる競技プログラミングなど、楽しみつつ手軽にプログラミングを触れる環境を自分に合わせて構築していくことがおすすめです。 コードを書くのは楽しいという感覚が身についているのは、エンジニアにとってかなり心の拠り所となります。 メモを取る癖があったこと 私は正直記憶力がそこまで良い方ではないので、昔から聞いた内容やその時考えていることなどをなるべくメモに書き出すようにしていました。 仕事を始めてからは毎日の業務に加え、新しい知識のインプットなど扱う情報の量や覚えることが増えたので、このメモを取る癖がかなり役に立っています。 もちろん言われたことを忘れないようにする目的もありますが、定期的に見直したりまとめ直したりすることでより知識を定着することができていると感じます。 また、雑多に書き残すのに加えて、部署に配属されてからは毎日やったことやその時悩んでいる点、その日の気分を一言で などを盛り込んだ日報を書くようになりました。 今回の記事を書く際にも日報を読み返しつつこんなこともやったなぁと思い出しながら執筆しています。 自分がやってきたことを時系列順に書き残すことで、成長を実感してモチベーションにつなげることもできますし、当時の記述をレベルアップした今の自分が読むと思いがけない新しい発見があったりもして楽しいです。 メモを取る癖をつけてみるのと、日報あるいは日記のように日付順に見られる形式で自分の文章を残すこと、おすすめなのでぜひやってみてください。 おわりに 冒頭にも書いた通り、気づくと半期が終わってあっという間の社会人0.5年目でした。 でも振り返ってみると中身の詰まった濃い毎日で、充実した日々を過ごしています(今回書ききれていない仕事の話やチーム内外での勉強会の話、チームビルディングでマインクラフトをやったりLIFULL独自のイベントがあったりと盛り沢山でした!)。 業務もわからないことや難しい内容が多く苦しいことも多々ありますが、それでも難しいことに挑戦をすることは楽しいですし、それが達成できた時の嬉しさは他の何にも勝ると思っています。 今後も精進して強いエンジニアを目指しつつ、ユーザーのみなさまにより良いプロダクトをお届けできるように頑張っていきたいと思います。 最後になりますが、LIFULLでは一緒に働いていただける仲間を募集しています。カジュアル面談もやっていますので、よろしければこちらもご覧ください。 hrmos.co hrmos.co
こんにちは。プロダクトエンジニアリング部の島村です。 今回は部署の垣根を超えてサービスの改修を進める「横断案件」の取り組みについて紹介します。 プロダクトエンジニアリング部の課題 プロダクトエンジニアリング部はLIFULL HOME'Sの開発を担当するエンジニアが集まった部署です。 LIFULL HOME'Sは「不動産・住宅情報の総合サービス」であり、多数の領域を扱っているため、それぞれの領域を扱うグループがそれぞれ存在します。 そのため、下記のような課題が存在していました。 各グループは基本的にそれぞれの担当領域の開発に専念するため、担当外領域に影響が及ぶ改修は調整コスト、学習コストが高い 部署ごとに個別の開発が行われるため、同じような仕組みを各部署で開発しているケースがあり、効率が悪い 多数の領域を扱う巨大なサービスであるがゆえ、対応優先度が上がらずに放置された技術的負債が存在する 上記のようにプロダクトエンジニアリング部では、領域をまたぐ横断的な改修を進めにくいという課題がありました。 また、同じ仕組みを実装しているケースなど、技術的負債になりそうな課題についても早めに吸い上げ、対応可否含めて検討をする必要がありました。 横断案件WG 上記課題に対する対策のため、プロダクトエンジニアリング部では横断案件WGを発足しました。 このWGでの活動は主に下記です。 メンバーに起票してもらった「横断案件」の実施に関する協議 実施中の「横断案件」の進捗確認 メンバーからは上がっていないが、WGとして「横断案件」として扱うべき課題に関する協議 横断案件WGでは各部署からメンバーを出し合い、横断案件を効率的に実施することを目的に活動しています。 横断案件の進め方 各部署のメンバーには、 「こんな取り組みを横断案件で進めてほしい!」 「これ技術的負債になりそう・・」 といった案件を事前にシートに記入してもらいます。 WGでは上記のシートを確認し、横断案件としての実施を協議します。 実施が決まった案件については、下記のステップで実施に向けて進めます。 実施することを決めた案件についてはWG内から担当者をアサイン その担当者が案件を具体化し、実際に案件を実施するメンバーを公募 実施するメンバーが無事見つかれば案件を進めてもらい、完了までWGがサポート 案件はメンバーに起票してもらったものがメインではありますが、WG内で「横断案件」として対応すべき重点項目を決め、その項目に関する案件を起票して進めることもあります。 活動の成果について 上記のような仕組みで今年1年活動し、下記のような実施結果となっています。 起票された案件:32件 実施した案件:23件 実施完了した案件:11件 例えば下記のような案件がこの1年で実施されました。 プロダクトごとに別々のリソースを参照していた祝日情報をAPI化して共通化 独自管理されていたアプリケーションを全社アプリケーション実行基盤「KEEL」に載せ替え 複数領域のプロダクトで問題となっていたキャッシュ機構のバグの解消 この1年は同じようなデータを参照しているのに個別管理になっているようなものを共通化する、特定領域の問題でないため、組織的問題で優先度が上がっていなかった負債/バグの解消が案件としては多かったです。 横断案件の課題と展望 この「横断案件」の取り組みですが、現状ではまだまだ上手くいっているとは言えない仕組みだとは思います。 案件を進める上での主な課題は下記です。 起票される案件がまだまだ少ない 案件を実施する実施担当者が見つからない 自部署案件との兼ね合いでどうしても優先度が劣後してしまう 特に実施担当者が見つからない問題は解決が難しく、担当者が決まらない場合には担当部署を決めた上で、部門長にアサインを委ねる等の試行錯誤もしてみたのですが、なかなか状況の改善には至っていない状況です。 仕組み的な面での難しさもあるため、全体的な仕組みの改善、インセンティブの強化等含め、今後改善を進めて参ります。 一方、しっかりと成果が出た案件も複数存在しており、特に若手のエンジニアが積極的に案件に挑戦してくれる姿は大変心強かったです。 このような案件をロールモデルとしつつ、彼らの利他の心に甘えすぎないような仕組みに仕上げ、LIFULL HOME'Sの開発に根付いた取り組みにしていければと思っています。 まとめ 今回は部署の垣根を超えてサービスの改修を進める「横断案件」について紹介しました。 横断案件WGで扱っている、調整コストの高い案件の対応や技術的負債への対応については、頭を悩ませる企業も多いのではないでしょうか。 LIFULLでは組織として優先度の高い課題は、専門チームの発足等で対応を進めているものの、どうしてもこぼれ落ちる課題は出てくるので、そうした課題にできるだけボトムアップで解決してゆければと考えています。 LIFULLではこのような課題解決にも興味がある、一緒に働く仲間を募集しています。よろしければこちらも合わせてご覧ください。 hrmos.co hrmos.co
LIFULLのプロダクトエンジニアリング部でエンジニアリングマネージャーをやっております野澤と申します。 もともとはゲーム用途で使われることの多かったDiscordですが、最近はIT系のイベントでは必ずといっていいほど使われるようになりました。LIFULLでも昨年導入され、各部署でDiscordを使って日々コミュニケーションを取っているようです。 コロナ禍になる前はオフィスで偶然出会った人と話せたり、連絡せずともふらっと会いたい人に会いに行って話せたりしていました。ところが、リモートワークだとどうしても自分が所属するプロジェクトやチームの人としかコミュニケーションしなくなりがちではないでしょうか。 そこで、私が所属するプロダクトエンジニアリング部ではDiscordを使った「ハンガーフライト」というコミュニケーション施策を講じてみたので少しだけご紹介できればと思います。 ハンガーフライト Okänd fotograf "Montering / underhåll av flygplan S 6 samt B 4 på CVM, Centrala verkstäderna Malmslätt. Arbete i hangar." Public Domain Mark 1.0 https://digitaltmuseum.se/021016832015/montering-underhall-av-flygplan-s-6-samt-b-4-pa-cvm-centrala-verkstaderna 飛行機を格納する倉庫のことをハンガーフライトと呼ぶそうです。 ところが、飛行機のパイロットたちが天候悪化などの理由で空を飛べないときに、ハンガーフライトに集まって飛行技術のコツなどについて雑談したり、情報交換をしていたことから、この行為自体をハンガーフライトと呼ぶようになったようです。 (詳しくは市谷聡啓さんの『 カイゼン・ジャーニー 』をご参照ください) コロナ禍の影響でリモートワークが中心になり、自部署以外のメンバーと話す機会が激減したため、ちょっとでも話す機会を作ろうと思ったのがきっかけで、私が所属する部署でもZOOMを使ってハンガーフライトを始めてみました。毎週決まった時間に自部署以外のメンバー含む数名でおしゃべりをしていました。 コロナ禍が長期化し、リモートワークが常態化しました。そのことによって、会社でも他部署の人と交流する機会が減ったことに対して課題意識が強まってきました。そこで、自分たちの周りでしかやっていなかったハンガーフライトを、エンジニア組織全体でやってみてはどうだろうと思い立ち、実施することにしました。 協力してくれる仲間とともに開催する時間を決め、Discordサーバーにエンジニア専用のチャンネルを作りました。最初はそんなに参加者も多くないだろうから、ざっくり「仕事について」「キャリアについて」「雑談」という3つのボイスチャンネルを作りました。ちょっとした仕事の息抜きや、誰かと喋りたいときに、興味のあるテーマのチャンネルに入っておしゃべりする、という感じです。 始めてみてどうだったか 8月の上旬から開始し、今のところ火曜日の16:00-17:00と、金曜日の13:00-14:00の週2回で開催しています。最初は数名しか参加してくれなかったものの、最近では大体10名くらいの方が参加してくれます。 普段あまり一緒に仕事をしないような人や、初めて話す人など、部署関係なくいろんな人が集まります。特に新卒や中途採用で入社された方々に喜ばれている印象です(まだ人数自体は少ないですが)。話を聞くと、入社してから直接会って話したことのある人は数名だし、自部署以外の社員と話すこともほとんどなかったからということです。 話の内容としては、やはり仕事のこと、最近の会社での出来事、キャリア、技術系のニュースなどについての話が盛り上がります。「好きな言語について話そう」とか最近参加したセミナーの感想、LIFULLに入社した理由や今後どういったことをやっていきたいか、などなど様々な話題で盛り上がります。 ただし、初めての人同士だとなかなか話すのも大変です。なるべく私も週一回は参加し、ファシリテーションをして、いろんな人の話が聞けるように努めています。そのうち特定のファシリテーターがいなくてもその場で自然に話せるようになるといいなと思っています。 ハンガーフライト以外の時間でも時々Discordに来て誰かが喋っているのを見かけました。弊社のCTOもちょくちょく顔を出しているようで、普段CTOと話す機会が無いようなメンバーも、CTOと直接話すことができているようです。 結果として、今まで相談しにくかった部署への相談がしやすくなったり、他の部署で実施しているコミュニケーション施策を別の部署でもマネしてみたりと、少しずつ業務改善にもつながってきています。最近施行されたセキュリティ施策の担当者が参加してくれて、みんなでその施策の内容について質問したり、解説してもらったりと予想していなかったような情報の共有が行われており、参加してくれた方のリピート率は非常に高いです。 今後の課題と展望 とはいっても、なかなか参加人数が増えません。まずは粘り強くエンジニア全員が入っているチャットルームで開催30分前にアナウンスをしたり、参加してくれた人にも身の回りの方を連れて一緒に参加してみて欲しい旨を伝え続けています。最近は多くの社内エンジニアが参加するイベントでも関連して告知をしたりしています。 弱くなってしまった社員同士のつながりを修復しようとしているので時間がかかるのは当たり前です。焦らず、諦めず、根気強くやっていくことが大事かなと思っています。ご参考になれば幸いです。 LIFULLでは共に成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは。テクノロジー本部基盤開発ユニット改善推進グループの花岡です。 改善推進グループでは技術的負債解消の一環としてLIFULL HOME'S で使用されているDBエンジンプロジェクトに取り組んでいます。 今回はDBエンジン移行プロジェクトにおけるSQL改修漏れを防ぐための取り組みについて紹介したいと思います。 DBエンジン移行プロジェクトの概要 LIFULLでは運用コストの削減や開発効率やパフォーマンス改善のためにOracle DatabaseからPostgreSQLへの移行を進めています。 両DBのデータを同期させつつアプリケーションのSQL改修を進めています。 ※ 以下ではOracle DatabaseをOracle, PostgreSQLをPostgresと呼びます。 プロジェクトの内容については以下の弊社ブログでも紹介されていますのでそちらもご覧いただければと思います。 www.LIFULL.blog www.lifull.blog DBエンジン移行の進め方について https://www.LIFULL.blog/entry/2021/03/24/151447 でも紹介されていますが、本プロジェクトでは以下のようにDBエンジン移行を進めています。現在は参照系SQLの移行を進めている最中です。 スキーマオブジェクトとアプリケーションの依存関係の洗い出し Postgresに移行先のテーブルを作成(AWS SCTを使用 OracleとPostgresのデータ同期(AWS DMSを使用 参照系SQLを移行 上記のブログでも言及されていますが、移行が必要なSQLの調査が不十分なことによるアプリケーションの障害が課題の一つとなっていました。 そこで今回は抜け漏れ検知のためのDBアクセス監視のしくみを構築することで移行漏れを早く検知できる様にしました。 DBアクセス監視のしくみ DBアクセス監視のしくみは以下の通りです。 Oracleにアクセスがあった際に検知してChatWorkへ送信して開発者が確認できる様にします。 Oracleの対象テーブルに監査ログ(以下Audit Log)を設定してアクセス状況をログ出力する。 ⬇︎ CloudWatch Logsのサブスクリプションフィルタで監視対象スキーマオブジェクトへのアクセスを検知してLambdaを起動する。監視対象スキーマオブジェクトはJSONで管理する。 ⬇︎ Lambdaでデータを整形してChatWorkへ送信する。 ⬇︎ ChatWorkでアクセス情報を表示する。 全体の構成は以下の通りです。 ここからはそれぞれの設定や処理内容についてもう少し詳細に説明していきたいと思います。 Oracle OracleへのSQLの実行を検知するために、Audit Logを出力する設定をします。 監査ログにはアクセス対象のスキーマオブジェクトや実行されたSQLの種類を出力できます。 対象テーブルの参照系SQLがすべてPostgres用に改修された段階で、以下のコマンドで対象テーブルに監査ログの設定を追加します。 AUDIT SELECT ON <対象テーブル>; この設定により監査ログが出力される様になります。 ログの内容についての詳しい説明は省きますが、このログでどのスキーマオブジェクトがアクセスされているかやスキーマオブジェクトに対してどのような操作があったのかを調査できます。 今回は監査ログからアクセスされたスキーマオブジェクト名を抽出します。 監査ログについて詳しく知りたい方はこちらも参照してください。 docs.oracle.com OracleでAudit Logを出力できる様にした後、Amazon RDS側でCloudWatchにログを出力する設定をすれば監査ログの設定は完了です。 CloudWatch Logs Audit Logを記録しているCloudWatchLogsのロググループにサブスクリプションフィルタをつけて、ロググループへのログの追加をトリガにしてLambdaを実行する様にします。 これで、ログのデータをLabmdaで受け取ることも可能になります。 サブスクリプションフィルタは以下のコマンドで追加します。 $ aws logs put-subscription-filter \ --log-group-name $LOGGROUP_NAME \ --filter-name ora2pos-notification-filter --filter-pattern "$FILTER_PATTERN" \ --destination-arn arn:aws:lambda:ap-northeast-1:$AWS_ACCOUNT:function:$FUNCTION_NAME \ --profile $AWS_PROFILE --region ap-northeast-1 サブスクリプションフィルタのfilter patternは監視対象のスキーマオブジェクトリストから抽出して作成します。 監視対象のスキーマオブジェクトリストは以下のようにJSONで管理しています。 { " objects ": [ { " schema ":" HOMES ", " object ":" object1 " } , { " schema ":" HOMES ", " object ":" object2 " } , { " schema ":" HOMES ", " object ":" object3 " } , ] } サブスクリプションフィルタでLambdaに送信するデータはBase64でエンコードされ、gzip形式に圧縮されます。 複合化したデータは以下のようになります。 { " awslogs ": { " data ": { " owner ": " xxxx ", " logGroup ": " xxxx ", " logStream ": " xxxx ", " subscriptionFilters ": [ " xxxx " ] , " messageType ": " DATA_MESSAGE ", " logEvents ": [ { " id ": " xxxx ", " timestamp ": 000000000000, " message ": " Audit Logの内容 " } , ] } } } サブスクリプションフィルタの設定方法について詳しく知りたい方はこちらも参照してください。 docs.aws.amazon.com Lambda Lambda側で受け取ったログから必要な情報を抽出して整形し、ChatWorkへ送信します。 Lambdaはgzip形式で圧縮されたデータを受け取るため、以下のようにしてログ情報を取得します。 const zlib = require( 'zlib' ); var payload = Buffer.from( event .awslogs.data, 'base64' ); zlib.gunzip(payload, function (error,result) { if (error) { console.log(error); } else { result = JSON.parse(result.toString( 'ascii' )); const logs = result.logEvents; const logGroup = result.logGroup; const logStream = result.logStream; ... SQLでアクセスされたスキーマオブジェクトを監視対象のスキーマオブジェクトリストと照合します。このリストはサブスクリプションフィルタのfilter patternに使用したのと同じ物を使用します。 照合が終わったら受け取ったデータを以下の形式に整形します。 これでアクセスされたスキーマオブジェクトを確認し、ログのURLから原因調査できる様にします。 oracle異常アクセスログ検知 異常アクセスがあるオブジェクト一覧: ... ... ... 以下のLogStreamを参照してください: <ログのURL> データを整形したらChatwork APIでChatworkに通知メッセージを送信します。 APIの形式は以下の通りです。 URL https://api.chatwork.com/v2/rooms/$CHATWORK_ROOM_ID/messages ヘッダ 'X-ChatWorkToken': $CHATWORK_TOKEN ChatWork APIについては、以下のドキュメントを参照ください。 developer.chatwork.com ChatWork ChatWorkでAPIを受け取ると以下のようなメッセージが表示されます。 モザイクで隠していますが、アクセスのあったスキーマオブジェクトとログのURLが表示される様になっています。 これでOracleのアクセスをChatWorkで確認するできるようになりました。 移行漏れ検出時の運用フロー ChatWorkでアクセスを検知した場合は以下のような運用フローで問題を把握して対処します。 ChatWorkでSQLの移行漏れを検出する。 ⬇︎ 監査ログの詳細(ログが発生した原因)を調査する。 ユーザーやアクセス対象のオブジェクト、どのサーバからアクセスされているか?SQLの種類は何か?を把握する。SQLの種類は SQL Command Codes を参考にする ⬇︎ ログ発生の原因や状況に応じて緊急度を判断して対応する。 原因・状況 緊急度 対応内容 手作業によるSQL実行など、移行漏れが原因でない場合 対応の必要なし - 移行漏れだが障害につながらない場合 緊急度低 ・対象のSQLを確認 ・移行計画を立て、対象のSQLを移行する ・移行後、ログが出なくなった場合はOKとする 移行もれによる障害が発生した場合(もしくは発生する可能性が高い場合) 緊急度高 ・至急で障害対応を実施する ・障害対応後、ログが出なくなった場合はOKとする これでSQLの移行漏れがあった場合に早く検知して対処するしくみを作り上げることができました。 まとめ 今回はCloudWatchやLambdaを使ったSQL検知システムについて紹介しました。 DBエンジン移行を行っている方にとって少しでもお役に立てれば幸いです。 まだDBエンジン移行プロジェクトは道半ばですが、一つ一つの課題に対して解決方法を模索しつつ進めています。 ここで紹介したのはその取り組みの一つですが、 これから発信できる様な取り組みがあれば引き続きブログで紹介していきたいと思います。ぜひ楽しみにしてください。 LIFULLではともに成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
検索エンジンチームの加藤宏脩です。 突然ですが、自分たちが行う施策をもっと雑談に近いところから決められたらいいなと思ったことはないですか? 私は気軽に話せる環境のほうがいろいろな意見が出やすく、またそういった話し合いから生まれるアイディアが意外と良いものだったりするのかなと考えています。 検索エンジンチームでは、「検索」というテーマを軸にラフな議論を行いその中から次にやる施策を決めるという方法を半年ほど続けています。 その結果、 現状のLIFULL HOME'Sが利用している検索エンジンの構成を前提にするのではなく試験的なユーザー体験をシームレスに提供・検証していく検索エンジンの環境を用意するプロジェクトができました。 LIFULLには「クリエイターの日」というイノベーションを創造するため、通常業務を離れ新たな技術や手法に取り組むための仕組みがあり、 こういった仕組みを利用して検索エンジンの開発をできるようにしたいというアイディアからこのプロジェクトが生まれました。 これ以外にもいくつかプロジェクトが徐々に進行し始めています。 今回はこの施策の作り方について紹介します。 気軽な会話から施策を作るとはどのようなことか? この活動は大きく以下の4ステップに分かれています。 思いついた課題や解決策をメンバーに共有するステップ テーマに対して課題や解決策を話し合うステップ 課題と解決をまとめて、できそうな施策を見つけるステップ 調査、設計、実装をするステップ 1. 思いついた課題や解決策をメンバーに共有するステップ ここではふと思いついたアイディアを忘れないようにメンバーに共有します 施策のアイディアについて話し合うためのslackのチャンネルがあります。 MTG中や休憩時間に思いついたアイディアや、約に立ちそうな情報をそのチャンネルに記入しています。 記入したアイディアや、情報は次の「テーマに対して課題や解決策を話し合うステップ」で話題の1つとして活用します。 2. テーマに対して課題や解決策を話し合うステップ ここではアイディアの発散をします 週に一度全員で話し合うMTGを設定します。 毎週一人のメンバーが「検索」を軸にしたテーマを用意します。(テーマを持ち寄るメンバーはローテーション) テーマ発表後、そのテーマについて課題や解決策を各メンバーが感じたままに挙げていきます 私達の場合、slackにテーマを記入して、スレッドにコメントで書いていますが、もう少しやりやすい方法もあると思います。 このステップを全メンバーがテーマを持ってくるまで繰り返します。(5人のメンバーであれば5週かけて行う) 3. 課題と解決をまとめて、できそうな施策を見つけるステップ ここではアイディアの収束を行います。 全員がテーマを出し終えた次の週のMTGで全員が出した課題、解決策をグルーピングします。 LIFULLではコンフルエンスを使っており、そこに今までのアイディアのまとめページを追加します。 各メンバーが、まとめから実施したい施策を選びとります 4. 施策としてすすめるステップ ここではアイディアを実現させていきます 他のプロジェクトと同様に製品要求仕様書(PRD)を作って調査、設計、実装を行います。 MTGの最後の時間に、進捗を共有します。 似ているところをやっていれば柔軟にメンバーが合流したり、途中で分裂したりします。 なぜこの取組みを始めたか もともと検索エンジンチームは運用に時間を取られてしまい、利用者への価値提供に時間を使えていないという課題がありました。 これを解決するために、運用方法の変更、自動化、手順化を行っていました。 運用コストを下げた結果、利用者への価値提供に時間を割けるようになってきたという経緯があります。 この方法で進めてよかったこと LIFULLの知識が増えました 私のようにLIFULLでの経験の浅いメンバーにとっては、今まで通りの施策の仕方だけでは得られない知見が溜まっていきました(部署外で運用しているデータやサイト、検証方法等) チームメンバーが考えている課題感を知ることができました 開発者がSolrの開発に苦手意識を持っており、実現しなかった事業案があることがわかりました(調査の際の副産物ですが) 開発者に対するSolrの勉強会の開催を積極的に行うようになり、社内に対してもいい影響を与えています この方法で進めて課題だと感じているところ ジャストアイディアで話しているため、データがなくてこれ以上話が進まないというときがあります MTG中に社内の担当の方に聞いて解決することもありますが、どうしても限界があります。 まとめ 今回は検索エンジンチームでの取り組みについて紹介しました。 雑談のような気軽な会話の中で出てきた考えは今までとは違う視点からの新しいアイディアを生み出す機会にもなります。 また、ボトムアップに施策を作ることは会社のことを知り、考えるきっかけにもなります。 ちなみにLIFULLではこういうコミュニケーションから新たなことを始める取り組みを色々なところで行っています。 例えば月に一度、部内の他チームと雑談をするという機会があり、このような雑談から新たな目的や構想を誘発すると考えております。 カジュアル面談もやっていますので、LIFULLやLIFULLの取り組みに興味がありましたらぜひお話しましょう! ここまで読んでいただきありがとうございました。 hrmos.co
こんにちは。 プロダクトエンジニアリング部の吉永です。 本日はLIFULL HOME'S におけるLINEを活用した施策「 LINEで新着物件通知を受け取る 」について機能改善を行ったプロセスとリリース内容について紹介したいと思います。 プロジェクトメンバーが以前投稿した下記記事も一緒にご覧いただけると幸いです。 www.lifull.blog www.lifull.blog アジェンダ LINEで新着物件通知を受け取るとは どのような機能改善を行ったのか 機能改善に至るまでのプロセス まとめ LINEで新着物件通知を受け取るとは LIFULL HOME'SにおけるLINE活用 #1 LINEで新着物件通知を受け取る の再掲にはなりますが、改めて本機能についてご紹介いたします。 その名前のとおり、 毎日決まった時間に希望に沿った新着物件をLINEでお知らせしてくれる機能 になります。 新着物件を受取るまでのフローを簡易的にあらわすと下記のようになります。 LIFULL HOME'Sで任意の条件で物件を検索する。 検索結果画面から「LINEで受取る」ボタンを押す。 LIFULL HOME'S公式LINEアカウントを友達登録する。 翌日以降に、登録した検索条件に合致した新着物件情報がLINEで通知されるようになる。 ※LINEで受取るまでの詳細なフローについては こちらのページ をご参照ください。 また、開発に携わったエンジニアとして、個人的に新着物件通知機能で良いなと思っている点を紹介すると下記3点になります。 希望条件を決めて、新着情報受け取り設定をしておけば、 自分からサイトを見に行かなくても 新着物件情報を通知してくれる。 通知情報である程度の概要情報(賃料、所在地、最寄り駅からの徒歩分数など)が把握できるので、 本当に気になった物件のみ をサイトに見に行けば良い。 普段から 使い慣れているLINEアプリ でこれらの有益な情報を受取れる。 この機能を開発すると決まった際に、ちょうど自分も住み替えを検討していたので、素直に「 この機能使いたいな、欲しいな 」と思ったことを覚えています。 どのような機能改善を行ったのか 新着情報受け取り設定の延長機能 初期リリースでは新着情報受け取り設定を行ってから、30日後には自動で新着情報の配信が停止される仕様でした。 よって、31日目以降も新着情報を受け取る為には、物件の検索画面上からもう一度新着情報受け取り設定を行う必要がありました。 今回自動で停止する仕様はそのままなのですが、自動で配信が停止されていることに気づいていない方もいるであろうということから、29日目に「明日で配信が停止しますが、引き続き配信を受取りますか?」というお知らせとともに、LINEトーク画面上から手軽に延長を行えるボタンを配信し、 利用者が簡単に配信受け取りを延長できる機能 を追加しました。 同一物件情報を繰り返し送らないようにする 初期リリースでは、配信受け取り設定後、登録した希望条件で検索した結果から1週間以内に登録された物件情報の新着順からLINEで新着通知メッセージを送っていましたが、希望条件によっては検索結果が少なく、毎日同じような情報が送られてきてしまう方も一部いました。 今回はこちらの改善をする為に、一度通知した物件情報は繰り返し送らないようにして、 LINEで通知が来た時の「また同じ情報か...」というがっかり体験を無くす ようにしました。 同じ日に同じ建物の部屋情報を送らないようにする 上記の改良と合わせ、なるべく受け取る情報が有益なものになるように、 同じ建物の部屋情報を同一日には通知しない ようにしました。 物件によっては同じ建物の別々の部屋が同時に空き部屋として登録されることもあり、初期リリースでは通知されるメッセージの物件1件目が101号室、2件目が201号室などのように同じ建物の別の部屋が並ぶことがあったので、なるべく多くの物件情報を新着情報として届けたいとのことから、この機能を追加しました。 物件情報で表示する情報を追加 初期リリースでは、物件名、所在地、最寄り駅までの徒歩分数、賃料、物件画像などをLINEで通知していました。 今回の改修で、物件が持っているこだわり条件(2階以上、角部屋など)合致情報を元に、 こだわり条件をカテゴライズした情報を表示 するようにしました。 例えば、新着通知としてお知らせする物件が「2階以上」もしくは「南向き」などのこだわり条件と合致している場合には 日当たりを良くしたい というカテゴリ情報をテキストで、物件情報と合わせて表示するようにしました。 登録した希望条件に加えて、こだわり条件を分かりやすく伝える情報も同時に見せることで、物件を決める際の判断材料の一つになればいいなと思い、この機能を追加しました。 機能改善に至るまでのプロセス 続いて、上記の機能を実際に開発するまでにどのようなプロセスを経たのかについて紹介したいと思います。 初期リリース直後 まずは初期リリース後に開発に携わったステークホルダーでKPTを用いた振り返り会を開催しました。 振り返り会の内容は、 プロジェクト進行において良かったこと、悪かったこと、次回トライしてみたいことに着目しての振り返り会 だったので、具体的に機能改善したいことについての言及はあまりありませんでした。 初期リリースから1か月後 このプロジェクトに直接的に関わった、関わっていないに限らず、プランナー、デザイナー、エンジニアでオンラインホワイトボードツールを用いての機能改善案のブレストを実施しました。 ブレストでは改修にかかる工数や、実現可能性についての考慮はせず、ひとまず各々が、 こんな風になった方が良いよなと思う機能 を次々と上げ、それぞれのアイディアをグルーピングするところまでを行いました。 初期リリースから1か月半後 ブレストで出たアイディアの具体的な実現案や、UX向上の為の改善案などをプランナーとエンジニアで出しあい、おおよその改修イメージを皆で共有しました。 また、開発工数についてもチーム内のエンジニア間で認識を統一したかったので、プランニングポーカーを行い、 おおよその開発工数イメージの共有 をプランナーとエンジニア間で行いました。 初期リリースから2か月後 プランナー側で、 利用者アンケートでいただいたご意見やプランニングポーカーで算出されたポイントを元に改修案の優先度付け を行い、今回の機能改修を実施しました。 まとめ 今回は「LINEで新着物件通知を受け取る」機能と、LIFULL内でどのような流れで機能改善を行っているのかについて紹介させていただきました。 機能改善するまでのプロセスについてはプロジェクトやチーム毎に異なるので、この方法がLIFULLのスタンダードというわけではないのですが、社内で一つだけ共通していることがあるとすれば、 我々は職種に関わらず常に利用者目線で物事を捉え、自分ごと化して考えるようにしている という点だと思います。 LIFULLの経営理念である 常に革進することで、より多くの人々が心からの「安心」と「喜び」を得られる社会の仕組みを創る を実現する為に、私たちはLINEに限らずあらゆるチャネルを通じて感動体験を創出できるように日々プロダクト改善に取り組んでいます! 「LINEで新着物件通知を受け取る」機能についても、今後更に機能改良していくつもりですので、アップデート情報を楽しみにお待ちいただけますと幸いです。 最後に、LIFULLでは共に成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
LIFULLプロダクトエンジニアリング部の堀です。具体的にはLIFULL HOME'Sの開発を行っています。私はその中でも賃貸領域の開発を担当するグループに所属しています。 今回は、エンジニア・企画・デザイナー3職種混合チームで、少し今までとは違った形でスクラム開発に取り組んだのでその話をしたいと思います。 テーマは「3職種上流」 です。 前提 本記事の立ち位置 これまでの課題感 3職種上流で効率良い開発 3職種上流とは 半期の目標設定 施策検討 施策ブレスト会 UT(ユーザーテスト)実施 デイリースクラム 開発効率の向上 スプリントスケジュール まとめ 前提 開発体制などはチームの構成や仕組みによって大きく変わるので、まず前提として私が所属しているチームの説明を行います。 我々の部門はLIFULL HOME'Sの賃貸開発を担当しており、今期は5つのチームに分かれています。その中でも私はプロダクトのグロースチームに所属しています。 チームのメンバーはエンジニア4人、企画2人、デザイナー1人という構成になっていて、エンジニアの1人がSM(スクラムマスター)を兼任しています。 プロダクトオーナーと呼ばれる存在はチーム内には存在せず、プロダクトの方向性やバックログ施策の妥当性は企画会議で判断されます。 グロースチームはとあるプロダクトに対してKPI目標を持っており、我々はその目標を期を通して達成することを使命として動いています。 また、各スプリントは2週間単位で設定されています。 本記事の立ち位置 本記事は私が所属している1チームの動き方にフォーカスを当てて書いておりますが、今期はチーム間の調整や全体でのバックログの運用などにも部門を上げて取り組んでいたので、そちらの内容は以下の記事を参考にしてみてください。 www.lifull.blog また、ここではスクラム中の具体的な動き方を説明していますが、「他職種に期待していること」をチーム内で擦り合わせて職種間の連携を強める取り組みを別部門の方が最近記事にしていたので、そちらも是非ご覧ください。 www.lifull.blog これまでの課題感 これまでのスクラム開発でも、メンバー構成や目標の持ち方はほぼ同じでした。 我々が感じていた課題感は、特に各施策の仕様調整に関してです。基本的にバックログに追加する施策は企画の方が施策案を出し、企画全体の会議で決定し、仕様作成後に優先順位付けを行った状態でバックログに追加していました。そして計画MTGで各エンジニアにアサインを行います。 開発を始めると、仕様に関して疑問を持ったりもっと良い方法を思いついたりすることが良くありました。また仕様決定後にデザインをデザイナーが行うのですが、その時に「伝わりにくさ」などUX観点での課題点を発見されることもあり、その度に仕様調整が生じていたので少し効率悪さを感じていました。 3職種上流で効率良い開発 3職種上流とは これまでの課題を解決するために、施策立案から仕様決定までをエンジニア・企画・デザイナー3職種全員で行おうという取り組みを我々は3職種上流と呼んでいます。 半期の目標設定 従来はリーダー達が各チームの追う目標を設定し、メンバーはそれを自分の中に落とし込んで期の始まりを迎えていました。それに対して今期は3職種上流を実現するために、まずチームが結成された期初に、各チーム毎に半年で追うアウトカムの設定を行いました。目的としては 同じ方向を向いて走り出すことができる状態にする それぞれで自律して目標のために動ける状態にする 以上2点です。 今、期が終わろうとしているところですが振り返ってみると、この目標設定はとても大事だったなと改めて感じました。弊社はKPIマネジメントに取り組んでいるので、部として大きなKPIは持っているのですが、そのKPIを達成するための方法は各チームに委ねられています。これをメンバーで決めることで、期を通して3職種全員でしっかりと目標を見据えて動けたなと思います。 施策検討 施策ブレスト会 目標を達成するための施策検討は、これまで企画職がメインで行っていました。しかし上に挙げた課題感があったので、職種関係なく施策案出しから入るようにしました。大体、週1時間のペースで施策ブレスト会を開催し、次以降のスプリントで実施する施策の大まかな仕様決定まで行いました。 エンジニアが上流から入ることで、より工数の少ない方法や別の実現方法などアイデアの幅が広がったり、デザイナーが入ることで 施策そのものに対しユーザーの視点を反映させることができたりします。また仕様作成の時点でデザイナーがビュジュアル的に可視化できるので、他職種もより仕様を理解しやすくなり3職種上流を加速させることができます。 UT(ユーザーテスト)実施 UXプロダクトを作っていると数値分析は行っているので課題となっている部分は分かるのですが、「なぜユーザーは離脱するのか」「何が迷わせているのか」等の原因が作っている側の目線だと分からない事が多々あります。それでは3職種集まっても有意義な施策ブレストは行えません。 そこで我々のチームはUTをチーム内で行い、課題の原因を探るようにしました。UT分析から見えてきた原因をブレスト会に持ち込むことで施策の案出しの材料とする事ができました。 弊社にはUTを専門にしている部署もあるのですが、様々なプロダクトを見ているので実施回数に限界があります。そこをチーム内で行うことでPDCAを早く回せるようになりました。UTを行うことで課題感が見えるだけでなく、施策の効果も実際の動きを見ながら確認できるのでモチベーションにも繋がります。ただ、チーム内で実施するとメンバーの工数が嵩むので、UTチームと連携を取って臨機応変に担当範囲を決めたりなど工夫すると良いと思います。 チームで設計や分析の参考にした書籍を参考に置いておきます。 http://www.amazon.co.jp/dp/4274226719 www.amazon.co.jp デイリースクラム 施策ブレスト会で実施が決まった施策は、企画が細かい仕様決定を行い、デザイナーがデザインを作成します。途中で悩むポイントが出てきた場合や相談が必要な場合は、チームメンバーが揃っているデイリースクラムで相談を行います。 そこでの相談ではfigmaやXDなどのツールを使って可視化されたものをベースに全員で議論するので、視覚的にも想像しやすく深い議論が可能です。必要に応じてエンジニアがプロトタイプを作成し持ち込むこともあります。 開発効率の向上 従来は課題の発見から仕様に落とし込む部分までを企画職が行い、その後にデザイン・実装・リリースと続く形が取られていました。この場合だと上流工程はウォーターフォールのようになっており、結果が出るまでに時間がかかります。 しかし、上に述べたようなUT・施策ブレストを通して3職種全員で課題発見からアイデア出しまでを行い、全員の認識が揃った状態で仕様作成・実装・デザインなどを同時並行で行うことによって、リリースまでの工程が短くなり効率よく開発を進めることが可能になります。 また、全員の認識が揃っている状態なので、相談や議論が必要な際はそれぞれの職種の観点でスムーズに意見を言い合うことが可能です。 開発効率の向上 スプリントスケジュール 参考になるかも知れないので、スプリントのスケジュール例を記載します。 スプリントスケジュール リリーススケジュールの関係で、木曜日がスプリント区切りになっていて、計画MTGと振り返りMTGを行います。 計画MTGでは主に施策アサインとストーリーポイントの設定を行います。 施策の優先順位付やバックログの整理は基本的にSMと企画職で水曜日のバックログリファイメントの時間に行います。 上で述べた施策ブレスト会は月曜日に行うようにしていました。 まとめ 今回、3職種上流でスクラムを戦ってみて、エンジニア目線で開発のやりやすさは勿論、目標にコミットしようと思う意欲や1つ1つの施策に対する思い入れも以前より増えたかなと思いました。 実際のユーザーの動きを見て、チーム内で打ち手を考えることでなぜその施策をやるのか、ユーザーにどうなって欲しいのかが自分の中にもチームの中にもしっかりと落とし込めたと思っています。 3職種上流はどの職種であっても、チームの目標や各施策によりコミットしていく動きの一つだと思っているので、今後もより良い開発体制を目指していきたいと思います。 LIFULLでは共に成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは。LIFULLでiOSアプリケーションの開発を担当している山手です。 LIFULL HOME'S iOSアプリは2009年12月にリリースされて以来、約12年ほどサービスを継続しておりプロジェクト規模は年々大きくなっています。 新機能導入や既存機能の改修などを重ねるにつれてアプリのビルド時間が延び、 エンジニアの作業時間が圧迫、開発効率が下ってしまい満足した状態で開発を行うことが難しくなります。 そんな状況を回避すべく、自動化して任せられる部分は任せてエンジニアは開発に専念できる環境を作るために、CI/CDサービスのBitriseを2018年6月頃から導入し約3年ほど利用しています。 今回は、規模の大きくなったLIFULL HOME'S iOSアプリでBitriseを利用する際に直面した問題や事例などをご紹介致します。 Bitriseとは Bitriseはモバイルアプリ開発に特化したCI/CDプラットフォームを提供してくれるサービスです。 ビルドの一連の流れを「ワークフロー」という形で作成できます。 作成したワークフローに対してトリガーを設定することで、トリガータイミングで自動的にワークフローが走り始めます。 また、これらの作成はGUI上で設定することができ、簡単にCI/CD環境を構築することができます。 HOME'S iOSアプリにおけるBitrise(CI/CD)の役割 テスト、及びLint Checkの実行 Pull Requestを立ち上げたタイミング、またはPRにコードをPushしたタイミングでテストとLint Checkを実施し、結果をPRにコメントします。 テスト用アプリの作成、及び配布 テスト用アプリを作成し、インストール用リンク(Bitrise.io)のQRコードをSlackに通知します。 App Store Connectへのデリバリー masterブランチからReleaseブランチへPRを出した時に、App Store Connectへのアプリのデリバリーを実施します。 CIを止めるビルド時間 最新の料金プランでは、ビルド時間の上限は設けられていませんが、以前の料金プランでは45分または90分のビルド時間の上限が設けられており、ビルドに時間がかかるプロジェクトの場合はCIが完了する前に強制終了されてしまうことがありました。 画像は以前のBitriseの管理画面のスクリーンショットです。 ※一部画像を加工しています 現在は契約プランを見直し上限時間が90分になっていますが当時は45分のプランであったためある時期を境にすべてのPull RequestのUTが通らないという問題が生じました。 主な原因としては、モバイルデータベースのRealmのビルド時間が影響しています。 45分のビルド時間の内、約14分がRealmのビルド時間に専有されてしまいUTが走る前に強制終了されてしまっていたのです。 当時はCocoaPods経由で、Realmを利用していました。CocoaPods経由で利用していた理由は、Realm Framework自体のサイズです。 Carthageを用いればビルド済みのFrameworkをリポジトリに含めることもできるのでビルド時間を短縮することが出来ますが、Realm Frameworkのビルド後に得られるFrameworkのファイルサイズが100MBを超えてしまいGitHubにPush出来ませんでした。 一方CocoaPodsは、都度ビルドしてFrameworkを生成します。リポジトリにはPodfileのみ含めることで、開発者が複数人いてもPod installを実行するだけで利用しているFrameworkを手元に導入することが可能です。 これ以外にもCarthage + Git LFSを用いて、ビルド済みのFrameworkをGit LFS側に置くなどの案もありましたが費用などの問題から当時の開発チームではCocoaPodsでRealmを扱うこととしました。 Realm導入から時が流れ、利用するFrameworkなども増え次第にビルド時間が肥大化し続けたことで、ついに契約プランの上限時間を超えるようになってしまいました。 大きな要因としては、Apple Watch向けの新機能でもRealm Frameworkを利用したことが影響しています。従来iOS向けのビルドだけだったものがwatchOS向けのビルドも必要になったことでビルド時間が大幅に伸びてしまいました。 このままCIが回らない状況が続くとBitriseの月額費用が無駄になってしまうことからiOSチームとしてはCI環境上でのビルド時間短縮が急務でした。 かつ追加のコストを大きく掛けずに問題を解消するということも求められました。 Carthageを活用する CarthageにはGitHub上にアップロードされているビルド済みのFrameworkを利用する機能が備わっています。 この機能を利用するにはFrameworkをビルドしたXcodeのVersionを揃える必要があります。 今回はこの機能に着目しCI環境上でのビルド時間を短縮してみることにしました。 賛否両論ある問題ですが、今回問題になっているRealm関連のCarthageでのビルド後得られるファイルを.gitignoreでgitの追跡対象から除外します。 Realm以外のCarthage経由で導入したFrameworkは、リポジトリに含めるように変更しています。 # Realmはbinaryを再利用させるため、追跡から除外する */Carthage/Build/iOS/**Realm**.framework/ */Carthage/Build/iOS/**Realm**.framework.dSYM/ */Carthage/Build/iOS/**Realm**.framework/ */Carthage/Build/iOS/**Realm**.framework.dSYM/ この変更によりリポジトリをクローンした直後、Realm以外の必要なFrameworkは得られる環境が出来上がります。 リポジトリクローン後、下記のコマンドを実行しRealmのみ取得します。 $ carthage update --platform " iOS, watchOS " realm-cocoa 実行時、Cartfileで指定したRealm Frameworkのバージョンに紐づくビルド済みのFrameworkをビルドしたXcodeのバージョンがCI環境で利用しているバージョンと一致する場合は、ビルド不要でFrameworkを利用することができます。 これによってCI環境上で約14分ほどかかっていたビルド時間を約2分まで短縮することに成功しました。 導入後のデメリット ビルド済みのFrameworkをビルドしたXcodeバージョンでないバージョンで、アプリをフルビルドするとFrameworkも再ビルドの必要が生まれてしまいビルド時間が延びてしまいます。 そのため、チーム内で利用するXcodeのバージョンを上げる際にはRealmのバージョンも見直す必要が生まれてしまいました。 通常の機能改修や新機能追加では大きな支障にはならないものの、毎年9月頃に行われるXcodeのメジャーアップデートの際は、Framework側が最新のXcodeに対応することを待たないとCIが利用できないという点が難点です。 このようなデメリットも伴うので、こんな方法あるという事例として頭の片隅に残していただければ幸いです。 デメリットを運用でカバーする デメリットを許容してしまうとXcodeのメジャーアップデートが無い限りXcodeのバージョンとFrameworkバージョンが固定化されてしまうリスクが生まれてしまいます。 古いバージョンでも開発を継続することは出来ますが、Framework内で利用しているiOSのメソッドの非推奨や廃止などによってFrameworkのバージョンアップが急遽求められてしまう場面などが生まれます。定期的なアップデートを行う体制を整えることで余裕を持った対応も可能になり、新しい機能などの導入も積極的に行うことが出来ます。 現在iOSチームでは、定期的なFrameworkのバージョンアップデートを行うことで、バージョン固定化のデメリット・リスクを低減しようとしています。 Bitriseでライブラリバージョンの更新を監視する 今回は、ライブラリのアップデートを確認しSlackに通知するのワークフローを用意し定期的にワークフローを実行することで、ライブラリバージョンの更新を監視するようにしました。 現在は、CocoaPodsで管理しているライブラリのみを対象にしています。 これを実現するための簡単な手順は以下になります。 CocoaPods管理下のライブラリの最新バージョンを pod outdated を用いて、リストアップする リストアップした中から、ライブラリとバージョンについて記載されている各行を抽出する 抽出した結果をSlackに通知する 実際に作成したワークフローが動作し、届いたSlackへの通知が下記になります。 iOSチームでは、金曜日に次週のタスク設定を行うミーティングがあるので毎週金曜日に pod outdated の結果を通知しています。 これによりミーティング時にFrameworkの更新状況を確認し、チームメンバー全員でどのFrameworkのアップデートに対して優先的に対応を行うかなどを話し合える機会を作っています。 今後の展開 現在はSlackへの通知に留まっており、アップデート作業などは手作業に頼っています。また、監視対象もCocoaPodsで管理しているFrameworkに留まっています。 今後は、Frameworkのアップデートの必要が生まれれば自動的にPull Requestを自動生成する仕組みやCarthageで管理しているFrameworkも含めて通知できるような仕組みづくりを進めていく予定です。 終わりに 長年開発が続けられてきたプロジェクトのため随所にレガシーな運用であったり、処理が残っています。 日々アプリを利用してくださる方に対して、より良い価値を素早く提供し続けるために、その時その時でチームに合った運用の改善や仕組み作りを行い続けることが大切です。 iOSアプリを通じた住まい探し体験を今以上により良くするためにも、開発のしやすさやアプリの品質向上につながる改善はチームとして継続的に改革し続けていきたいと考えています。 LIFULLでは一緒により良い体験を作っていける仲間を募集しています。よろしければこちらも合わせてご覧ください。 hrmos.co hrmos.co
検索エンジンチームの宮崎です。 今日は、Solr内部でも使用されている全文検索アルゴリズムの転置インデックスについて話をしようと思います。 転置インデックスの仕組みについてざっくり理解したい人の手助けになれば幸いです。 全文検索アルゴリズム 全文検索の方法として大まかに 「grep型」と「インデックス型」があります。 多くの検索エンジンや全文検索ライブラリでは、インデックス型が使われています。 これはgrep型が都度すべての文書を検索するのに対して、インデックス型はその名の通り索引を用いて効率的に検索を行うことができるためです。 インデックスのアルゴリズムもいくつもありますが、今回は apache/solr・apache/luceneでも使用されている転置インデックスについて、簡単な例を用いて解説しようと思います。 今回は転置インデックスを使用した簡単な例として、「 google/codesearch 」を使用します。 転置インデックス関連の基礎知識 実際の例を見ながら説明する前に、説明する上で必要な基礎知識について触れておきます。 ポスティングリスト ポスティングリストとは、辞書の各単語がどの文書に出現するかを保存したものです。 キーを単語、値を文書の配列とした連想配列をイメージするとわかりやすいかと思います。 検索を行う際は検索クエリに含まれる単語ごとにポスティングリストを取得し、その積集合を取ることで検索クエリに含まれる単語を含んだ文書を取得することができます。 以下のようなポスティングリストがあった場合、「search」の単語が含まれているのは「doc1, doc2, doc3」であることがわかります。 「search」「engine」の両方の単語が含まれているのは「doc1, doc2, doc3」と「doc1, doc5」の積集合なので、「doc1」のみとなることがわかります。 このように、単語に対して文書が保存されているものを文書レベルのポスティングリストと呼びます。 search: doc1,doc2,doc3 searcher: doc1,doc2,doc3,doc4 engine: doc1,doc5 ... ただし、「search engine」のように連続している単語を検索する、フレーズ検索を行う場合はどの文書に出現するかだけではなく、出現する位置も同時に保存する必要があります。 このように出現する位置も保存されているものを単語レベルのポスティングリストと呼びます。 検索を行う際は、検索クエリに含まれる単語ごとにポスティングリストを取得し、その積集合を取り、取得した文書の出現位置が「search engine」のように隣り合っていものだけを取り出すことでフレーズ検索を行うことができます。 実際の転置インデックスの中身 それでは、codesearchのインデックスの中身を実際に追っていきましょう。 Index構造体の中身がほぼそのままインデックスファイルに対応します。ファイルの構造は以下のようになっており、バイナリフォーマットで保存されています。 index format: "csearch index 1\n" # magic list of paths list of names list of posting lists name index posting list index offset of path list # 4bytes offset of name # 4bytes offset of posting lists # 4bytes offset of name index # 4bytes offset of posting list index # 4bytes "\ncsearch trailer\n" # trailer magic それぞれの項目について説明していきます。 list of paths は、ファイル名もしくはディレクトリ名が、NULL終端(\x00)で保存されています。 空文字で終了します。インデックス作成の際に指定したパスが保存されます。 list of names は、ファイル名がNULL終端(\x00)で保存されています。ファイルにはそれぞれファイルIDが割り当てられており、ファイルID順にソートされています。空文字で終了します。実際のファイル一つ一つの名前が格納されています。 list of posting lists は、ポスティングリストがtrigramとファイルIDの差分のペアで保存されています。ファイルIDは32-bitの整数で表現されていますが、全てをそのままエンコードすると無駄が大きくなるので、ファイルIDの差分であれば大体小さい値になり、効率的にエンコードできることを利用するため、ファイルIDの差分を保存しています。 詳細が気になる方は https://pkg.go.dev/encoding/binary のuvarintについて見てみてください。 name index は、ファイルIDに対応する名前へのオフセットが保存されています。名前は可変長なのでファイルIDに対応する名前を探索する際には線形探索を行う必要があります。しかしオフセットを持つことによって固定長のデータになるため、 name index のファイルID*4のオフセットの場所を参照するだけで、名前への参照を取得することができるので探索する必要がありません。 posting list index は、単語に対応するドキュメントの数と実際のポスティングリストへのオフセットが保存されています。ポスティングリストは可変長ですが、このように持つことによってポスティングリストインデックスが固定長となるので、検索時に検索クエリに含まれる単語を2分探索で効率的に取得することができます。 name index や、 posting list index で使われた、オフセットを扱って固定長にすることで探索を効率的に行うことはよく行われるようです。 あとはそれぞれの項目に対するオフセットが4バイトずつ保存されています。 実際にはバイナリで保存されていますが、イメージとしては以下のような感じです。L数字 は抽象化して行数を表しています。実際のインデックスにおいては行数ではなく、オフセットが保存されています。 また、実際にはそれぞれに改行は無く、 list of paths のようなラベルもありません。 L1: csearch index 1 list of paths: L2: /opt/codesearch list of names: L3: /opt/codesearch/AUTHORS L4: /opt/codesearch/CONTRIBUTORS ... list of posting lists: L30: aaa 1,2,3 L31: aab 1 L32: aac 1,2,4,5 L33: aad 1,2,3,5,6,8,9,10,11 ... name index: L50: 0 # list of namesのオフセット(L3)からのオフセット(=0)が保存されます L51: 1 ... posting list index: L77: aaa 3 0 # list of posting listのオフセット(L30)からのオフセット(=0)が保存されます L78: aab 1 1 L79: aac 4 2 L80: aad 9 3 ... offsets: L97: L2 # list of paths L98: L3 # list of names L99: L30 # list of posting lists L100: L50 # name index L101: L77 # posting list index csearch trailer 検索に必要な処理 転置インデックスの基本的な要素についてはこれまでで話したとおりです。 これから検索に必要な処理について見ていきます。 検索時に必要な処理をまとめると以下の処理になります。 クエリの構築処理 インデックスのオープン処理 検索処理 ファイル名の取得 実際の検索クエリを作るために「クエリの構築処理」が必要になります。 バイナリフォーマットのインデックスをパースして、検索時に使用できる形に展開するために「インデックスのオープン処理」が必要になります。 クエリを元に、実際にインデックスから該当のドキュメントを取得するために「検索処理」が必要になります。 検索処理の結果、ファイルIDが取得されるが、実際のアプリケーションで活用できる形にするために、ファイルIDから「ファイル名の取得」ができる必要があります。 それぞれの処理について、codesearchの具体的なコードを見ていきます。 クエリの構築処理 codesearchは正規表現で検索できるように、正規表現から出現しうる文字列を trigram に変換しています。 ここも一つのトピックとして面白いですが、今回話したい転置インデックスの本質とはずれるのでここでは割愛します。 インデックスのオープン処理 インデックスのオープンではインデックスファイルのパースを行い、以降の操作に必要なそれぞれへのオフセットの取得や、オフセットの差分から必要な統計値を計算します。 インデックスのOpen時にはインデックスファイルをmmapし、ファイルの最後の部分からオフセットを順に読み出します。 オフセットはすべて4バイトで保存されているので固定サイズを指定することでオフセットを取得することができます。 https://github.com/google/codesearch/blob/8ba29bd255b740aee4eb4e4ddb5d7ec0b4d9f23e/index/read.go#L97-L112 検索処理 クエリの構築処理で変換した trigram を元にポスティングリストを取得します。構築したクエリはすべて trigram で表現されているため、3文字より長い文字は AND などを使って結合されています。 Hello を検索する場合は以下のようなクエリになります。 Hel AND ell AND llo なので、"Hel", "ell", "llo" のポスティングリストを取得し、それらのドキュメントIDを AND の条件に従ってマージします。 ファイル名の取得 ファイル名は、 list of names のセクションに保存されていますがオフセットが直接はわからないので、 name index のオフセット+ファイルID*4バイト目に書かれているオフセットを元に list of names からファイル名を取得します。 これで検索は完了です。 おわり 実際のインデックスの中身やインデックスを用いた検索について見てきました。検索エンジンが内部でどのようなことを行っているか、より具体的にイメージできるようになれれば嬉しいです。 次回はインデックスの構築編でお会いしましょう。
フロントエンドエンジニアの嶌田です。今回が LIFULL Creators Blog への初めての投稿です。 「サービス共通ヘッダ・フッタ」は、ただのヘッダ・フッタではありません。ソースコードはいくつものサイトやサービスで使いまわされます。組込み先が持っている CSS によっては表示が崩れてしまうかもしれません。ブレークポイントやコンテンツの幅がそろわないかもしれません。サービス共通で使えるヘッダ・フッタには相応の強さや柔軟さが求められます。 この記事では、LIFULL HOME'S のサービス共通のレスポンシブ版ヘッダ・フッタを実装するために動員した「強く・堅牢に実装するためのノウハウ」を紹介します。 どこにでも組み込めるように実装する 重複しないクラス名ルールを設定する 詳細度や継承とうまく付き合う プレーンな技術を使う ブレークポイントや z-index 等をカスタマイズ可能にする html { font-size: 62.5% } に対応する さらに実装品質を高める テキスト量の変動を考慮する 上部固定ヘッダとページ内リンク先の要素を被らないようにする 複数のリセット CSS 環境下で動作確認する 読み込み速度に配慮する アクセシビリティへの配慮 div ではなく button を使う スキップリンク機能 文字の視認性 文字サイズを固定しない キーボード操作対応 WAI-ARIA 属性を指定する ドキュメントを書く どこにでも組み込めるように実装する 共通ヘッダ・フッタをどんなコードベースに入れても崩れることなく表示されることは求められる品質の一つです。サービスごとに適用されている CSS がわからない中で、これを完璧に実装することは困難ですが、極力崩れにくい実装なら可能です。 重複しないクラス名ルールを設定する クラス名が重複すると意図しないスタイルが当たってしまいます。たとえば次のような CSS と HTML があると、 .container の要素には意図しないスタイルが適用されてしまいます。 /* 既存の CSS */ .container { color : red ; } /* ヘッダー・フッターの CSS */ .header .container { ... } < header class = "header" > < div class = "container" > <!-- 文字色は赤になる --> </ div > </ header > この問題を避けるには以下に留意するとよいでしょう。 BEM の考え方を取り入れる ユニークなブロック名にする BEM は言わずと知れた CSS 命名規則で、一定の UI のまとまりを Block とし、Block を構成する各要素を Element として扱います。Element は必ず接頭辞として Block 名を含むため、冗長ながらも重複を避けたクラス名を付けることができます。 .header { ... } .header__container { ... } これだけだと、 .header というクラス名がサービス既存の CSS で使われている可能性を捨てきれません。そのため Block 名自体のユニークさも必要です。Block 名の具体性を高めるために複数の単語を使ったり、接頭辞を付けたりして重複しない Block 名を考えましょう。 /* 複数の単語を使う */ .responsive-header { ... } /* 共通パーツをあらわす cmn- 接頭辞を付ける */ .cmn-header { ... } /* 使われていない大文字小文字ルールにする */ .Header { ... } 弊社のサービスの場合、大文字始まりの単語でクラス名をつけているサービスはなさそうだったので、最後の .Header の形式の命名を採用しました。 詳細度や継承とうまく付き合う CSS セレクタの詳細度や継承(カスケード)のしくみをしっかり理解していないと、組込みの思わぬスタイルが適用されてしまいます。 分量の都合上、詳細度の解説は割愛します。詳細度設計において見過ごしがちなのは、セレクタの詳細度をどれだけ高めても、内側の要素に適用されるスタイルには勝てないということです。 a { color : red ; } #header { color : black !important ; } < header id = "header" > < a href = "/" > LIFULL HOME'S </ a > </ header > このような CSS と HTML があったとき、「LIFULL HOME'S」の文字色は赤になってしまいます。 組込みにどのような CSS が指定されているかは基本的に予期できないので、念を入れておく必要があります。念を入れるには全称セレクタ * を使うのがよいでしょう。次に示すコードでは Header 内のあらゆる要素と疑似要素について、スタイルをリセットしています。 .Header * , .Header * :: before , .Header * :: after { box-sizing : border-box ; margin : 0 ; padding : 0 ; list-style-type : none ; color : inherit ; line-height : 1.5 ; font-family : inherit ; letter-spacing : 0 ; text-decoration : none ; } 注意点もあります。全称セレクタは詳細度を増やさないため、 .Header * のセレクタの詳細度はクラス名をセレクタにとして単独で使う場合と同じです。そのため、ありがちな a:visited などのセレクタには負けてしまいます。 これらのケースは例外的でパターンも限られるので、ヘッダ・フッタの CSS で任意のスタイルを当ててあげましょう。 .Header a : visited { color : inherit ; text-decoration : none ; } より万全を期すのであれば、共通のヘッダ・フッタの Block にはすべて id 属性を付与し、ID 起点でセレクタを書くようにするとよいでしょう。 #Header { ... } #Header .Header * , #Header .Header * :: before , #Header .Header * :: after { ... } #Header .Header__element { ... } 打ち勝ちたい気持ちが早まって !important は付けないようにしましょう。うっかり付けてしまうと、各要素にこれから指定するスタイルにもすべて !important を付けなくてはいけなくなってしまいます。 プレーンな技術を使う 組込みのサービスがどのフレームワークを採用しているのか、共通ヘッダ・フッタにとっては知る由がありません。そのため共通ヘッダ・フッタは、特定のフレームワークに依存しないコードで実装されていることが望ましいでしょう。 もし特定のフレームワークに依存してしまうと、ユーザーは利用頻度の高くない共通部分のためにフレームワーク全体をダウンロードしなければいけません。表示パフォーマンスが悪化することはサービス全体にとってよいことではありません。 jQuery・Bootstrap・React・Vue・Tailwind CSS といった JS/CSS フレームワークは使わず、プレーンなコードを書くとよいでしょう。jQuery を入れてないサービスはない! ということなら jQuery ありきのコードを書くのもありです。このあたりは現場によりけりです。 Sass や TypeScript などを開発に取り入れることは特に問題ありません。クライアントサイドでの動作には直接影響しないからです。 ブレークポイントや z-index 等をカスタマイズ可能にする 組込みのデザインに適用できるように、ある程度カスタマイズができるようにしておく必要があります。代表的なものは、ブレークポイントや z-index の値です。 ブレークポイントは、サービスによってその値はバラバラなことが多いでしょう。共通ヘッダ・フッタのブレークポイントは、サービス側が用意しているポイントと一致しているほうが望ましいです。 z-index の設計もサービスごとにまちまちです。たとえば共通ヘッダに極めて大きな z-index の値を設定したことで、サービス側が持っているモーダルダイアログ(画面全体を覆うタイプのダイアログ)の上に重なってしまうかもしれません。 これらの値をカスタマイズするために PostCSS や Sass といったプリプロセッサを使うとよいでしょう。サービスによって変動する値は変数として用意しておき、各サービスで組み込む際にはその変数の値を修正し、プリプロセッサにかけてもらうことにしましょう。次のコードは Sass(scss 記法)での例です。 /* 設定部分 */ // ブレイクポイントの設定 $mq-mobile : '(max-width: 1023.9px)' ; $mq-desktop : '(min-width: 1024px)' ; // z-index の設定 $zi-header : 50 ; $zi-menu : 51 ; /* 利用部分 */ @media # {$mq-mobile} { // モバイル向けスタイル } @media # {$mq-desktop} { // デスクトップ向けスタイル } このように準備しておくことで、サービスごとに各要素へのスタイルを微調整することなく共通ヘッダ・フッタを適用できます。変更箇所を変数のみに限定させることで、オリジナルとの差分がファイルの中に分散することがなくなり、将来のバージョンアップへの追随が簡単になります。もしレイアウトが崩れたときの責任分担も明確になります。 ちなみに LIFULL HOME'S の共通ヘッダ・フッタにはこれ以外にも次の値をカスタマイズ可能な項目として用意していました。 コンテンツの最大幅 コンテンツの左右に設ける、ウィンドウとの余白の大きさ 開かれたメニューの z-index どのような項目をカスタマイズ可能にするかは、デザイン要件や機能要件に応じて設計するとよいでしょう。 html { font-size: 62.5% } に対応する LIFULL HOME'S 特有の要件ですが、サイトによっては html 要素に font-size: 62.5% というスタイルが宣言されていることがありました。これは rem 単位の扱いを簡便にするためのテクニックです。 font-size: 62.5% テクニックとは これは、CSS における rem 単位の指定を簡便にするためのちょっとしたテクニックです。rem 単位は「ルート要素= html 要素が持っているフォントサイズに対する相対値」を表す単位です。ブラウザーのデフォルト文字サイズはたいてい 16px ですから、 1rem は 16px * 62.5% * 1 = 10px となります。デザインファイルから拾った値を 10 で割るだけで CSS 上の数値に変換できるようになるため、オペレーションがちょびっと効率化するメリットがあるとされています。 ブラウザーの文字サイズ設定を尊重するために CSS の中の文字サイズは rem 単位で記述することになります。rem 単位はルート要素(= html 要素)を基準とするため、 html { font-size: 62.5% } の有無によって 1rem あたりの大きさが変わってしまいます。rem 単位を使って 62.5% 指定のサイト、そうでないサイト両方に対応する必要がありました。 今回は、html 要素にかかった文字サイズを打ち消すための係数を用意し、rem 単位を使用する場面では必ずその係数をかけた値を利用するようにしました。 // HTML 要素に適用されている font-size の逆数を比率で // 例: html{font-size:62.5%} の場合、 1 / 0.625 = 1.6 // 例: html{font-size:12px} の場合、1 / (12 / 16) = 1.333333 $font-scale-factor : 1 ; // 常に 16px になる rem 値 $rem : 1rem * $font-scale-factor ; // 補正済みの rem 単位を利用 $font-size-20 : 1.25 * $rem ; $font-size-16 : 1 * $rem ; $font-size-12 : 0.75 * $rem ; . Header__logo { // どんな環境でも 16px で表示される font-size : $font-size-16 ; } さらに実装品質を高める 少しマニアックな内容ですが、サービス横断的に広く使われるヘッダ・フッタですので、さらにクオリティを上げていきます。 テキスト量の変動を考慮する テキスト量が増えたときにどうするかを決めて実装に反映します。ヘッダ・フッタの内容の多くはナビゲーションのためテキスト量の増減は考えなくてよいことが多いですが、一部必要になる箇所はしっかりと対応していきます。機械翻訳にかけるとラテン系の言語ではテキスト量が増えがちなので留意しましょう。 上部固定ヘッダとページ内リンク先の要素を被らないようにする 新ヘッダ・フッタには、ページをスクロールしていくと画面上部に固定ヘッダがニョキッと現れる仕様があります。 ハッシュ( # )付きリンクのデフォルトの挙動は、遷移先の部分がウィンドウの上端にピッタリくっつくようにスクロールされて表示されます。そのため上部に固定しているヘッダがあると、遷移先の要素と固定しているヘッダとの干渉が発生します。 うまくかぶらないように表示したいものです。大丈夫です。まさにこのために使いたい CSS プロパティがあります。 scroll-margin-top です。 /* 固定ヘッダーの分だけスクロール位置を調整する */ : target { scroll- margin-top : 64px ; } これで固定ヘッダとジャンプ先の要素が干渉しなくなりました。 複数のリセット CSS 環境下で動作確認する リセット CSS はいくつかの種類があり、各サービスでどのリセット CSS が使われているかわかりません。リセット CSS はページ全体のスタイルに影響を及ぼすものですから、種類によってはこれまで書いてきた CSS との相性問題があるかもしれません。 リセット CSS の個数は有限ですから、いくつかメジャーに使われているリセット CSS ライブラリを実際に入れてみて、表示に崩れがないかどうかを確かめます。今回の実装では以下のリセット CSS を試してみて、問題ないことを確認しました。 Normalize.css v8 Eric Mayer Reset CSS Modern CSS Reset CSS Remedy Bootstrap Reboot Tailwind CSS LIFULL HOME'S reset(独自リセット) リセット CSS を入れ替えて検証しておくと、ヘッダ・フッタを移植したときに表示崩れを起こす可能性はぐぐっと低くなります。 読み込み速度に配慮する ヘッダ・フッタにはロゴ画像やアイコン画像が使われています。これらの画像は急いで読み込む必要はありません。ヘッダ・フッタは、あくまでナビゲーションや情報提供を目的としていて、正味のコンテンツに比べたら重要度は高くないからです。 SVG 画像の場合、HTML に SVG データを直接埋め込むことで外部ファイルのリクエスト数を減らすことができます。しかし述べた通りヘッダ・フッタの画像は重要度が高くなく、各ページで繰り返して登場することになります。画像は外部ファイル化し、ブラウザーのキャッシュを利用する戦略のほうがよさそうです。 画像を遅延読み込みさせるために、 loading 属性と decoding 属性が利用できます。loading 属性によって、まだ画面内に表示されていない画像の読み込みを遅延させることができます。decoding 属性によって、ダウンロードされた画像のデコード処理を非同期に行ってよいことを明示します。 loading 属性 | MDN (mozilla.org) decoding 属性 | MDN (mozilla.org) < a href = "https://www.homes.co.jp/search/bukken-history/" > < span > 最近見た物件 </ span > < div > < b > 3 </ b > < img src = "images/icon-clock.svg" alt = "" width = "24" height = "24" decoding= "async" loading= "lazy" > </ div > </ a > すべての img 要素に width 属性と height 属性を含めることも忘れてはいけません。画像読み込みによるリフローを抑え、高速化につながります。 Core Web Vitals における CLS の削減にもなります。 アクセシビリティへの配慮 LIFULL HOME'S のフロントエンドはアクセシビリティを重要視しています。ヘッダ・フッタの実装においても、多様なユーザーやアクセス方法を受け入れられるように配慮した実装を行っています。 div ではなく button を使う メニューを開く動作は JavaScript を使って実現しています。メニューを開閉するボタンの click イベントを購読し、イベントの発生に応じてメニューの表示・非表示を切り替えます。 ありがちなのが、開閉ボタンを div 要素や span 要素を使ってマークアップしてしまうケースです。マウスやタッチ操作で問題なく操作できるように思いきや、Tab キーを使ってフォーカスを当てることができずキーボードで操作できなくなってしまいます。 開閉の操作に限らず、 クリック起点で JavaScript の処理が作動するような要素は button 要素を使ってマークアップする 必要があります。 ところで、メニューやディスクロージャ(いわゆるアコーディオン的 UI)の開閉のために、チェックボックスと CSS を駆使して実現するチョイ技が巷にあります。これは本来の使い方ではないため、たとえ JavaScript が不要になるとしてもお勧めできません。 スキップリンク機能 スキップリンクとは、ヘッダ等サイトの共通領域をすっ飛ばして、いきなりメインエリアやナビゲーションにジャンプするためのアクセシビリティの機能です。キーボードやスクリーンリーダーを利用する人にとって有用な機能です。 左から GitHub・Google・IBM のWebサイト上でスキップリンクを表示したところ。 LIFULL HOME'S のヘッダ・フッタにもスキップリンク機能を実装しました。スキップリンクは初期状態では隠れています。ページを開いたあとキーボードの Tab キーを押すとスキップリンクが現れます。 スキップリンクの実装は難しくありません。初期状態で見えなくしておきますが、 display: none や visibility: hidden を使うとタブフォーカスの対象からも消えてしまいますから、それ以外の手段で視覚的に見えなくします。そして :focus 疑似クラスを使い、フォーカスが当たったときだけ表示すればオーケーです。 .Header__skipLink { position : absolute ; top : 0 ; left : 0 ; padding : 0.5em 1.5em ; background-color : #ed6103 ; font-weight : bold ; transform : translateY( -100% ) ; } .Header__skipLink : link , .Header__skipLink : visited { color : #fff ; } .Header__skipLink : focus { transform : translateY( 0% ) ; } 文字の視認性 文字が薄すぎたり小さすぎたりすると、文字は読みづらくなります。独断で「読めるじゃん!」と判断してしまうのは尚早です。屋外で強い日差しの下では画面が見えにくいかもしれませんし、そもそも見え方には個人差があります。 このようなデザインを見かけたら、実装する前に「待った!」をかけましょう。利用者の能力や状況が多様であることを説明し、もう少しクッキリした色使いにできないか検討してもらいましょう。 実装面でも配慮できることがあります。LIFULL の日本語部分のブランド書体は、Windows や macOS に標準搭載されている游ゴシックです。すばらしい書体ですが、Windows+Chrome の組み合わせで表示したときにレンダリングが細くなりすぎてしまう問題があります。 この問題への対処方法はたくさんのブログで多彩な解決が試みられていますが、上手に解決しているケースはあまり多くありません。 body { font-family : "Yu Gothic Medium" , sans-serif ; } 上記は font-family にウェイト付の書体名を記述するパターン。実は仕様にない書き方で、游ゴシックが表示できているのはたまたまです。また太字にしたとき「偽ボールド」になってしまうことも特徴です。 body { font-family : "Yu Gothic" , sans-serif ; font-weight : 500 ; } このようにすると、body の規定のウェイトが 500(Medium)になります。「偽ボールド」にもならず良い感じです。ですが 500 という数値がマジックナンバー的になってしまうため、気付かず font-weight: normal と書いてしまうと元の木阿弥です。また、すべてのテキストのレンダリングが Medium 相当の太さになってしまうため使いづらさもあります。 Windows と游ゴシックの組み合わせにだけ調整をかけるのにベストな指定は、次のような書き方だと考えています。 @font-face { font-family : AdjustedYuGothic; font-weight : normal ; src : local( "Yu Gothic Medium" ) ; } @font-face { font-family : AdjustedYuGothic; font-weight : bold ; src : local( "Yu Gothic Bold" ) ; } .Header , .Footer { font-family : AdjustedYuGothic , YuGothic , sans-serif ; } 詳細を述べると長くなってしまうので割愛しますが、上記のような指定をしておくと Windows と游ゴシックの組み合わせのみ若干太い書体が適用されるようになり、可読性が向上します。ほかのフォントや、macOS でのレンダリングには影響しません。 文字の視認性は重要ですので、使い勝手のよい UI を実装するためにもこのような細かいテクニックを知っておくとよいでしょう。 文字サイズを固定しない ブラウザーには2種類の拡大機能があることはご存じでしょうか? おそらく馴染み深いのはズーム機能でしょう。ズーム機能は画像を含むブラウザーに表示されるあらゆるものを拡大表示する機能です。もうひとつは文字サイズの拡大機能です。Webページに表示されるテキストの大きさだけをデフォルトより大きくできます。 前者のズーム機能はブラウザーが勝手にやってくれるため、特に意識する必要はありません。後者の文字サイズ拡大機能は、意図せず無効化してしまわないように制作者は意識する必要があります。この設定を有効にしているユーザーは小さい文字を読むのが苦手なユーザーと考えられます。この設定は可能な限り尊重したほうがよいでしょう。 font-size に指定する値を rem 単位にすることで文字サイズ設定を表示に反映できます。 .Header__links dt { font-size : 0.75rem ; /* 16px × 0.75 = 12px */ } 文字サイズが大きくなることで文字が読めなくならないようにしておくことも重要です。要素の幅や高さを固定値にしていると、文字サイズが大きくなったときにはみ出てしまったり、ほかの要素と被って読めなくなったりしてしまいます。変動する文字サイズを前提としたコーディングはなかなか高度ですが、ぜひ身に着けておきたい技能です。 文字サイズを大きくしたときの画面キャプチャー。デザイン通りの見た目ではなくなっているが、ユーザーの設定の通りに文字サイズが大きくなっている。 キーボード操作対応 Webサイトのアクセシビリティを高めるために最も重要なことの一つが、キーボードでも操作可能にしておくことです。キーボードで操作できるために抑えておくべきポイントは次のようなことです。 Tab キーを使ってフォーカスが当たる どこにフォーカスが当たっているか視覚的にわかる フォーカス順序が自然である 「div ではなく button を使う」の節で述べた通り、クリックを起点に何かが動くようなボタンは button 要素を使ってマークアップしましょう。button 要素は Tab キーでフォーカスを受け取る対象になり、キーボード操作可能になります。 < button type = "button" > < span > < span > メニュー </ span > < img src = "images/icon-menu.svg" alt = "" width = "24" height = "24" decoding= "async" loading= "lazy" > </ span > </ button > また、フォーカスを受け取っていることが視覚的にわかることも同じくらい重要です。CSS で outline: none を指定してフォーカスインジケータ(フォーカスしたときに出る枠線)を完全に非表示にしてしまっているケースにはいまだによく遭遇します。大原則として むやみな outline: none は避けてください 。視覚的表現を重視するプロジェクトだと、ブラウザーデフォルトのアウトラインが表示されることを嫌い、フォーカスインジケータを非表示にすることを求められることがあります。そういうときは focus-visible ポリフィル や what-input をつかって、キーボード操作時のみアウトラインを表示するようにしてください。 ちなみに共通ヘッダ・フッタのアウトラインは次のようなコードになりました。 /* 基本のフォーカススタイルの設定 */ .Header : focus -visible , .Footer : focus -visible { outline : 2px solid ; outline : 1px auto -webkit- focus-ring-color; outline-color : #005fcc ; } .js-focus-visible .Header .focus-visible , [ data-whatintent = "keyboard" ] .Header : focus , .js-focus-visible .Footer .focus-visible , [ data-whatintent = "keyboard" ] .Footer : focus { outline : 2px solid ; outline : 1px auto -webkit- focus-ring-color; outline-color : #005fcc ; } .Header__stickyBar : focus -visible { outline-color : #ffe680 ; outline-offset : -3px ; } .js-focus-visible .Header__stickyBar .focus-visible , [ data-whatintent = "keyboard" ] .Header__stickyBar : focus { outline-color : #ffe680 ; outline-offset : -3px ; } ポイントは3点です。①サービス側で focus-visible Polyfillか what-input を導入することを前提に、どちらが導入されても意図通り動くようになっています。②LIFULL オレンジを背景とする箇所はデザイナー要望で色を変えています。③Firefox でのフォーカスインジケータの視認性を確保しつつ、Chrome や Edge のデフォルトインジケータのスタイルを踏襲するようにしています。 WAI-ARIA 属性を指定する JavaScript を使って表示や値が動的に変わる UI には、 WAI-ARIA が定める属性を指定することでアクセシビリティを高められることがあります。 共通ヘッダ・フッタには何ヵ所か JavaScript で制御している箇所があります。グローバルメニューと、フッタの折りたたまれたリンク集です。どちらも共通して「クリックしたら特定の要素の表示・非表示を切り替える」という振る舞いをします。「ディスクロージャ」と呼ばれる UI パターンです。 < button type = "button" aria-controls= "Menu" aria-expanded= "false" > < span > < span > メニュー </ span > < img src = "images/icon-menu.svg" alt = "" width = "24" height = "24" decoding= "async" loading= "lazy" > </ span > </ button > < div class = "Menu" id = "Menu" > <!-- メニューの中身 --> </ div > aria-expanded 属性には開閉状態に応じて true もしくは false を切り替えるように実装します。このように aria-expanded と aria-controls 属性を用いると、ディスクロージャの開閉状態をスクリーンリーダー等の支援技術に伝えられます。 これらの属性以外にも、UI の種類や状態を表現するための属性が WAI-ARIA にはたくさん定義されています。 WAI-ARIA オーサリング プラクティス には動作サンプル付きで UI パターンごとの実装方法が解説されています。一度流し見しておいて、UI 実装の機会があったときに思い出してみるとよいかもしれません。 ドキュメントを書く 共通ヘッダ・フッタは必ずしも気心の知れたエンジニアが使ってくれるとは限りません。交流のないほか部署のエンジニアが組込み作業をするかもしれません。実装の仕上げに、使い方や組込み方を記した文書を残しておくことが大切です。 LIFULL HOME'S のヘッダ・フッタについても重厚なドキュメントを用意しました。内容をかいつまむとたとえば次のような内容を記載しました。 z-index やコンテンツ幅のカスタマイズ方法 CSS と JS のビルド方法 focus-visible Polyfillの導入方法 サービスごとに改変可能な箇所 スキップリンクを動作させるため、メインエリアに id 属性を付与すること メニューの開閉をイベントで受け取るための JS API 不明な点があった場合の連絡先 あとは社内に周知できればお仕事は終わりです。ドキュメントには連絡先などを書き添えておいて、利用してくれる人のサポート役にまわるとよいでしょう。 今回私が作成したヘッダ・フッタは、 LIFULL HOME'S 注文住宅 で見ることができます。今後も展開が進み、広く利用されていくものと期待しています。 高品質な実装やアクセシビリティにともに取り組んでくれる仲間を募っています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
エンジニアの松尾です。LIFULL HOME'Sの売買領域を支えるエンジニアチームのマネジメントを担当しています。 弊社のサービスは、主にサービス企画、デザイナー、エンジニアの3職種により進められています。今回はより良いプロダクト開発のために3職種の連携を強める取り組みについて紹介します。 より良いプロダクトを開発するために 現在LIFULLでは組織が職能別に分かれており、各領域の専門性を高めるためにさまざまな取り組みが行われています。 私が所属するプロダクトエンジニアリング部でも、 勉強会の開催 、 独自のKPIの設定 、 技術的負債解消への取り組み などを進めてきました。 一方で職種間の連携にもまだまだ伸びしろはあります。そこで、現場におけるコミュニケーション上の課題と打ち手を考えてみます。 上手に期待感をすり合わせたい 書籍「 アジャイルサムライ 」では、チームメンバーどうしでお互いの期待感を伝え合うための「ドラッカー風エクササイズ」という手法が紹介されています。自身が思う強みや役割と他者からの期待のギャップを明らかにすることで、お互いの魅力を引き出します。 弊社でもプロジェクトごとにキックオフのタイミングで実施されていることが多く、チームビルディングの一つとして重宝されています。しかし、現在の組織は構成人数が多く、プロジェクトメンバーの変更が発生することもあり、個人間の期待をすり合わせ続けることはなかなかのコストです。 そんな悩みを抱えていたときに「 どういうデザイナーとだと仕事しやすいか 」という記事を拝見し、個人以前にほかの職種に対しての期待も少なくないことに気付きました。 そこで、コミュニケーションの土台として、サービス企画、デザイナー、エンジニアの3職種間での期待感をすり合わせるための取り組みに着手しました。 チームで取り組んだこと 今回の取り組みではLIFULL HOME'Sの売買領域でエンドユーザ向けの開発に関わるメンバーを対象にしました。各職種は下記のような内訳です。 サービス企画: 10名 デザイナー: 5名 エンジニア: 15名 事前課題 自分の職種について、「役割/強み」を記入する 他の職種に対して、「期待すること」を記入する 「期待すること」を見渡して、同意するものにリアクションをつける 気軽に回答できることを優先するため、記入する内容はドラッカー風エクササイズよりシンプルにしています。とはいえ大人数での意見をまとめる必要があるため、スペースや人数の制限が少ないホワイトボードとしてmiroを採用しました。 議論 記載された内容について、不明瞭な部分や背景を聞きたい部分を職種間で議論しながら深掘っていきます。すべて理解できたあとに、職種内で期待されていることに対しての振る舞いをふりかえり、教訓とアクションをまとめました。 職種ごとに用意したmiroのボード 私達が期待している/されていること 全体的に「協力は惜しまないので、妥協せずにやってほしい」という姿勢の意見が多く見られました。ある人にとっては「当然のことでは?」ということでも明示的に伝えることで、より互いの理解が深まりました。 実際に出てきた意見の一部を抜粋して紹介します。 サービス企画への期待 PJ全体の優先順位の相談〜判断 目的/意義/効果に納得できる施策を提案してほしい リリース後にも継続的にその機能を見直してくれるとうれしい デザイナーへの期待 すごいと思える/説得力のある/最良のデザイン サービス全体を俯瞰した情報・デザインの整合性 UIだけでなくUXも一緒に考えてほしい エンジニアへの期待 実装方法の工夫で仕様の可能性を広げること 他のサービスに横展開しやすいように実装してくれること 実装中にモヤモヤするところがあれば率直に伝えてほしい まとめ 自組織において職種間の期待を伝え合う方法を紹介しました。今回の結果はあくまでも弊社での一例ですので、プロダクトの特徴や組織のあり方に応じてきっと違う結果が出ます。 ちなみに後日メンバーから、「対個人じゃないので素直に書きやすかった」というコメントがありました。完成度の高いチームでは個人間で率直に伝え合えば済む話ですが、そこに至るまでの途中段階として今回のようなワークショップを挟むのも良い手段だと思います。 今後もより良いプロダクトを世に送り出していきます。ともに最高のチームを作っていく仲間を募集しておりますので、よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは!LIFULLのプロダクトエンジニアリング部の井上です。 エンジニアリングマネージャーをやっています。 今回は、私が管掌する組織で「大規模スクラム」という、スクラムを拡張して複数チームで運用する開発手法へチャレンジし、生産性向上を図った話をしようと思います。 結論 結論から伝えますと、組織を俯瞰して管理する立場からすると、一定レベル、メンバー主体で合理的に動ける仕組みをつくれた実感をもてました! その反面、経験値不足もありますが本質的な組織的なスクラム開発が実践できているか?と言うと、まだまだ改善をしていく必要があるとは感じています。 取組みのポイント スクラムの3本柱「透明性」「検査」「適応」に倣うならポイントはこのような感じです。 透明性 バックログは組織に1つだけ 誰でもいつでも全体像が見れる、判断できる状態をつくる(チームや誰かだけしか知りえない状態は禁止) ただし認知コスト削減の為にチーム単位でフィルタを作るのは問題なし 検査 意思統一できる場を用意 全体+各チームの状況を共有や相談できる場、共有や議論できる定例を用意 適応 優先度ルール 基本的には各チーム内施策に集中すればよい ただし、優先度が高い障害や、組織判断で優先度「高」の施策が現れたら、各チームの施策に専念する限りではないというルールを初期に徹底 まとめの図 前提(それまでの組織の構造) 最初に、私が管掌している組織の話からさせてください。 私が管掌する組織ではあるプロダクトに対して1つミッションを持っています。 今回の取り組み以前は、それらの達成のために4~6人程度の職種混合の小チームを4~5チーム程組成し、それぞれで施策表/課題表(バックログ)をつくり開発していました。 チームだけで考えれば関心ごとがチーム中で閉じている為、意思決定速度は担保されるのですが、チームでミッションを固定している故にチーム外への意識が弱くなったり、全体では優先度が高い施策や変化に対して、柔軟性が損なわれる課題がみえていました。 組織構造の図 私たちが目指したいこと 限られたリソースで、効率よく最大の効果を出すためには・・ =チームへミッションを固定させすぎず、全体視点で優先度が高い課題から対応できると良さそう 真の優先度が高い施策から実行したい これを実現するためには、ひとつのミッションを分解した課題群が組織で1つのバックログで管理され、各チームがバックログから自主自律的に、優先度が高い課題を選んで実行していくことが理想と考えました。 複数チームが1つのバックログからチケットを取得する図 柔軟な組織 また、期の途中であってもミッション実現のために当初計画していたタスクとは別に、重要な課題が発生するケースも考えられ、突発の事態に柔軟に対処できる仕組みである必要があります。 想定される、計画外案件: 障害対応 他部署からの依頼・相談 成果を出すために、戦術レベルでの優先度変更 など 効果的なチームの組成 効果的なチームである為には心理的安全性が重要であるという調査結果もでています(Googleが公開しているre:Workより)が、この観点でも配慮はいれたいと考えました。 その為にチームメンバーを固定することができれば、互いに信頼し合い働き方や考え方・期待値を一致させることがしやすく、仮に優先度にあわせてミッションやKPIが多少変化したとしても高いパフォーマンスで乗りきりやすいと考えました。 逆に、チームメンバーがコロコロ変化すれば信頼や考え方の一致が出来るように至るまで、パフォーマンス低下は免れないでしょう。 現実は複雑 新規開発などシンプルなKPIを追いかける構造のミッションや、スクラムに対して習熟度が高いメンバーが揃っていれば、上述した仕組みがワークする気がするのですが・・・ 施策優先度が単純に比較できない 数年間、運用を続けているプロダクトに対して適用させると、方向性がそこそこ違う複数の打ち手が並列で並んできます。 すると、チーム間で優先度の判断は非常に困難になってきます。 優先度が判断できない課題群(例) チーム 打ち手 課題 A UX追及 〇〇モーダルの挙動調整 B SEO対策 〇〇記事からのリンクの新規設置 C 新機能開発 〇〇機能の開発 全体で優先度を判断するためには、方向性の異なる打ち手を誰かが管理する必要があると思います。 その方法として以下のようなパターンが考えられます。 方法①:各チームのスクラムマスターが集まり、全体バックログリファインメントを実施し全体管理して各優先度を決定。 方法②: 全体をみている上長が、トップダウンで優先度を判断。 しかし、前者は認知負荷が大きかったり、後者も上長が都度意思決定するプロセスが増えてしまい、不要なコストがかかりすぎるように思えました。 前提となるスクラム / アジャイル開発に対して、理解や習熟度がバラバラ そもそもスクラムやアジャイル開発を知らない・経験がないというメンバーは、いないものの、習熟度も興味も人によって差が大きく、理想形から組織に落としていくには、導入コストもコミュニケーションコストも大きい割に十分なリターンが得られるか不透明で、困難を極める事が想定されました。 導入の為に、意識したこと 理想と現実のジレンマはあるのですが、最初からすべて完ぺきに実行するのではなく、次の3点をベースに 極力シンプルなルール を設ける事としました。 意思決定の速度を高める、不要なものを可能な限り排除 チームメンバー全員がチームの中と外の両軸について、常に関心を持ち続けることは認知コストが高く効率性を下げてしまいます。 よって、シンプルに原則チームに集中してよいルールとし、チーム内はもちろん組織全体で発生した優先度が高い課題をとるかはチームの自主・自律性に任せる体制としました。 合わせて上長が意思決定や承認に入るケースも最低限とすることで、より効果を高めることも図りました。 定期的に、全体の数字や課題を確認・議論できる場を用意 とはいえ、チームに関心を集中しすぎると、横断的な施策などに対して組織的な柔軟性は失われ、上長が指示をしたとしても自主性がないためストレスを抱えかねないと考えました。 これは心理学でいうザイオンス効果(単純接触効果)により、自身が一番よく触れる数字や課題、プロダクトに対して、興味をもってしまうので致し方がないことだと考えていますが、極端になりすぎないようにしなければいけません。 効率よく結果を出す為には、隣のチームと協力できる程度の情報共有はされているべきですし、上述していますが、当初計画していなかった戦術の変化や緊急対応はありえる事だから対処が当然必要です。 その為に、隔週でスクラムマスターを集めて組織全体の中でも特に課題となっている部分だけを議論する場を用意し、話し合いで合理的に処理するようにしました。 学習コストは最低限に ルールは、シンプルに 可視化により組織全員が共通認識をとれるようにする 為のルール(=小さな学習コスト)だけが必要とだけ伝えていました。 結論 冒頭にも記述していますが、3か月ほど運用した時点での感想としては、本当の意味で、組織の目指すKPIに対して優先度が高いものから動けているか?というと、まだまだ伸び代を感じていますが、最初の一歩としては、よい状態をつくれたと感じています。 導入のポイント 今回、アジャイルをスケーリングした開発手法を導入するにあたり、Scrum of ScrumやLeSS・SAFeといった様々なものを参考にさせてもらったのですが、フレームワークのカタチに拘らず、そもそも自組織の課題やありたい姿と照らし合わせてシンプルに小さく適用したのがよかった気がしています。 ここまで、この記事を読まれた方は、 アジャイルをスケーリングすることに興味がある方かと思いますが、何かの参考になれば幸いです。 最後に 今回とりあげた、組織で実行するスクラムは私自身も上手く行くか不安があったものの、 チャンレンジをさせてもらって一定の次に繋がるカタチにできたかなと思っています。 このように、LIFULLでは、さまざまな新しいチャレンジに取り組みやすい環境が整っています。 LIFULLでは一緒に働く仲間を募集しています。よろしければこちらも合わせてご覧ください。 hrmos.co hrmos.co
KEELチーム の相原です。 もう随分と前のことになるのですが、以前我々が管理するKubernetesクラスタであるKEELで起きた障害のふりかえりについて書きます。 今回起きた障害 PriorityClassとはなにか 経緯 何が起きたか なぜPodDisruptionBudgetは機能しなかったのか どうすればよかったか 最後に 今回起きた障害 既にサービスインしているKubernetesクラスタ に対して globalDefault: true なPriorityClassをデプロイした 影響で、まだPriorityClassが設定されていない priority: 0 なPodが一斉にPreemptされ一時的にサービスに障害が起きた。 PriorityClassとはなにか 詳しい説明は公式ドキュメントの Pod Priority and Preemption に譲りますが、初めにPriorityClassについて軽く説明しておきます。 kubernetes.io PriorityClassとは以下のように定義するKubernetes組み込みのリソースで、KubernetesがPodをスケジューリングする時の優先度を表します。 apiVersion : scheduling.k8s.io/v1 kind : PriorityClass metadata : name : high value : 1000 Pod側の priorityClassName で以下のように指定することで有効になり、このPodのスケジューリング時にNodeに十分なリソースがなければ priority の値が 1000 より小さいPodを対象のNodeから選択してPreemptするというものです。 apiVersion : v1 kind : Pod metadata : name : example spec : priorityClassName : high ... Preemptionとはkube-schedulerによって実行される単なるPodの終了のことで、通常のPodの終了と同じように preStop の実行 -> SIGTERMの送信 -> terminationGracePeriodSeconds の秒数経ってPodが終了しなければSIGKILLを送信する といったフローで実行されます。 PriorityClassに preemptionPolicy: Never と書けばPreemptionを無効にできますが、この場合リソースに空きが出た時に優先的にスケジューリングされるというだけなので当然待ち時間は長くなります。 globalDefault: true と書くことで priorityClassName を指定していないPodに自動的にPriorityClassを設定することも可能です。 デフォルトではPodの priority の値は0となるため、基準となる priority を globalDefault: true として作成しておくとよいでしょう。 priority はKubernetesのAdmission Controllerによって設定されるため、次回のPodデプロイ時から有効になります。 後述しますがこれが今回の障害の原因となりました。 ちなみに、このPriorityClassにはデフォルトで system-cluster-critical と system-node-critical の2つのPriorityClassが存在していて、これらはKubernetes 1.13で廃止された scheduler.alpha.kubernetes.io/critical-pod の代わりとして用意されるようになったものです。 絶対にPreemptされたくないクラスタの重要なコンポーネントであるDaemonSetに付与することが想定されていて、 kube-system 以外では利用することができせん。 経緯 我々が管理するKubernetesクラスタであるKEELではPodのほとんどをAWSのスポットインスタンス上で稼働させるべく、 スポットフリート の導入や、スポットインスタンス売り切れ時のフォールバック機能やスポットインスタンス上のPodの安全な終了処理の開発などを進めてきました。 フォールバック機能は開発したものの依然スケジューリング待ちは予想できたため、待ち時間をできるだけ短くするべくスポットインスタンス上のPodの中でもスケジューリングの優先度をつける必要が出てきました。 そう、PriorityClassです。 そこで我々は以下の3つのPriorityClassを用意して適用しました。 PreemptされてもよいPodに付与する value: 100 の low PriorityClass 標準的なPodに付与する globalDefault: true かつ value: 1000 の medium PriorityClass 優先度の高いPodに付与する value: 10000 の high PriorityClass 適用後、無事にlow PriorityClassを持ったPodのPreemptionを確認することができ、これで更に安定してスポットインスタンスを利用できるようになりました。 と、思ったのもつかの間、突如チームに対して大量のアラートが届きます。 アラートの発生元は外形監視で、どうもクラスタ外からのリクエストのエラーレートが急上昇していそうです。 調査をしてみると、クラスタ外からのリクエストを受け付けるリバースプロキシである istio-ingressgateway をはじめとして多くのPodに再起動した形跡が見られました。 クラスタ内のリクエストはKEELで利用しているService MeshであるIstioによってリトライされていたため大きな影響はありませんでしたが、クラスタ外からのリクエストを受け付けるリバースプロキシの台数が減ったことによって外からのエラーレートが急上昇していました。 何が起きたか この障害は globalDefault: true によって引き起こされました。 前述の通り、デフォルトのPodの priority の値は0です。 PriorityClassを導入する前にデプロイされたPodにはすべて priority: 0 が設定されています。 priority はAdmission Controllerによって設定されるため、PriorityClassを適用しても既存のPodは引き続き priority: 0 のままです。 そこに今回 value: 1000 なPriorityClassに globalDefault: true を設定した影響で、十分なリソースがないNodeに新たに priority: 1000 なPodがデプロイされ、PriorityClass導入以前のPodが一斉にPreemptされました。 なぜPodDisruptionBudgetは機能しなかったのか KubernetesにはEvictionというPreemptionに似た挙動が存在します。 Evictionは Node-pressureが起きた際 や Taintによって PodをNodeから退避させるものですが、PodDisruptionBudgetによって一度に退避させるPod数に制限をかけることができます。 KEEL上にデプロイされているPodにはほぼすべてPodDisruptionBudgetが設定されているため、こういった現象は防がれていたはずです。 しかし、Preemptionのドキュメントには以下のようにあります。 Kubernetes supports PDB when preempting Pods, but respecting PDB is best effort. The scheduler tries to find victims whose PDB are not violated by preemption, but if no such victims are found, preemption will still happen, and lower priority Pods will be removed despite their PDBs being violated. kubernetes.io なるべくPodDisruptionBudgetの通りにPreemptしようとするが、完全に保証されるわけではありません。 そのため、PodDisruptionBudgetを設定しているにも関わらず今回のような障害が起きてしまいました。 どうすればよかったか まず第一に、稼働しているクラスタに対してPriorityClassを設定することには慎重にならなければなりません。 全てのPodにはデフォルトで priority: 0 が設定されているため、PriorityClassの設定によって意図しないPreemptionが発生してしまう可能性があります。 globalDefault: true を設定するならなおさらで、以降デプロイされる全てのPodには priority が設定されるため、これが0を超えている場合PriorityClass導入以前の全てのPodがPreemptionの対象となってしまいます。 priority の取りうる数値は n < 2000000000 と広いため、つい幅を持ってPriorityClassを作りたくなってしまいますが、恐らくそれほど細かく priority を設定することは少ないため、KEELでは以下のようなPriorityClassの定義に落ち着きました。 apiVersion : scheduling.k8s.io/v1 kind : PriorityClass metadata : name : low value : -10 --- apiVersion : scheduling.k8s.io/v1 kind : PriorityClass metadata : name : low-nonpreempting value : -10 preemptionPolicy : Never --- apiVersion : scheduling.k8s.io/v1 kind : PriorityClass metadata : name : medium value : 0 globalDefault : true --- apiVersion : scheduling.k8s.io/v1 kind : PriorityClass metadata : name : medium-nonpreempting value : 0 preemptionPolicy : Never --- apiVersion : scheduling.k8s.io/v1 kind : PriorityClass metadata : name : high value : 10 --- apiVersion : scheduling.k8s.io/v1 kind : PriorityClass metadata : name : high-nonpreempting value : 10 preemptionPolicy : Never globalDefault: true なPriorityClassの value を0にしているため、万が一後からPriorityClassを設定することになっても既存のPodがPreemptされることはありません。 KEELはStatelessなKubernetesクラスタであるため、大きな変更を伴うバージョンアップの際にはクラスタの稼働系を2つ用意してCanary Deploymentをするので、念のため後からもPriorityClassを設定できるようにしました。 また、 kubernetes/autoscaler のcluster-autoscalerを利用している場合、 --expendable-pods-priority-cutoff を下回った priority は無視されるため デフォルト値の-10 を下限としています。 最後に 実は恐ろしいことに、この障害の2週間前に Grafana社での同様の障害のPostmortem をチーム内で共有したばかりでした。 grafana.com しかもこのPriorityClass導入の作業者である私がです。 Preemptionの挙動も頭に入っていたはずにも関わらずの大失態でしたが、Kubernetesの失敗談を集めた Kubernetes Failure Stories にもPriorityClassにまつわるエピソードは多いため、このエントリでの注意喚起をもって罪滅ぼしとさせてください。 k8s.af Multi TenancyなKubernetesクラスタでは利用者による意図しないPreemptionを防ぐため、勝手にPriorityClassをデプロイできないよう権限を絞るのはもちろんのこと、ResourceQuotaやOPA Gatekeeperで利用できるPriorityClassを細かくコントロールする必要があることにもご注意ください。
自己紹介 プロダクトエンジニアリングチームの石川です。 2020年新卒入社で2年目のエンジニアです。 今回の記事では、エンジニアだけで新機能開発・検証を行うPEChallengeについて紹介させていただきます。 ユーザーテストの具体的なやり方まで紹介させていただきますので、最後まで読んでいただければ幸いです! PEChallengeとは PEChallengeは「Product Engineer Challenge」の略で、エンジニアだけでHOME'Sの新機能のプロトタイプを作るプロジェクトです。 開発は クリエイターの日 という社内制度を活用して、現在は半年ごとに7営業日の活動期間を確保しています。 2020年7月に発足し、プロトタイプを二度作り、現在ユーザーテストを実施しています。 プロジェクトの内容は企業秘密になるため、具体的な内容を書くことはできないのですが、今回の記事ではエンジニアだけで、どのように新機能のプロトタイプを作り、何に悩んだのかを書かせていただきます。 メンバー構成 エンジニアマネージャー:1人 エンジニア:4人 ここに記載しているエンジニアは、普段HOME'Sの開発をしているエンジニアの中から有志で集まったメンバーで構成されています。 自分たちで企画を行い技術選定ができるので、そこに魅力を感じて集まったメンバーが多い印象です。 開発を行うのはエンジニアの4人で、エンジニアマネージャーは、MTGの進行を務めプロジェクトの方向性を決めたり、社内での承認対応をしています。 どのように進めているのか? PEChallengeでは技術的な面ではなく、ビジネス的な面でのHOME'Sの課題をエンジニアリングで解決する事を目的としています。 そのために週次で全員で1時間MTGを開催し、HOME'Sのサービスとしての課題を定義し、エンジニアリングでの解決策を考え、プロトタイプの技術選定を行います。「どんな技術で何を作るのか」が決まると実際に開発に入ります。 開発は半年に一回、7営業日の時間を利用して進めているので、ここで一気に設計からプロトタイプ作成まで行います。 その後ユーザーテストや振り返りを実施し、プロダクトを作り直すのか、拡張させるのかを決めます。 このサイクルが半年で回るように進行しています。 チームがエンジニアメンバーのみで構成されるため、サービスの課題設定からプロトタイプ作成・検証まで行うことができ、非常にやりがいのあるプロジェクトです! 何が大変なのか? 設計が定まっていないと、開発が滞る このチームメンバーで初めてプロトタイプを作る際に、画面設計、API設計、DB設計を役割分担して行いました。 画面設計(2人)、API設計、DB設計の役割を決めた後に、同時進行で設計を進めてしまいました。 僕はAPIの設計を担当だったので、DB設計ををざっくりと頭で想像しながら、設計を行っていました。 しかしその後にDBの構成に変更を加えることになり、途中まで作り上げていたAPIの設計も大幅な修正をすることになりました。 教訓としては画面設計(2人)、API設計、DB設計を同時並行で進行させずに、一つ一つの設計をメンバーで議論、吟味して着実に進めることが大切だと感じました。 何をMVPにする? よく新規事業をするときに「MVPを作れ」と言われる事が多いかと思います。 MVPとは「minimum viable product」の略で、「価値を提供できる最小限のプロダクト」という事です。 今回のプロジェクトでは何を「実用的な最低限機能」とするのかということに度々つまりました。 新しい商品を作ろうとすると、どうしても「あんな機能も欲しい」とたくさん機能を追加したいという気持ちになります。 プロダクトを作る上で多くの人がこの問題に悩むかと思うのですが、僕たちも同じ問題に悩まされました。 そこで僕たちは再度課題に戻るために、「誰の、どんな課題を解決するのか」という問題に対して、以下の手順でアイデアを精査しました。 そもそも最も大きな課題は何か? 今の方法でその課題解決できているか? その方法は抵抗感なく使用してもらえるのか? 結果として、最も大きな課題だけを解決するためのMVPを作成する事ができました。 しかしここで安心してはいけません。 なぜならば、MVPを作成した後のユーザーテストを行った時に、再度アイデアが発散するからです。 ユーザーテストをすると、テスターの方から「こんな機能があったら嬉しいなー」などと意見をいただく事が多々あります。 それは非常にありがたい事なのですが、その意見を全て反映させていると、結局また「誰の、どんな課題を解決するのか」という部分がうやむやになってしまいます。 ですので、ユーザーテストで今回開発した機能が課題解決に結びついているのかを最優先で確認し、 ユーザーテストの結果を踏まえて再度メンバーで上記3つの手順に戻ることが大切です。 全てのステークホルダーを重んじれているか? LIFULL HOME'Sでは新機能を作る際に ユーザー(HOME'Sを使って住み替えをする人)とクライアント(HOME'Sに物件を載せている不動産会社) のどちらに対してもメリットが出せているかを考える必要がありました。 両者にとってメリットがある状態にする事はかなり難しく、解決策の立案に苦労しました。どちらの立場から見ても良いプロダクトにするために、僕たちは同じプロトタイプのテストを、ユーザー側とクライアント側の両方で行いました。 プロトタイプに対する両者の意見を取り入れて、どうすれば両者を立てたプロダクトができるのかを模索しております。 両者によって良いプロダクトを考えると、またアイデアが発散して混沌とした状態になってしまうので、 ユーザーテスト完了後、再度「誰の、どんな課題を解決するのか」という問題に戻りたいと思っています。 ユーザーテスト PEChallengeのユーザーテストの手順・結果は下記のようになりました。 手順(PEChallengeのユーザーテスト) テスターは、今回の新機能で解決する課題を持っていそうかで選定 テスター1人に対して対面でユーザーテストを実施(約30分) 1人のユーザーテストにチームメンバーが全員参加し、司会・書記を分担して行う 司会(PEChallengeメンバーの一人)がプロトタイプを使用する手順を一つずつ説明して、手順が終わる毎に質問する 「OOというボタンを押してください」 「期待通りに動作しましたか?、どのような動きだと良いと感じますか?」 「この機能に5段階で点数をつけるなら何点ですか?」 結果(PEChallengeのユーザーテスト) 1時間かけて2人に対してヒアリング取得 使い方を丁寧に説明しながら質問を重ねたので、機能を理解してもらった上で回答してもらえました テスターの方からの質問に対して、僕たちが随時回答することで、「それだったこんなアイデアの方が良くないですか?」という提案を頂くことも多く、質の高いフィードバックを得ることができました テスターの方に手順を一つ一つ説明し、その後に実際のプロトタイプの操作を実施してもらい、最後に質問をしていたので テスターの方が機能をしっかり理解した状態でヒアリングをする事ができました。 また1人のテスターに対して十分な時間を確保し、双方向にコミュニケーションをとれたため、より深くまで意見を聞くことができました。 自作アプリケーションで行ったユーザーテストとの比較 個人的な話になりますが、僕もプライベートの時間で自作アプリケーションを作成して、友人にユーザーテストを行うことがありました。今回PEChallengeで行ったユーザーテストと自作アプリケーションのユーザーテストの違いを比べて、学べる部分があったので、ここで少し書かせていただきます。 手順(石川の自作アプリケーションで行ったユーザーテスト) LINEで複数の友人に、自作アプリケーションのURLとGoogle form(アンケート)のURLを共有して回答してもらう 友人の選定は協力してくれそうかで判断 Google formに自作アプリケーションに対するアンケートを明記 アンケートの冒頭で自作アプリケーションの特徴と使用手順をざっくり説明 質問内容は以下2つ 「このアプリを使うとOOという課題が解決された状態になりますか?」 「このアプリを空き時間で使用したいですか」 選択肢を5段階で用意してチェックして回答してもらう 結果(石川の自作アプリケーションで行ったユーザーテスト) 1時間LINEで複数の友人にテストの依頼を行い、13件の回答を取得 機能と使い方の説明がざっくりであったため、自作アプリケーションの使い方自体がわからないという声が殺到 自作アプリケーションで実施したテストでは手軽に多くの回答を得ようとするあまり、テスターの方が機能を理解しないままテストが終了してしまう事がありました。 設計思想や使用用途を激詰めするような電話もいただきました笑(良い友人なので大事にします)。このようにテスターの方が感じた疑問に対して、双方向でコミュニケーションが取れなかったため、より細かいフィードバックを得ることができませんでした。 またテスターの選定においても違いがありました。自作アプリケーションのテストでは無差別に行い、PEChallengeのテストでは、開発した機能を使用したいと想定される人を中心に選定しました。 そのため後者のユーザテストの方が、テスターの方が自分事として捉えてテストを受けてくださったので、より深いフィードバックを得る事ができました。 今回のPEChallengeのユーザテストは不特定多数の人から手軽に回答を得る事ではなく、新機能を使うと想定される人に深く質問する事を意識していました。 そのため事前に「どのようにすれば実際に使用しているように感じられるのか?」や「どういう手順ならば機能を理解してもらえるか?」などを考え、 インタビューの流れや質問内容を吟味し設計していました。このように目的に合わせてテスト設計を行う事が必要な回答を得るために重要なのだと感じました。 まとめ 約1年間このプロジェクトをやってみて、使ってもらえる新機能をつくる事がこんなにも大変なのかと感じました。 実装の前に設計を確定させる事、迷ったら常に課題に立ち戻る事、適切なユーザテストを実施する事を肝に銘じてこれからも開発したいと思います。 今後新規事業や新機能開発をされる方やそれらで困っている方々のご参考になれば幸いです。 またLIFULLでは、PEChallengeのように 「エンジニアとして経営をリードする」 を体現したい方を募集しております。ご興味のある方はこちらのページもご覧ください! hrmos.co hrmos.co
はじめまして、品質改善推進ユニットの根本です。 ユニットではプロダクトや業務プロセスの品質を継続的にモニタリングし、改善計画の作成を支援していくパートナーシッププログラムという取り組みが始動しました。 詳しくは下記の記事をご覧ください。 www.lifull.blog モニタリングのスコープにはセキュリティも含まれます。今回は、このセキュリティのモニタリングに、スレットモデリングを取り入れる試みについて、ご紹介したいと思います。 課題 モニタリングの方法としては、システムを直接診断するもの、セキュリティにかかわる業務プロセスを診断するものが考えられます。例えば、システムを診断するものとしては、Webアプリケーションやプラットフォームの脆弱性診断 などがあげられ、業務プロセスを診断するものとしては、脆弱性への対応業務プロセスを整備状況や運用状況の面から評価するものなどがあげられます。 これらは、第三者もしくはツールが診断を実施し開発・運用チーム側にレポートすることが一般的です。そのためレポートされる開発・運用チーム側は受け身の姿勢になりがち、という状況が発生します。 第三者による客観的な診断結果は必須なものですが、最もシステムについて詳しいのは開発・運用チームですから、もったいない状況ともいえます。開発・運用チームがもつシステムそして業務プロセスに関する情報が診断の精度を上げることは間違いありません。 この課題について、スレットモデリングが解決の一助になるかもしれません。プロダクト開発・運用のステークホルダーが参加し、自らセキュリティ面の脅威(スレット)を洗い出していくところに効果を期待できるからです。 スレットモデリング スレットモデリングは、システムの設計段階で用いられる脅威の洗い出し・分析・評価の手法ですが、すでに運用に入ったシステムについても実施する価値があるといえます。大きなシステムの場合、どこに対して優先的にセキュリティ対策を実施していくかなど、限られたリソースを振り分ける際の判断材料になるからです。 スレットモデリングについて詳しく知りたい方は下記をご覧ください。 owasp.org 今回の試みでは二つのアプローチをとりました。 STRIDEモデルの活用 公開されているスレットモデル情報の活用 1.STRIDEモデルの活用 まず、DFD(データフロー図)の作成を通して、重要なデータの流れについて参加者間で情報を共有します。つぎに、STRIDEモデルとよばれる脅威を洗い出すための観点を通してデータの流れを追い、思いつく懸念点を出し合っていきます。この洗い出し作業を脅威ブレストと呼んで、DFD上に付箋紙を張る方法で進めました。 Microsoft のSTRIDE定義 出典) https://docs.microsoft.com/ja-jp/azure/security/develop/threat-modeling-tool-threats このアプローチでは多くの懸念点を出し合うことができました。一方、実施上の課題も見えてきました。 当初は抽象度の高いDFDからはじめ、段階を踏んで具体性を高め、それに合わせて洗い出す脅威の具体性も高めていく予定でした。しかし、システムと常に向かい合う開発・運用チームはすでに具体的な懸念点を持っており、脅威ブレストよりも既知の懸念点を直接リスト化する方が効率的でした。 このように、すでに具体的な懸念点が認識されている場合、必然的にそちらにフォーカスすることになるためデメリットが生じました。新たな脅威を見つけ出すことが手薄になる点です。STRIDEで脅威を洗い出していくには、練度を上げることが必要と感じました。 また、脅威ブレストにしろ、既知の懸念点のリスト化にしろ、そこで出てきたものが本当に脅威であるかは、さらに踏み込んだ調査が必要となりました。短い開発サイクルで多くの機能を実装・改修する開発・運用チームにとっては、この活動のためのリソース確保が最大の課題といえます。 2.公開されているスレットモデル情報の活用 特定の技術については、すでにスレットモデルが検討され公開されているものがあります。今回の試みでは、OAuth2.0 に関する下記の文書を活用することができました。 datatracker.ietf.org 文書の扱いには作法があり、少し文書自体について説明をします。 この文書は、現在インターネットドラフトという状態で、まだ作業が進行中(work in progress)の文書です。2016年11月から作成され始め2021年4月にはドラフトの18版がでました。文書内に、この18版の有効期限は2021年10月と記されています。 また、BCP(Best Current Practice)という種類の文書に属し、OAuth2.0実装におけるセキュリティ面のベストプラクティスを検討している文書ということになります。 文書の第2章には実装時のセキュリティに関する推奨事項がまとめられています。内容理解にはOAuth2.0に関する前提知識が必要とされますが、OAuth2.0については書籍の出版もあり、インターネット上からも多くの情報を得ることができるので、それらを参照しながら理解を進めていくことができます。 第3章ではどのような攻撃者を想定する必要があるのか確認することができ、第4章では具体的な脅威および対策について知ることができます。 第2章の推奨事項や第4章の対策事項は「MUST(NOT)」や「SHOULD」などの形で表現され、対策の必要性について強弱が分かるようになっています。これらの表現が対策優先度の判断において拠りどころとなり、たいへん便利といえます。各表現の定義は、RFC2119、RFC8174で確認することができます。 出典) https://datatracker.ietf.org/doc/draft-ietf-oauth-security-topics/ datatracker.ietf.org datatracker.ietf.org 今回の試みでは、モニタリング対象のシステムに当てはまる脅威を文書から探し出し、先の「MUST(NOT)」や「SHOULD」を考慮しながら確認項目をリストアップすることができました。 このような文書を活用できるのは、脅威の考慮漏れを防ぐという面でも大変有効だと感じられます。また、具体的な対策まで知ることができるので脅威を洗い出した後の対策検討も楽になります。 一方、このアプローチにおいても課題がないわけではありません。先ほど「システムに当てはまる脅威」と書きましたが、OAuth2.0の実装の詳細を知るために、DFDよりも詳しいシーケンス図を必要としました。開発チームからは詳細な図を提供いただき確認項目のリストアップが可能になりました。 まとめ ブレストを通してざっくばらんに懸念事項を出していくアプローチ、特定の技術について公開された文書から核心部を確認していくアプローチ、両方にメリットがあることは確かだと感じました。 そして、スレットモデリングという手法にトライしながら、脅威があるかもしれない事項に気づいたとき、気づいた脅威に特化してその有無を検証する作業が必要になりそうだと感じました。 スレットモデリングの方法として、STRIDEとともに紹介されることのあるPASTAという方法が有効ではないかと考えています。 今回は、セキュリティのモニタリングにスレットモデリングを取り入れる試みについて、大まかに紹介させていただきました。 この試みをすすめるなかで、ご紹介できるような失敗談、成功事例などが出てくると思います。それらを、また別の機会にご紹介できればと思います。 LIFULLでは共に成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
こんにちは。テクノロジー本部のyoshikawaです。好きなW3C Recommendation は RDF 1.1 Concepts and Abstract Syntax です。 会議やチャットでのやり取りの決定事項・議事録、アプリケーションや機能の設計書・仕様書、READMEなどなど... LIFULLの開発現場においては、ソースコード以外にもこのように様々な文書の管理・蓄積(=ドキュメンテーション)を実施しています。 多くの開発者・メンバーがドキュメンテーションの重要性やその恩恵は理解はしているものの、なかなかうまく情報の蓄積・管理ができない、 その結果、本質的ではない調査に時間を取られてしまいDeveloper Experienceが下落してしまう。 このような課題を抱えているプロジェクトやチームは世の開発現場において少なからず存在すると思います。 LIFULLの開発現場にもこの課題は当てはまります。 私が参加しているバックエンド刷新プロジェクトではドキュメンテーションにまつわる課題を解決すべく様々な取り組みを行ってきました。 この記事では私が参加しているバックエンド刷新プロジェクトを中心に実施している、継続的なドキュメンテーションを可能にする仕組みについて紹介します。 バックエンド刷新プロジェクトについてはこちらの記事をご覧ください。Clean Architectureを採用しバックエンドAPI開発を行っています。 www.lifull.blog 想定している対象読者 ドキュメンテーションをうまく機能させるための仕組みやヒントを知りたい人 社内WikiやREADME(Markdownファイル)でのドキュメントの作成と管理に限界を感じている人 GitHub Discussionsの活用事例を知りたい人 どうやってドキュメンテーションを実施しているのか LIFULLでは主に社内Wikiツールを利用してドキュメンテーションを実施しています。 メンバーがエンジニアのみで構成されているチームでは専用GitHub Repositoryを作成しMarkdownファイルをバージョン管理したり、 GitHub Wikiなどの機能を活用しているケースもあります。バックエンド刷新プロジェクトもそのケースの一つです。 現在は主に社内Wiki、ドキュメンテーション専用GitHub Repository、そしてGitHub Discussionsの3つをツールとして活用しドキュメンテーションを実施しています。 ドキュメントの性質とツールの使い分け ドキュメントの性質によって、バックエンド刷新プロジェクトでは以下のようにツールを使い分けています。 以下で挙げているツール以外にGitHub Wikiも利用していた時期もありました。記事の本題と逸れるため経緯は割愛しますがGitHub Repositoryへと移行しています。 社内Wiki: 技術要素が薄くエンジニア職以外が見ることを想定しているもの、便宜上社内Wiki内の機能を利用したいもの 例: プロジェクト憲章、Retrospective資料 GitHub Repository: 技術的な内容に関する規約、ルール、ベストプラクティスや学習資料など、「決まりきったもの」 例: コーディング規約、推奨実装パターン、Clean Architecture学習資料 GitHub Discussions: 技術的な内容に関するメモやログ、構想など、「とりあえず記録しておきたいもの」 例: アーキテクチャに関する規約やパターンの提案、決定事項のメモ(Architecture Decision Records) このように社内Wikiは必要性が生じた際に利用し、なるべくGitHub上に情報を蓄積させていく運用をしています。 当初は通常のソースコード管理と同様に、GitHub Repositoryで全てのドキュメントのバージョン管理を実施していましたが、これからお伝えするような「辛さ」が生じてしまったために、 「決まりきったもの」はRepository内に、「とりあえず記録したいもの」はArchitecture Decision Recordsの考えの下、GitHub Discussionsに蓄積する運用に変更しました。 GitHub Repositoryだけでドキュメントを管理してみた結果と課題 どうやって運用してきたのか 当初はGitHub Repositoryのみで技術的なドキュメントを管理していました。 個人ブログや各種ブログサービスの記事管理と同じように、通常のソースコードと同様にドキュメント(.mdファイル)を管理するというものです。 ドキュメンテーション専用Github Repository ドキュメントが作成・更新されるまで ドキュメントの作成や更新の際には、ドキュメントの体裁が崩れることや用語の表記揺れを防ぐためにtextlintというライブラリを利用してドキュメントのフォーマットを実施しています。 github.com 下記のように設定ファイルを作成し体裁や表記の揺らぎを防止しています。 version : 1 rules : - expected : Library patterns : - ライブラリ - expected : Application patterns : - アプリケーション - expected : CleanArchitecture patterns : - clean architecture - Clean architecture - Clean Architecture - クリーンアーキテクチャ - クリーンアーキテクチャー ローカルではnpm script, pushした後もGitHub Actionsでtextlintを実行&規約指摘するように設定し、 文章の体裁やフォーマットの揺らぎを防ぎつつ、ドキュメントの作成・更新を実施してきました。 ドキュメント中の文章の体裁やフォーマットだけでなく、ドキュメントのファイル名やドキュメントの階層構造の適切さもレビュー時にチェックするようレビュー体制の運用もしてきました。 この一連の厳格なドキュメンテーション更新フローによって読者に優しく読みやすいドキュメントを提供できます。 しかし、厳格なドキュメンテーションのフローを変更の多いプロジェクトにおいて適用することはドキュメント提供者への負荷となり得ます。 浮上した課題: 書くのが「辛い」 ドキュメンテーションに対して課題を持ちながらも、開発者は人間なので構造化され整ったドキュメントを書くことが辛いということもあります。 実際に運用しているとちょっとしたドキュメントの変更をする際にもtextlintからの厳格な指摘をpassする必要があり、「文章を書く辛さ」が目立つようになりました。 textlintの設定を緩和すれば多少の改善になりますが、ドキュメントの品質は維持したいため根本的な解決にはなりません。 文章を書く辛さ以外にも、「階層構造を維持する辛さ」もまた見逃すことはできませんでした。 意気揚々とブログを始めて最初は丁寧に記事の階層構造やカテゴリ管理をしていたがやがて面倒になった、そんな経験を持ったことがある方もいるかと思います。 ドキュメンテーション不全が招くディストピア ステークホルダーが多いため、仕様変更や実装規約、推奨実装パターンなどの変更をなるべく最新の状態に保つ必要があるという、バックエンド刷新プロジェクトの性質上、 変更の頻度の多さに乗じて膨れ上がるこの辛さは無視できるものではありません。 ドキュメントを書くことが辛くなると、ドキュメンテーション専用Repositoryを触る/見るのが辛くなります。 開発プロセスにおいて、往々にしてドキュメンテーションは優先度が低くなり、後回しにされがちです。 やがて優先度が低くて誰も更新したがらないドキュメンテーション専用Repositoryが出来上がります。 そして誰も更新しないのでRepository内のドキュメントは陳腐化し、ドキュメントにすべき知識は個人のナレッジとして埋没してナレッジの共有が機能不全に陥り、 挙句には調査工数として新規開発者にそのツケがのしかかることでしょう。 実際にはさほど深刻な状態にはなっていませんでしたが、「ちゃんとした文章を書くのが辛い」ことに起因してナレッジの共有が失敗することは無視できないリスクです。 解決策 開発者ガイドラインを敷くことで紳士協定的の名の下にドキュメンテーションを強制するのではなく、 そして、ドキュメントとして機能するような適度にまとまった文書を作成可能でありながら、ドキュメンテーションの辛さを解消できる別の仕組みを用意できないか? それこそが GitHub Discussions を利用した Architcture Decision Records(ADR) です。 docs.github.com GitHub DiscussionsでのADR ADRとは? 以下はThoughtworksのTechnology Radarからの引用です。 Much documentation can be replaced with highly readable code and tests. In a world of evolutionary architecture, however, it's important to record certain design decisions for the benefit of future team members as well as for external oversight. Lightweight Architecture Decision Records is a technique for capturing important architectural decisions along with their context and consequences. We recommend storing these details in source control, instead of a wiki or website, as then they can provide a record that remains in sync with the code itself. For most projects, we see no reason why you wouldn't want to use this technique. www.thoughtworks.com バックエンド刷新チームでは以下のことに着目してADRの考え方をドキュメンテーションに取り入れました。 決定事項の経緯や背景を第三者が閲覧可能 軽量なドキュメンテーション 第三者が簡単にコメント、編集可能 元の定義ではアーキテクチャに関する決定事項やその背景を記録することに焦点を当てていますが、バックエンド刷新チームでは実装に関する事柄も含めアーキテクチャ以外の事柄でも記録しています。 GitHub Discussionsを採用する プロジェクトメンバーも、ステークホルダーもエンジニアが中心であるため、GitHubが提供する仕組みを利用してADRを実現することは自然な流れでした。 そして、スレッド形式で簡単にドキュメントの作成、第三者の編集/コメントが可能であり、ドキュメントをカテゴライズ、タグ付け可能なGitHub Discussionsを採用しました。 以下はDDD(ドメイン駆動開発)におけるDomain Serviceを巡ってのADRの実例です。 ADRの実例. とりあえず残しておきたいことをメモしています なお採用当初、GitHub DiscussionsはBeta版でしたが2021年8月17日にBeta版から正式版になりました。 github.blog GitHub Discussions × ADRを実践してみた効果 意図していたドキュメンテーションの負荷削減に加え、コミュニケーションにおいて意図していなかった効果がありました。 とりあえず残しておきたいことのドキュメンテーションが楽になった 大事なことだけど、とりあえず書き殴りのメモで残しておきたい、埋もれがちなテキストチャット上でのやりとりを保存しておきたい、 といったことを気軽に残せるようになりました。 その結果、大事な情報が個人のナレッジとして埋没してしまう機会が減りました。 下記のように、様々な粒度・トピックの内容を気兼ねなく残せています。 GitHub Discussions上にあるスレッド一覧 よって、当初課題となっていた「文章を書く辛さ」はGitHub Discussions × ADRによって狙い通りに克服できました。 Repositoryにあるドキュメントの管理も楽になる ADRを取り入れていなかった以前、日数が経ってからドキュメンテーションをする場合は何を書くべきか思い出す/書くべきことを関係者にヒアリングすることが必要であり、 Repository上に残しておきたいような体裁の整ったドキュメントを作成するコストが高くなっていました。 ADRとして「とりあえず」残しておいた情報があることで、そのようなコストが発生することを予防できます。 なお、RepositoryはDiscussionsを採用した後も「決まりきったもの」を残す場として活用し続けています。 事例こそ多くありませんが、Discussionsで記載された「とりあえず残したいもの」を「決まりきったもの」としてRepositoryに残すという運用を実施しています。 スレッドを階層構造ではなくラベリングやタグ付けで管理できる ドキュメントを見つけやすくするためにもドキュメントに階層構造などのメタ情報を持たせることは重要です。 以前の運用ではドキュメントをディレクトリに格納し階層構造持たせていたため、やや管理コストがかかっていました。(ドキュメントの階層構造を変更するためにcommitする必要がある) GitHub Discussionsで管理した場合、カスタムのラベルやタグによって各スレッドを管理・検索できるため、ドキュメントのメタ情報の管理コストが低くなりました。 そのため、第二の辛さである「階層構造を維持する辛さ」も解決することができました。 予定外の効果: 部署間のコミュニケーションチャンネルが増えた 当初は意図していませんでしたが、GitHub Discussionsを採用したことによる恩恵もありました。 プロジェクト内でのADRとして利用してきましたが、プロジェクト外の方でも広く閲覧可能&コメント可能にしてあります。 そのため、プロジェクト外の方からのフィードバックをいただけるような機会が増え、ドキュメントの質を向上させることに繋がりました。 また、チャットツール(Slackなど)で発生した他部署からの質問対応のやりとりをGitHub Discussionに転載・蓄積・転用することでコミュニケーションコストの削減にも役立っています。 現在はバックエンド刷新チームがモデレーターとなり新バックエンド基盤(新BFF)の開発コミュニティを運営していますが、 今後は開発者が増え、社内開発者コミュニティとして醸成されゆくことを期待しています。 カテゴリ一覧. プロジェクト外の方も見やすいようにカテゴライズ おわりに 以上がバックエンド刷新チームで実施しているドキュメンテーションです。 先日Beta版から正式版となったばかりということもあり、GitHub Discussionsは社外/社内どちらからの視点でもその採用事例は多くはないです。 しかしその効果は確かなもので、本記事で紹介したドキュメンテーションとしての用途の他にも、著名なOSSコミュニティでもコミュニケーションフォーラムとして利用されています。 ドキュメンテーションで悩む方、あるいはGitHub Discussionsの利点がわからない、といった方に本記事が参考になれば幸いです。
こんにちは。LIFULL でネイティブアプリのスペシャリストをしている菊地です。 今回は LIFULL HOME'S アプリにおけるプッシュ通知の役割やアーキテクチャの変遷についてご紹介させていただきます。 一般的にスマートフォンアプリにとって プッシュ通知 というのはユーザーとのコミュニケーションのために重要な役割を持っています。 SNS などでコメントやメッセージが来た際にリアルタイムに気付くための通知 ゲームやマンガアプリなどで、時間によって回復するライフなどが回復したことを知らせる通知 サービス内における重要な情報に気付いてもらうための通知 EC やオークションアプリなどで気になる商品の値段が下がったり、高値がついた場合にお知らせする通知 など様々なシーン、目的で使われています。 LIFULL HOME'S アプリにおけるプッシュ通知とは LIFULL HOME'S アプリでは、どのようなシーン、目的で使っているかというと 住まい探しをしているが条件はほぼ決まっており物件が出てくるのを待っているユーザーに対して、最新の情報を提供する 気になっている物件について掲載が終了してしまって見れなくならないように期限をお知らせすることで見逃さないようにする サービスからユーザーに対して重要な内容を告知する ある行動をした際に次のステップにつなげてもらうための通知 一定期間アプリを利用していないユーザーに対して、アプリに戻ってきてもらうための通知 などがあります。 その中でも今回は 1 の新着物件通知 と 2 の掲載期限通知 と呼ばれる「ユーザーに対して有益な情報を提供」するための通知をご紹介します。 新着物件通知 目的 ユーザーが自分の探している条件で毎回アプリを立ち上げて探す手間を省く 探している条件で新しい物件が出てきたタイミングでユーザーのアプリの起動を促す Android アプリ 配信時間 :朝、昼、夜の3つの時間帯で指定可能 配信条件 :アプリ内で 保存した検索条件 がある場合はその中から新着物件を。保存した検索条件がなければ 前回検索条件 から新着物件を検索して通知する ※ Android では 保存した検索条件 もしくは 前回検索条件 のどちらか一つの条件で送る仕様 iOS アプリ 配信時間 :夜のみ 配信条件 : 保存した検索条件 で「通知を受け取る」にチェックを入れたものの新着物件を検索して通知する ※ iOS では複数の 保存した検索条件 をまとめて送る仕様 掲載期限通知 目的 ユーザーがお気に入りに登録している物件の掲載がいつの間にか切れてしまい問合せを失ってしまうという機会損失を減らす 問合せの機会損失を減らしつつ、ユーザーがアプリを起動するきっかけを作る 配信時間 :夕方 配信条件 :お気に入りに登録している物件の掲載期限を確認して、期限切れ数日前の物件があれば通知する ※ Android のみ アーキテクチャの変遷 初期 開発工数の関係で、プッシュ通知ではなくアプリ内で実装を行い、バックグラウンドで定期的に処理を走らせてローカル通知を行なっていました。 ただし、毎回バックグラウンドで処理をして API を呼び出して受け取った結果を通知するため、導入コストは低かった反面、改修する際のコストは高くなっていきました。 具体的には、A/B テストの実施のたびにリリースが必要となることや、通知を送るための条件や内容を変更するたびにリリースが必要となるといったことが挙げられます。 さらに、Android の場合は OS のアップデートにより、バックグラウンド処理に対する制限が厳しくなっていきました。何も対処をしないと バックグラウンドで API を呼び出している間にプロセスが終了してしまい通知が行われない 通知の対象とならなかったため通知されなかったのか?通知の対象となっていたが通知が行われなかったのか?の判別が困難 指定時刻に通知を行いたいのに、ユーザーがアプリを立ち上げた瞬間などに突然通知が行われる という問題が出てきました。 このままではユーザー体験としておかしくなるため、改修を重ねて行きましたが、継続的に改修をしようとすればするほどサーバーサイドでロジックを持ち自由に A/B テストができる環境を求める声がチーム内で強くなってきました。 そこでプッシュ通知に移行することを検討したのですが、要件として 新着物件通知 :ユーザー毎の検索条件に合わせた新着物件 掲載期限通知 :ユーザー毎のお気に入り物件の掲載期限 といったユーザーごとに違う情報を用いて通知を行う必要がありました。 最初に検討した Firebase ではユーザーセグメント単位での通知は簡単に利用することができますが、ユーザー毎に違うデータを元に通知を送るということが難しいため、サーバー側でサードパーティーのプッシュ通知を送る必要が出てきました。 移行期 ローカル通知における課題などを解決するにあたり、プッシュ通知として今後どういうことを実現するか?という要件定義から行って、新たに仕様を作ることになりました。 様々な要件を定義して施策を行ったのですが、わかりやすいものとしては「配信時間帯(朝・昼・夜)の中でもユーザー毎に開封してもらいやすいタイミングに合わせて配信する」というものがあります。 新着物件 及び 掲載期限通知 それぞれで朝・昼・夜(夕方)という配信時間帯の中でも「ユーザーのライフスタイルに合わせてスマートフォンをみる時間にはばらつきがあるため、朝・昼・夜(夕方)ごとに基準となる配信時間を設けて、そこからユーザー毎に時間をずらすことで開封率の向上を狙うということを行いました。 これを実現するためには 時間帯別の配信リストを作成する 配信時間帯毎に配信リストを処理する ユーザー毎に異なる条件を元に検索を行い、通知を送る という手順を踏む必要がありました。 この時に出来る限りサーバーレスで、かつ今後も継続的に改修がしやすい構成でと考え、Cloud Functions をベースとして Cloud PubSub と組み合わせた構成に移行しました。 アーキテクチャ 時間帯別の配信リストの作成 時間帯別の配信処理 メリット 処理毎に疎結合となっているため、テストや改修が非常にしやすい 今後の改修でアーキテクチャを移行することになっても一部のみ切り替えということが可能になるため、メンテナンス性が非常に高い サーバーレスのみで構成しているため、通知配信用のサーバーなどを用意する必要がなく運用の負担が低い デメリット 配信リストの作成、時間帯別の配信処理毎に Cloud Scheduler が存在してしまうため、管理が大変になる ユーザー数が増加した場合や処理が複雑になった場合に、Cloud Functions の実行時間の制限に引っかかる恐れがある Cloud Firestore の Read / Write の頻度が高く、単位時間あたりの制限に引っかかる恐れがある API server に対して、Cloud Functions からのリクエスト制限をかけるのが難しいため、大量のリクエストが同時に発生する恐れがある 現在 移行期のアーキテクチャでも運用はできていたのですが、プッシュ通知をより使いやすくと改修を繰り返していくうちに想定よりも早い段階で、移行期のアーキテクチャのデメリットとしてあげていた「ユーザー数が増加した場合や処理が複雑になった場合に、Cloud Functions の実行時間の制限に引っかかる恐れがある」という懸念が現実の問題となり始めていました。 元々、Cloud Functions の実行時間の制限を回避するために、Cloud Functions -> Cloud Pub/Sub で配信対象ユーザーの抽出を段階的に行なっていましたが、一度に処理できる量の問題や処理の回数が増えてきてしまい途中で失敗するといったことが懸念としてありました。 また、該当部分のロジックは特にスケールする必要もなく Cloud Functions でなくてもよいため、より実行時間の制限が長い Cloud Run での抽出に移行することになりました。 アーキテクチャ Cloud Run + Cloud Tasks + Cloud Functions 処理の流れとしては Cloud Scheduler-> Cloud Run で配信時間帯毎にユーザー抽出を行い、配信時間帯の中でさらにユーザーごとに時間を調整して Cloud Tasks にタスクを追加する Cloud Tasks では設定された時刻になるとタスクが実行され、Cloud Functionsを呼び出す Cloud Functions は Cloud Firestore にアクセスを行い、通知に必要となるデータの取得や加工を行い、FCM に通知を送る という流れになります。 メリット Cloud Functions から Cloud Run にしたことで、実行時間の制限が緩和された Cloud Scheduler から Cloud Tasks にしたことで、管理コストが減少 エラー発生時の再送処理などを全て Cloud Tasks に任せられるようになった API server へのアクセスのスロットリングを Cloud Tasks で簡単に調整できるようになった デメリット Cloud Functions は Typescript、Cloud Runは golangと別の言語で作ってしまったため、開発に学習が必要になった(チームのエンジニアに学習機会を提供するという目的もあったので負債ではない) 処理の流れも、構成も移行期のものよりもだいぶスッキリしたものとなりました。 さらに懸念されていた処理量の増加についても問題なく処理ができるパフォーマンスを出せているため、当分はこの構成でユーザー毎にパーソナライズされた通知を配信していくことになると思います。 最後に 今回は LIFULL HOME'S アプリを支えるプッシュ通知について、実際に行っているプッシュ通知の例とアーキテクチャの変遷をご紹介させていただきました。 ご紹介したものはローカル通知でも実現可能ではありますが、プッシュ通知にすることで、より良い体験を継続的に提供できるようにすることができました。LIFULL HOME'S アプリではサービスの成長に合わせて施策が実行できるようにするためにアーキテクチャの刷新を継続的に行っています。 また、紹介した通知以外にもアプリでは様々な通知を行なっており、Firebase を用いたユーザーセグメントに対する通知や、ユーザーの行動をトリガーとしたリアルタイムな通知なども行なっております。 プッシュ通知は便利な反面、送り過ぎてしまうとユーザーにとって有益なものでなくなってしまう場合もあるため、今後もカスタマイズを続けていきユーザーにとって最適なタイミングで最良の情報を提供できるようにしていきたいと思います。 LIFULLではメンバーを募集しております! カジュアル面談もありますのでご興味ある方は是非ご参加ください! hrmos.co
こんにちは。プロダクトエンジニアリング部の加藤です。 皆さん、リモートでのチームビルディングはどのように行っていますか? 弊社では本格的にリモートワークを導入し一年が経過したところとなりますが、リモート環境下でのコミュニケーションや組織形成の課題に対しさまざまな解決策を模索し取り組んできています。 そのような中、今回は競技プログラミングを用いたチームビルディングを行ったので紹介したいと思います。 どのように行ったか 今回の主な目的はチームの結束力の強化です。 部署のメンバーを複数のチームに分け、一つの問題をチームメンバーで協力して解答し勝敗を競うチーム対抗戦の方式にて実施しました。 題材として「AtCoder」の過去問を、コミュニケーションツールとして「Zoom」を利用しました。 atcoder.jp 基本ルール 3人1組のチーム対抗戦 40分の間に獲得した得点により勝敗を決定 得点は問題の難易度により設定(事前に運営が問題を複数ピックアップ) 問題に正解することで設定された得点を獲得 言語の選択は自由 チームの中で実際にコードを書くのは一人だけ(モブプロ方式) コードを書く人が画面共有を行う 途中で役割の変更はOK やってみてどうだったか やってみて良かった点、イマイチだった点・改善点は以下の通りです。 良かった点 業務で接する機会が少ないメンバーともコミュニケーションが取りやすかった 複数人で協力してコーディングする機会を強制的に設けられたことでモブプロへのハードルが下がったように感じられた 業務でもモブプロ・ペアプロを積極的に導入することで知識共有の効率化が図れそう 単純に楽しかった! 業務とは違った頭の使い方ができて刺激的だった イマイチだった点・改善点 40分では時間が短かった チーム内に一人実力者がいると力が偏ってしまい協力が難しい 標準入力の取得方法などAtCoder独自の利用方法の把握に手こずった 改善して再トライ! 実施後のアンケート結果から、チーム内にて経験値や知識量に差があると協力体制を築くことが難しいと分かりました。 そこで、チーム内での知識の偏りを減らし、なるべく各メンバーが活躍できる機会を設けるために新たに以下のルールを追加し再トライしました。 各チーム3言語以上利用すること 各チームランダムに縛りを適用(ワンライナーにて解答、if・for禁止など) これにより会話が増えて距離が縮まったという意見もあり、よりチームビルディングとしての効果が高まったように思います。 まとめ 今回はチームの結束力を強めるため実施したチームビルディングについて紹介をさせていただきました。 リモートワークの普及により業務効率化などのメリットが生まれる一方で、いまだコミュニケーションや組織形成の課題を抱える組織も多くあるかと思います。 今回紹介した取り組みが課題解決の一つの手段として参考になれば幸いです。