TECH PLAY

株式会社LIFULL

株式会社LIFULL の技術ブログ

652

KEELチーム の相原です。 前回のエントリは「比較的安全にMCPサーバを動かす」でした。 www.lifull.blog 今回は信頼できる可観測性基盤を提供するべく、KubernetesクラスタにおけるPull型アプローチ由来のログ・メトリクス欠損と色々向き合った話を書きます。 Pull型アプローチとそのトレードオフ kubeletにログファイルを削除される前にFluentdに読ませたい eBPFでunlinkat(2)を遅延実行させる PrometheusがCounterのインクリメントをScrapeするまでPodを待機させたい SIGTERMを受け取ったら次回のScrapeまで待機するプロキシを挟む まとめ Pull型アプローチとそのトレードオフ まずPull型アプローチについて軽く触れておきます。 Pull型アプローチとは、ログやメトリクスをその持ち主がどこかに公開しておいて、FluentdやPrometheusなどの他の誰かに取得しに来てもらうアプローチを指します。 対となるPush型アプローチは逆に持ち主がログやメトリクスを直接対象に送りつけるというものです。 Push型アプローチはサーバ側で流量のコントロールが難しかったり、Rate Limit時などのリトライの責任がクライアント側に要求される一方で、Pull型アプローチはその点が単純という関係性にあります。 Kubernetesクラスタで素直にログとメトリクスを収集しようと思うと、大体Pull型のアプローチを採用することが多いのではないかと思います。 コンテナランタイムはコンテナの標準出力をファイルとして出力しているのでそれをFluentdやPromtailで収集し、メトリクスはPrometheus Exporterで公開してPrometheusがScrapeするといった感じです。 ここで考えたいリスクが、「 Podが削除される時までに本当にそのログ・メトリクスは収集されているのか 」というものです。 Kubernetes上でコンテナランタイムを司るkubeletは、Podの削除時にログのファイルを削除します。 Podが削除されれば当然Scrapeするためのエンドポイントも無効になるためメトリクスも収集できません。 削除直前に出力されたログやインクリメントされた Counter はどうなるのでしょうか。 恐らくそれらは欠損している可能性が高いです。 Fluentdは比較的すぐにログを読みますがそれでも読み込みが間に合わず欠損することはありますし、PrometheusのScrapeの間隔は一般に短くとも秒単位なので望みは薄いです。 kubeletにログファイルを削除される前にFluentdに読ませたい まずはログの欠損から考えていきましょう。 最初に思いつく対処はkubeletがログを削除するまでに遅延を入れたり、削除を無効化にするといったことだと思います。 それはこの辺のIssuesで議論されていますが、今のところこれといった方法はありません。(Grafana AlloyとかはKubernetes API経由でのログ取得にも対応していますが、こちらの削除タイミングも同様のはずです) github.com github.com かといって全てのPodにログ転送用のサイドカーをデプロイして送信というのも辛いのでどうにかプラットフォーム側で対処したいところです。 こんな時、eBPFはいつでも私達の銀の弾丸になってくれます。 (eBPFとは、という話は以前に書いたのでこちらをご覧ください) www.lifull.blog eBPFでunlinkat(2)を遅延実行させる アプローチとしては、eBPFでkubeletが発行するファイル削除のunlinkat(2)をhookしたら bpf_override_return でファイルを削除することなく終了コードを偽装して返し、裏でFluentdが対象のファイルを読み切ったことを確認出来たら実際の削除を行うというものです。 Fluentdは *.pos ファイルで読み込んだファイルのバイト数を記録しているため、これを見ればファイルを読み切ったことを確認できます。 まずはこんな感じにunlinkat(2)をhookしましょう。 bpf_override_return は kprobeの一部の関数 からしか利用できないことに注意してください。 // /sys/kernel/debug/tracing/events/syscalls/sys_enter_unlinkat/format SEC ( "tracepoint/syscalls/sys_enter_unlinkat" ) int sys_enter_unlinkat ( struct trace_event_raw_sys_enter *ctx) { u64 __pid_tgid = bpf_get_current_pid_tgid (); gid_t tgid = __pid_tgid >> 32 ; pid_t pid = __pid_tgid; struct task_struct *task = ( struct task_struct *) bpf_get_current_task (); u32 ppid = BPF_CORE_READ (task, real_parent, tgid); if (tool_config.this == tgid || tool_config.this == ppid) { return 0 ; } const char *pathname = ( const char *)ctx->args[ 1 ]; int flag = ( int )ctx->args[ 2 ]; // AT_REMOVEDIR // https://elixir.bootlin.com/linux/v6.10.6/source/include/uapi/linux/fcntl.h#L104 if (flag & 0x200 ) { return 0 ; } struct arg arg = { .pathname = pathname, }; bpf_map_update_elem (&args, &pid, &arg, BPF_ANY); return 0 ; } SEC ( "kprobe/" SYS_PREFIX "sys_unlinkat" ) int BPF_KPROBE (sys_unlinkat) { u64 __pid_tgid = bpf_get_current_pid_tgid (); gid_t tgid = __pid_tgid >> 32 ; pid_t pid = __pid_tgid; struct arg *argp = bpf_map_lookup_elem (&args, &pid); if (!argp) { return 0 ; } int zero = 0 ; struct event *eventp = bpf_map_lookup_elem (&unlinkat_heap, &zero); if (!eventp) { goto end; } eventp->tgid = tgid; eventp->pid = pid; eventp->uid = bpf_get_current_uid_gid (); eventp->pathname[ 0 ] = '\0' ; if ( bpf_probe_read_user (eventp->pathname, sizeof (eventp->pathname), argp->pathname) < 0 ) { goto end; } u8 directory[DIRECTORY_MAX]; if ( bpf_probe_read_kernel (directory, sizeof (directory), tool_config.directory) < 0 ) { goto end; } if (! filter_directory (directory, eventp->pathname)) { goto end; } bpf_override_return (ctx, 0 ); bpf_perf_event_output (ctx, &events, BPF_F_CURRENT_CPU, eventp, sizeof (*eventp)); end : bpf_map_delete_elem (&args, &pid); return 0 ; } bpf_override_return(ctx, 0); によって即座にkubeletに返るので、これでファイルの削除処理をスキップすることができます。 unlinkat_heap は PERCPU_ARRAY にしているのでnull byteを区切り文字にしています。 Kubernetesのノード上で実行することになり余計なunlinkat(2)も流れてくるため、 filter_directory でcontainerdのログディレクトリ以外のイベントは無視しなければなりません。 if (tool_config.this == tgid || tool_config.this == ppid) は、ファイル削除を遅延実行する関係上unlinkat(2)を自身も実行するため、無限ループ防止のために hostPID: true における自身のpidを this として受け取ってスキップしています。 あとは std::ffi::CStr::from_bytes_until_nul でユーザ空間から pathname を取得して、 *.pos ファイルと突合しながらFluentdが読み切るまで待機してファイルを削除するだけです。 そこそこ流量は多くなるので、mtimeを見て適当に *.pos ファイルはキャッシュしておきます。 while let Some (pathname) = unlink_rx. recv ().await { let current_mtime = std :: fs :: metadata ( & args.pos_file) . and_then ( | m | m. modified ()) . unwrap_or ( std :: time :: SystemTime :: UNIX_EPOCH); let needs_refresh = pos_cache. read ().await.mtime != current_mtime; if needs_refresh { let new_pos = parse_pos_file ( & args.pos_file); let mut cache = pos_cache. write ().await; cache.mtime = current_mtime; cache.entries = new_pos; } if let Some (offset) = pos_cache . read () .await .entries . get ( & pathname) . map ( | (offset, _) | * offset) { let path = std :: path :: Path :: new ( & pathname); if let Ok (metadata) = path. metadata () && metadata. len () != offset { let unlink_tx = unlink_tx. clone (); tokio :: spawn (async move { tokio :: time :: sleep ( std :: time :: Duration :: from_secs ( args.delayed_seconds, )) .await; if let Err (e) = unlink_tx. send (pathname. clone ()) { eprintln! ( "Failed to re-queue {}: {}" , pathname, e); } }); continue ; } } let result = std :: fs :: remove_file ( & pathname). or_else ( | e | { if e. kind () == std :: io :: ErrorKind :: IsADirectory { std :: fs :: remove_dir ( & pathname) } else { Err (e) } }); if let Err (e) = result { if e. kind () == std :: io :: ErrorKind :: DirectoryNotEmpty { if let Ok (entries) = std :: fs :: read_dir ( & pathname) { for entry in entries. flatten () { let entry_path = entry. path (). to_string_lossy (). to_string (); if let Err (e) = unlink_tx. send (entry_path. clone ()) { eprintln! ( "Failed to queue {}: {}" , entry_path, e); } } } let unlink_tx = unlink_tx. clone (); tokio :: spawn (async move { tokio :: time :: sleep ( std :: time :: Duration :: from_secs ( args.delayed_seconds, )) .await; if let Err (e) = unlink_tx. send (pathname. clone ()) { eprintln! ( "Failed to re-queue {}: {}" , pathname, e); } }); } else { eprintln! ( "Failed to remove {}: {}" , pathname, e); } } } kubeletが実行するGoの os.RemoveAll はファイルとディレクトリを区別せずにとりあえずunlinkat(2)してくるので、ユーザ空間側では中身を削除しながらディレクトリが空になるまで待機するようにしています。 本来は EISDIR を受け取ってからAT_REMOVEDIR付きのunlinkat(2)にフォールバックするんだと思いますが、今回はbpf_override_returnでEISDIRを握り潰してしまっているので、 os.RemoveAll 前提の気持ち悪さはありつつもこちらで削除まで責任を持ちます。 (ファイルのみが渡ってくる vfs_unlink を使えるといいんですが、 ALLOW_ERROR_INJECTION されておらずこちらは bpf_override_return が使えません) また一つだけ注意点があって、containerdは /var/log/containers 以下にログファイルを作成しますが、実際にはこれは /var/log/pods へのシンボリックリンクとなっているため、unlinkat(2)が呼ばれる pathname とFluentdの *.pos ファイルが指すファイルが異なる可能性があります。 私はFluentd側を修正せず *.pos ファイルを読む時についでにシンボリックリンクも解決してしまいました。 この辺もあって *.pos ファイルはキャッシュしています。 fn parse_pos_file (path: & std :: path :: Path) -> std :: collections :: HashMap < String , ( u64 , u64 ) > { let mut pos = std :: collections :: HashMap :: new (); if let Ok (file) = std :: fs :: read_to_string (path) { for line in file. lines () { let mut parts = line. split_whitespace (); if let ( Some (path), Some (offset), Some (inode)) = (parts. next (), parts. next (), parts. next ()) { let resolved_path = std :: fs :: canonicalize (path) . map ( | p | p. to_string_lossy (). to_string ()) . unwrap_or_else ( | _ | path. to_string ()); pos. insert ( resolved_path, ( u64 :: from_str_radix (offset, 16 ). unwrap_or_default (), u64 :: from_str_radix (inode, 16 ). unwrap_or_default (), ), ); } } } pos } これで晴れてkubeletの実行するunlinkat(2)が握りつぶされ、ユーザ空間で安全にFluentdのin_tailを待ってから遅延削除されるようになりました。 同じようなアプローチはPromtailなどでも有効なはずです。 PrometheusがCounterのインクリメントをScrapeするまでPodを待機させたい 次はメトリクスの欠損です。 Gauge(UpDownCounterの実装がGaugeなこともありますが、用途としてのGauge)などのMetric typeは割とどうでもいいんですが、Counterなどは意外と重要なケースもあってインクリメントが欠損してしまうと困ることがあります。 LIFULLでは大きめのKubernetesクラスタをマルチテナントで運用しているということもあり、PrometheusがScrapeする間隔は30秒程度が限界で、Podが削除されるまでの30秒間のインクリメントが失われてしまうという問題がありました。 もっと短い間隔で運用できるのであれば、Kubernetes 1.29から利用できるようになった KEP-3960: Introducing Sleep Action for PreStop Hook で雑にsleepしてもいいですが、問答無用で30秒も待ってしまうとPodのロールアウトが遅くなってしまうためそうもいきません。 なるべくロールアウトに影響を与えないよう、きっちりScrapeされるまでを待てると理想です。 SIGTERMを受け取ったら次回のScrapeまで待機するプロキシを挟む 素直にはScrapeされたことを検知できないと思うので、間にプロキシを挟ことにしましょう。 アイデアはシンプルで、以下のようなPrometheusとPrometheus Exporterの間に挟んでScrape時刻を記録するプロキシを作ります。 Prometheus ExporterはPod削除時に送られてくるSIGTERMでそのままGraceful Shutdownされてしまうので、終了直前のメトリクスを cachedMetrics に入れておいてSingleHostReverseProxyのErrorHandlerでPrometheus Exporterがシャットダウンして疎通しなくなったらそれを返すようにしています。 type CachedResponse struct { Body [] byte ContentType string } var ( lastScrape atomic.Value terminating atomic.Bool scrapeChan chan struct {} cachedMetrics atomic.Pointer[CachedResponse] ) targetURL, err := url.Parse(a.TargetURL) if err != nil { return xerrors.Errorf( "failed to parse target URL: %w" , err) } proxy := httputil.NewSingleHostReverseProxy(targetURL) proxy.ErrorHandler = func (w http.ResponseWriter, r *http.Request, err error ) { if cached := cachedMetrics.Load(); cached != nil { w.Header().Set( "Content-Type" , cached.ContentType) w.WriteHeader(http.StatusOK) _, _ = w.Write(cached.Body) return } http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) } server := &http.Server{ Handler: http.HandlerFunc( func (w http.ResponseWriter, r *http.Request) { lastScrape.Store(time.Now()) if terminating.Load() { select { case scrapeChan <- struct {}{}: default : } } proxy.ServeHTTP(w, r) }), } そのためにこのプロキシはSIGTERMを受け取った時にPrometheus Exporterから最新のメトリクスを取得してからキャッシュし、 scrapeChan で待ち受けて次回のScrapeまで待機します。 SIGTERMの送信とEndpoint ControllerがEndpointを削除する処理は同時に行われるということは広く知られていて、LIFULLではこれへの対処のために全てのPodは終了時に数秒sleepするようにしています。 kubernetes.io 最新のメトリクスをSIGTERM時に取得する処理は、この設定とGraceful Shutdownの実装に依存している部分があることには注意したいです。 signal.Notify(quit, syscall.SIGTERM) <-quit ctx, cancel := context.WithTimeout(context.Background(), a.TerminationGracePeriod) defer cancel() t, ok := lastScrape.Load().(time.Time) if ok && !t.IsZero() { timeSinceLastScrape := time.Since(t) if timeSinceLastScrape > a.ScrapeWaitThreshold { if request, err := http.NewRequestWithContext(ctx, http.MethodGet, a.TargetURL, nil ); err == nil { if response, err := http.DefaultClient.Do(request); err == nil { defer func () { _ = response.Body.Close() }() if response.StatusCode < 400 { if body, err := io.ReadAll(response.Body); err == nil { cachedMetrics.Store(&CachedResponse{ Body: body, ContentType: response.Header.Get( "Content-Type" ), }) } } } } terminating.Store( true ) select { case <-scrapeChan: case <-ctx.Done(): } } } まとめるとこのプロキシは以下のように動きます。 常にPrometheusとPrometheus Exporterの間に入って最終Scrape時刻を記録しておく SIGTERMを受け取った時にScrapeWaitThreshold以内にScrapeされていなければ、Prometheus Exporterから最新のメトリクスを取得してキャッシュする 次回Scrape時にそのキャッシュからメトリクスを返して終了する これでPod終了直前にインクリメントされたCounterなどの値がPrometheusにScrapeされず欠損する問題を解決できました。 あとはPrometheus Operatorを使っているなら PodMonitor を、PodのアノテーションによるService Discoveryを利用している場合は metadata.annotations をこのプロキシに向けるだけです。 LIFULLではPodのアノテーションによるService Discoveryを利用しているため、 prometheus.io/wait: true と付与するとこのプロキシを注入するMutating Admission Webhookを開発して、このタイミングで prometheus.io/port もプロキシのポートに書き換えるようにしました。 これにより、利用者はPodアノテーションの付与をするだけで最小限のロールアウト遅延と引き換えに信頼性の高いメトリクスを得ることができます。 なお、KubernetesがSIGTERM送信後に諦めてSIGKILLを送るまでの spec.terminationGracePeriodSeconds のデフォルト値は30秒であるため、Scrape間隔が長いなどでこれを超えてプロキシが待機しなければならない場合はPodの spec.terminationGracePeriodSeconds を伸ばす必要もあります。 まとめ このエントリではKubernetesクラスタの可観測性基盤でよく採用されるPull型アプローチのトレードオフと、そのトレードオフへの対処を紹介しました。 一部コミュニティでは問題視されているものの、素直に利用していると意外と見落としがちな問題だと思っていて、お手元のKubernetesクラスタの監視を見直すきっかけになれば幸いです。 メトリクスはともかくログは欠損してしまうとそれなりに問題があるはずで、特にCronJobとかは実行ログを出力してから比較的すぐPodが終了することが多く、 successfulJobsHistoryLimit , failedJobsHistoryLimit を0にしていると直前のログはほぼ欠損していると考えてよいでしょう。 最近はトレースのSpanにログを持たせてしまうことも増えましたが、依然ログは重要な可観測性のシグナルだと思うので欠損がないように運用していきたいところです。 Fluentdのバッファはちゃんと設計しましょうとか、Prometheusの冗長化とか、Grafana Lokiは max_chunk_age の値に応じて送信が遅延してしまった同一ストリーム内の古いログを捨ててしまうので、fluent-plugin-grafana-lokiにパッチ当てて該当エラーの場合にUnrecoverableErrorを返すことでFluentdのsecondaryに流して、別ストリームとして送信し直すことで取りこぼさないようにしましょうとか、Pull型アプローチ由来以外の欠損を防ぐ話はまた別の機会に書きたいと思います。 ご興味をお持ちいただけましたら、ぜひ以下のページもご覧ください。 hrmos.co hrmos.co
アバター
LIFULLは、国立情報学研究所(NII)が運営する情報学研究データリポジトリ(IDR)に、 LIFULL HOME'Sデータセット を提供しています。 本記事では、先日開催された IDRユーザフォーラム2025 のご報告として、LIFULL HOME'Sデータセットを活用した研究発表や、LIFULLとしての取り組みについてご紹介します。 IDRユーザフォーラム2025とは 2025年5月、新たにLIFULL HOME'Sデータセットを追加 LIFULL HOME'Sデータセットを活用した研究ポスター発表 不動産情報の俯瞰的閲覧を可能にするVR探索インタフェース 公示地価・人口動態予測データを用いた機械学習による将来賃料の予測 スタートアップセッションにおける企業賞の授与 住宅ローン税制は住宅購入者の利益になっているか LIFULLからの発表:不動産データのビジネス応用事例 おわりに 一緒に不動産データの可能性を広げるエンジニアを募集しています 前回および前々回のフォーラムの様子については、以下の記事でもご紹介していますので、あわせてご覧ください。 www.lifull.blog www.lifull.blog IDRユーザフォーラム2025とは IDRユーザフォーラムは、NIIが提供する研究データ基盤を活用した研究成果を共有し、データ提供者・研究者・利用者が一堂に会して議論する場です。 不動産、医療、交通、社会経済など、分野横断的なデータ利活用の事例が紹介される点が特徴で、年々参加者・発表内容ともに広がりを見せています。 2025年5月、新たにLIFULL HOME'Sデータセットを追加 2025年5月には、IDRにおける LIFULL HOME'Sデータセットの拡充が行われました。 これにより、より多様な分析・研究テーマへの応用が可能となり、学術研究と実社会をつなぐ基盤としての価値がさらに高まっています。 データセットの詳細は、以下のページで公開されています。 情報学研究データリポジトリ LIFULL HOME'Sデータセット LIFULLとしては、引き続き「実社会で蓄積されたデータを、研究を通じて社会に還元する」ことを重視し、IDRを通じたデータ提供を進めていきます。 LIFULL HOME'Sデータセットを活用した研究ポスター発表 IDRユーザフォーラム2025では、LIFULL HOME'Sデータセットを活用した研究発表が複数行われました。 ここでは、特に印象的だった2件のポスター発表をご紹介します。 不動産情報の俯瞰的閲覧を可能にするVR探索インタフェース 中山 裕紀 氏,大島 裕明 氏(兵庫県立大学) ポスター資料 本研究では、不動産情報をVR空間上で俯瞰的に探索できるインタフェースが提案されました。 LIFULL HOME'Sデータセットを用いることで、従来のリスト表示や地図表示では捉えにくかった空間的・構造的な特徴を、直感的に把握できる点が示されていました。 不動産情報とXR技術の融合は、住まい探しや都市理解の新しい可能性を感じさせる研究事例でした。 公示地価・人口動態予測データを用いた機械学習による将来賃料の予測 佐藤 豪栄 氏,秦野 亮 氏,西山 裕之 氏(東京理科大学) ポスター資料 こちらの研究では、LIFULL HOME'Sデータセットに加えて、公示地価や人口動態予測データを組み合わせ、将来の賃料を機械学習によって予測する手法が提案されました。 不動産市場の将来予測という社会的ニーズの高いテーマに対し、オープンデータと民間データを組み合わせた分析は、IDRならではの研究アプローチと言えます。 スタートアップセッションにおける企業賞の授与 今回のIDRユーザフォーラム2025では、スタートアップセッションも開催されました。 LIFULLは企業賞として、以下の研究発表を表彰させていただきました。 住宅ローン税制は住宅購入者の利益になっているか 河瀬 豊 氏(神戸学院大学) 発表資料 本研究は、住宅ローン税制が実際に住宅購入者の利益につながっているのかを、データに基づいて検証したものです。 不動産・住宅政策とデータ分析を結びつけた点、そして社会的インパクトの大きさを評価し、LIFULLとして企業賞を授与させていただきました。 LIFULLからの発表:不動産データのビジネス応用事例 データ提供者セッションでは、LIFULLからも発表の機会をいただきました。 speakerdeck.com このセッションでは、実際の不動産データがどのようにビジネスの現場で活用されているかを、具体的な事例と技術アプローチの観点から紹介しました。発表はデータ提供者セッションにて行われ、参加者のみなさまにも幅広い関心を寄せていただきました。 発表では、以下のようなトピックに触れました。 「おとり物件」の防止に向けた取り組み:不動産情報の信頼性向上を目的とした取り組みと技術的背景の紹介 不動産売却査定におけるLLM活用:大規模言語モデルを用いた査定支援サービスの事例 LIFULL HOME'Sデータセットの拡充とその活用可能性:2025年5月に追加されたデータの内容と、これを活かした応用事例の可能性 この発表を通じて、データ利活用が不動産ビジネスにもたらす価値を、研究者・実務者双方の視点から捉えていただくことを目指しました。 LIFULLでは、今後もデータ駆動型サービスの可能性と実装事例を共有し、研究コミュニティとの対話を深めていきたいと考えています。 おわりに IDRユーザフォーラム2025を通じて、LIFULL HOME'Sデータセットが多様な研究テーマに活用されていること、そして研究成果が社会課題の理解や解決に寄与しうることを改めて実感しました。 LIFULLは今後も、 データ提供を通じた研究支援 学術と実社会をつなぐ取り組み 不動産・住まい領域におけるデータ利活用の発展 を継続していきます。 研究者の皆さま、データ利用者の皆さま、そしてIDR事務局の皆さま、改めてありがとうございました。 一緒に不動産データの可能性を広げるエンジニアを募集しています IDRユーザフォーラム2025を通じて、LIFULL HOME'Sデータセットが研究・ビジネスの両面で多様な価値を生み出していることを改めて実感しました。 これらの取り組みは、エンジニアリング・データサイエンス・AI技術の積み重ねによって支えられています。 LIFULLでは現在、不動産データやAI技術を活用し、社会課題の解決に取り組むエンジニアを積極的に募集しています。 データ基盤、機械学習、LLM活用、プロダクト開発などに関心のある方にとって、実データを使いながら挑戦できる環境があります。 エンジニア向けの募集職種一覧は、以下の採用ページをご覧ください。 hrmos.co 研究コミュニティと連携しながら、実社会にインパクトを与えるプロダクトをつくりたい方、不動産・住まい領域のデータ利活用に興味のあるエンジニアの皆さまのご応募をお待ちしています。
アバター
はじめに こんにちは、基盤グループでインフラの運用管理を担当している布川です。 今年の10月28日にAWS目黒オフィスにて、堅牢で回復力に優れたシステムの構築について学ぶAWS Architectural Resilience Dayが開催されました。 本イベントには弊社から基盤グループのメンバー3名が参加しました。本記事では、その内容や得られた学びをレポートとしてご紹介します。 はじめに 高いレジリエンスの実現に向けた6つのステップ 1. システムの中でビジネスクリティカルな部分を明確にする 2. 個々のコンポーネントに具体的な目標を設定する 3. 実際に設計に落とし込む 4. 現在の設計で目標を達成できるか?テストする 5. 適切な監視体系を構築する 6. 定期的に振り返りを実施する 講義を終えて 弊社のシステムに置き換えた話 実務で意識したいこと おわりに 高いレジリエンスの実現に向けた6つのステップ 講義では、システムを停止させないこと、また停止した場合でも影響を最小限に抑えることを目的として、システムのレジリエンスを高めるための6つのステップが示されました。 また、ハンズオンを通じて、AWS上に構築したシステムでこれらのステップを実践するための具体的な方法について学びました。 1. システムの中でビジネスクリティカルな部分を明確にする サービスを構成するシステム全体を見たとき、すべてのコンポーネントが同じ重要度を持つわけではありません。 まずは、ここだけは止めてはならない、止まったとしても最優先で復旧すべき、といったビジネスクリティカルな部分を明確にすることが重要だと学びました。 たとえば、弊社が運用する不動産ポータルでは、最低限のサービスレベルとして、地図サービスなど外部依存部分を除き、物件検索・問い合わせ、物件更新等の基本機能が一通り動く状態を定義しています。一方で、SEOや読み物コンテンツなど、ユーザ獲得を目的とした機能はこの範囲から外して考えています。 2. 個々のコンポーネントに具体的な目標を設定する 次に、ビジネスクリティカルと定義したコンポーネントについて、レジリエンスに関する具体的な目標値を設定します。講義では、次のような観点から整理していました。 項目 High Availability(高可用性) Disaster Recovery(災害復旧) 対象とする障害 小規模で頻度の高いイベント まれだが影響の大きい障害 具体例 ネットワークの問題、負荷スパイクなど 自然災害、技術的障害、人的ミスなど 対処方法 自動緩和、自己回復 事前に備えた復旧対応 評価指標 通期の平均値 MTBF ÷ (MTBF + MTTR) など 単一イベントでの測定 RPO, RTO など RTO(目標復旧時間)やRPO(目標復旧時点)は、ビジネス上どこまでの損失を許容できるかを元に決定します。たとえば、あるECサイトで1時間あたりの売上が1,000万円、サイトダウン時に許容できる機会損失額を1,000 万円(年 1 回まで)とした場合、RTOは1時間という考え方になります。 ハンズオンではAWS Resilience Hubを用いて、CloudFormationで管理されているリソース群にRPO, RTOを設定し、現在の構成がその目標を満たしているかを評価しました。また、S3のバージョニング有効化やDynamoDBのバックアップ設定などを追加し、要件を満たすまでの流れを体験しました。 3. 実際に設計に落とし込む 復旧目標が定まると、自己回復しない障害が発生した際に、目標時間内に復旧するための設計を考えられるようになります。 設計における基本方針は静的安定性の確保です。静的安定性とは、「自己回復しない障害が発生した時、システムをこちらから操作・変更しなくても復旧してくれる性質」のことを指します。 障害は大まかに、発生頻度の高い順に以下のように分類できます。 コードのデプロイや設定ミス(デプロイ失敗、証明書失効など) コアインフラストラクチャの障害(データセンター障害、ホスト障害など) データや状態の問題(データ破壊) めったに起こらないシナリオ(自然災害、全インターネット障害、電力等のサプライヤー障害など) 今回の講義では、特に4のようなレアケースでも重要業務を継続できるよう回復力を確保する設計に焦点が当てられていました。そのための方針として、APIや管理画面といったコントロールプレーンの操作を前提とせず、データプレーンのみで自律的に切り替わる構成とすることが重要である、という説明がとても参考になりました。(参考AWSドキュメント: コントロールプレーンとデータプレーン ) この前提のもと、マルチAZやマルチリージョンといった構成を検討します。ただし、コストをかけるほど復旧は早くなる一方で、金銭・運用負荷・複雑性といったトレードオフも発生します。抑えたい損失と支払うコストを天秤にかけ、現在の事業フェーズに合った手段を選択することが重要です。 ディザスタリカバリ戦略 出典: クラウド内での災害対策オプション (AWSドキュメント) 講義では深くは触れられていなかったものの、1〜3のような比較的発生頻度の高い障害に対しても、静的安定性を意識した設計を行うことが重要だと感じました。 デプロイや設定ミスに対してはCI/CDの整備やCanaryリリース、設定のIaC管理等を行うことでリスクを低減でき、インフラ障害に対してはマルチAZ等の冗長な構成を採用すること、データ障害に対しては定期的なバックアップやガードレールの導入を行うといったことが対策として考えられます。このような取り組みによって静的安定性を確保しながら、障害による影響や発生自体を抑える環境を作ることが求められると感じました。 ハンズオンではマルチリージョン構成のもとで、CloudFrontのオリジン(S3)のフェイルオーバーや、Route53によるALBの振り分け先のフェイルオーバーを実際に試しました。 4. 現在の設計で目標を達成できるか?テストする 現在の設計が確実に復旧目標を達成できることを確認するために、本番相当の環境、もしくは実際の本番環境のうちの一部のトラフィック群に対して、レジリエンステストを実施することを学びました。 ここでは、回復力を測る対象となる障害を意図的にシステムへ注入し、想定通りの挙動によって定常状態を維持、もしくは目標時間内に復旧できるかを確認します。 AWSでは、Resilience Hubの機能の一つであるFault Injection Service (FIS)を利用することで、電源の中断やインスタンス障害など、通常は再現が難しい障害シナリオを安全にテストできます。 ハンズオンでは、この後に学ぶ監視体系の構築と合わせて、FISを用いたAZの電源中断シナリオやリージョン障害発生時のシナリオを注入し、これまでに学んだレジリエンス対策を施したサービスが、目標時間内に復旧できるかどうかを、Cloudwatch SyntheticsのCanaryを用いて確認しました。 5. 適切な監視体系を構築する 目標とするレジリエンスを達成するシステムを設計・テストし、運用に乗せた後は、ユーザ体験の把握や障害の検知・影響判断、原因調査や復旧を支援するための監視が必要になります。 ただし、過剰な計測はデータ疲れを招くため、ビジネス目標を踏まえ、アプリケーションにとって重要な指標に絞って監視することが重要であることを学びました。 講義で紹介されていた監視体系の一例を整理すると、以下のような情報をバランスよく収集することが有効であると感じました。 リクエストの文脈:いつ・誰が・どのAPI/機能で・どこのAZ/リージョンで・何をしようとしていたのか 成否と結果:レスポンスコードやエラーの種類・原因 レイテンシと内訳:全体の処理時間や、接続・キャッシュやDB上の処理・アプリ上の内部処理にかかった時間 リソースの使用状況:キャッシュヒット等のリクエスト単位で計測できるもののほか、CPU使用率等の継続的に測定できるメトリクス 6. 定期的に振り返りを実施する 最後に、運用中に実際の障害が発生した後、どのように振り返りを行うべきかについて学びました。講義で紹介された事例では、以下のような観点で情報が整理されていました。 障害の概要:障害の影響、経緯、対策等のハイレベルな情報 メトリクス:障害の影響や問題の特定に利用したメトリクスのグラフ 影響範囲:障害の影響を受けたユーザ数、時間や程度、ビジネスへの影響 タイムライン:障害中に発生したすべてのイベントや、実施したプロセスを時系列で整理 ラーニング:対応や分析を通じて得られた学び アクションアイテム:改善策を優先度・責任者とともに明確化 特に重要な点として、個人を非難せず、プロセスや仕組みにフォーカスすることが強調されていました。 弊社でも障害対応のナレッジを蓄積するスペースを運用しているため、この考え方は共感しやすい内容でした。 講義を終えて 本講義を通して、堅牢で回復力の高いシステムをどのように構築するかを、段階的・体系的に学ぶことができました。なお、細かな説明については一部省略している点をご留意ください。 これまでふわっとした理解にとどまっていたレジリエンスについての考え方や具体的な手段について、このタイミングで整理して学べたことはとても有意義だったと感じています。 弊社のシステムに置き換えた話 弊社には KEEL(キール) と呼ばれるKubernetesベースの独自のアプリケーション実行基盤があり、多くのアプリケーションがその上で稼働しています。 マルチAZ構成を前提としているため、自然災害に対する静的安定性が確保されており、この点は大きな強みだと感じました。 また、運用中のサービスが必要なレジリエンス要件を満たしているかを定期的に確認することも重要だと感じました。 弊社では、サイバー攻撃を含むリスクに備えたマルチアカウント構成の事業継続計画(BCP)を策定しており、災害時などの緊急時において、損害を最小限に抑えつつ主力事業の継続や早期復旧を可能とするため、手順・担当者・作業範囲などをあらかじめ定めています。こちらについて、現状の対応は主にバックアップとリストアが中心であり、パイロットライト構成などを検討する余地もあると感じました。 このように、ハードウェア・論理的構成の両面で適切なレベル・手段を選びながら、想定されるさまざまな障害への対応策を講じておくことで、より堅牢なシステムを実現できるのだと理解しました。 実務で意識したいこと 基盤Gでは、運用に必要なアプリケーションも含めて開発・運用を行っています。 ステップ3でも触れられていましたが、システムが持つ静的安定性を最大限に活かすためのアプリケーション設計には、意識して取り組む必要があると感じました。 また、基盤グループでのこれまでの経験から、障害の原因の多くは人為的ミス、たとえばデプロイの失敗や設定不備であることが多いということは明らかです。 そのため、十分に統合テストを実施すること、リリースにあたっては影響範囲を明確にしつつ関係者と合意を取り、動作確認を行う体制を整備すること、といった基本的なことを積み重ねた上で、今回のようなレジリエンス設計や障害対応の議論ができる状態を作っていくことが重要だと感じました。 おわりに 本記事では、AWS Architectural Resilience Dayで学んだ、堅牢で回復力に優れたシステム構築に関する考え方や、その実践的な内容についてご紹介しました。 この記事が、レジリエンス向上に向けた動き方を検討する際の一助となれば幸いです。最後までお読みいただき、ありがとうございました。 最後に、LIFULLではともに成長していける仲間を募集しています。ご興味をお持ちいただけましたら、ぜひ以下のページもご覧ください。 hrmos.co hrmos.co
アバター
1. 始めに こんにちは。LIFULLでエンジニアをしている稲垣です。 2025年10月の3日間、AWSが提供する「AI駆動開発ライフサイクル(AI-DLC)Unicorn Gym」の研修に参加しました。この研修では、6つのチームに分かれて、AI-DLCを活用しながら実際のプロジェクト課題に取り組みました。 私たちのチームが取り組んだのは、「サポートが終了したサービスの新基盤への移行計画」です。具体的には、Amazon Linux 2のサポート終了(EOL: End of Life)への対応として、既存システムを新しい基盤へ移行する計画の策定に取り組みました。 このようなOSマイグレーションでは、以下のような課題に直面します: 取り組むべき手順や優先順位が不明確 移行オプションの調査と比較に数日かかる リスクの洗い出しが担当者の経験に依存し、見落としが発生しやすい この記事では、これらの課題に対してAI-DLCがどのように解決策を提供してくれたのか、実際の開発プロセスと得られた学びを共有します。特に、AIがルーティンタスクを処理する間に、チームは問題解決や意思決定に集中できる「ダイナミックなチームコラボレーション」がどのように実現されたかをお伝えします。 1. 始めに 2. 研修の内容とカリキュラム AI-DLCとは AI-DLCの開発フェーズ 私たちのチームの課題 私たちのチームの取り組みの流れ 私たちのチームの取り組みの流れ図 3. AI-DLCを活用した開発プロセス AI-DLCの基本サイクル 当初の構想とAI-DLCによる方針転換 Inception Phaseでの実践 Construction Phaseでの実践 4. 直面した課題とAI-DLCでの解決 5. 研修を通じて得られた気付きや学び AI-DLCを使った開発スタイルについての気付きと今後の活用 チーム開発での学び OSマイグレーションという課題から得た学び 6. まとめ 2. 研修の内容とカリキュラム AI-DLCとは AI-DLC(AI-Driven Development Lifecycle)は、AIが開発プロセスを主導する開発手法です。単なるコード生成ツールにとどまらず、要件定義から設計、実装、テスト、運用まで、開発の各フェーズでAIがタスク分解や実装を主導し、人間がレビュー・承認を行います。今回の研修では、Amazon Q Developerを使用してAI-DLCを実践しました。 AI-DLCの開発フェーズ AI-DLCは大きく3つのフェーズで構成されています: Inception Phase(構想フェーズ) - 現状分析、技術選定、計画策定を行うフェーズ Construction Phase(構築フェーズ) - 実装、テスト、ドキュメント作成を行うフェーズ Operation Phase(運用フェーズ) - 本番環境へのデプロイとインシデント管理を行うフェーズ 私たちのチームの課題 研修では6つのチームがそれぞれ異なる課題に取り組みました。私たちのチームは「サポートが終了したサービスの新基盤への移行」という課題に挑戦しました。 この課題は、参加チームの中でも比較的珍しいものでした。AWSの担当者によると、「AI駆動開発ライフサイクル(AI-DLC)Unicorn Gym」では、これまで主にアプリケーション開発の課題が扱われてきましたが、OSマイグレーションという領域でAI-DLCを活用するのは初の事例とのことでした。 具体的には、以下の点でほかのチームとは異なる挑戦となりました: 活用事例が少ない :インフラ移行という領域でのAI-DLC活用は前例が少ない 技術的な複雑さ :既存システムの完全な理解と、複数のAWSサービスにまたがる変更が必要 リスク管理の重要性 :本番環境への影響を最小限に抑える慎重な計画が求められる この特徴が、AI-DLCの新しい活用方法を模索する良い機会となりました。 私たちのチームの取り組みの流れ 今回の研修では、このAI-DLCのフェーズに沿って、OSマイグレーション計画を進めました。以下は、私たちのチームが実際に取り組んだ内容です。 Inception Phase(構想フェーズ) 既存システムの構成調査とAWSインフラの棚卸し 移行方法の比較検討と技術的制約の分析 ユニット(AI-DLCで用いる作業単位)への分割と依存関係の整理 Construction Phase(構築フェーズ) 以下の作業を並行して実施(研修時間内では途中まで): リスク管理台帳の作成と対応策の検討 技術調査レポートや運用手順書の作成 ベースイメージの作成 私たちのチームの取り組みの流れ図 図1:私たちのチームの取り組みの流れ。(Inception PhaseからConstruction Phaseの途中まで実施) 3. AI-DLCを活用した開発プロセス 本章では、AI-DLCを実際どのように活用して開発を進めたかを紹介します。 AI-DLCの基本サイクル AI-DLCでの開発は、以下のサイクルを繰り返します: AIにプランを作らせる 人がプランをレビュー AIがプランを修正 AIがプランを実行 人が結果を確認 図2:AI-DLCの基本サイクル。このサイクルを繰り返して成果物を完成させる 当初の構想とAI-DLCによる方針転換 プロジェクト開始時、私たちはAmazon Linux 2のEOL対応として、Amazon Linux 2023へのOSリプレイスを検討していました。既存のAmazon EC2(仮想サーバー)環境を維持したまま、OSのみをアップグレードする方針です。この方針は、既存システムへの影響を最小限に抑えられるという理由から選択しました。 しかし、AI-DLCを活用して検討を進める中で、方針が大きく変わりました。AIが生成した技術調査レポートで複数の移行オプションを比較した結果、 Amazon ECS(コンテナ実行基盤)へのコンテナ化 が望ましいという提案がありました。 理由は以下の通りです: 環境の再現性(不変性)の向上により、デプロイやロールバックが容易になる 将来的なミドルウェアアップグレードの基盤となる 長期的な保守性と運用効率の向上が期待できる コンテナ化という選択肢自体は、チーム内で漠然と認識していました。しかし、AI-DLCが生成したレポートを見るまでは、その具体的なメリットや実現可能性が明確ではありませんでした。AIが各オプションのメリット・デメリット、工数、コストを整理してくれたことで、チームでの議論が活性化しました。「環境の再現性(不変性)の向上」という観点や、「将来的なアップグレードの基盤」という長期的な視点は、AIの提案によって初めて明確になった部分です。 チームでの議論を経て、最終的にコンテナ化の方針で Construction Phaseを進めることに決めました。これは、AI-DLCが単なる作業支援ツールではなく、技術的な提案を行い、プロジェクトの方向性の決定に良い影響を与えてくれました。 図3:AI-DLCによる技術選定の変化。OSリプレイスからコンテナ化へ方針転換 Inception Phaseでの実践 Inception Phaseでは、現状分析と技術選定を行いました。 AIと人間の役割分担 AI-DLCでは、AIがルーティンタスクを処理することで、人間は問題解決や意思決定に集中できます。 AIが担当: 既存システムの構成情報の収集と整理 複数の移行オプション(OSリプレイス vs コンテナ化)の比較レポート生成 各オプションのメリット・デメリットの洗い出し 人間が担当: 移行方針の最終決定(ECSコンテナ化を選択) ビジネス要件との整合性確認 技術的な実現可能性の判断 Construction Phaseでの実践 Construction Phaseでは、計画策定とリスク管理を行いました。 AIと人間の役割分担 AIが担当: ユニットへの分割案の生成 依存関係図の作成 リスク項目の網羅的な洗い出し リスク管理台帳の初稿作成 人間が担当: タスクの優先順位付け リスクの重要度評価と対応策の決定 実装スケジュールの調整 たとえば、依存関係図の生成では: 人間がシステム情報を提供 AIがドキュメントを分析し、依存関係図を自動生成 人間が図をレビューし、不足部分を指摘 AIがフィードバックを反映して修正 チームが図を見ながら移行順序を議論 Mob Construction(いわゆるモブレビュー/モブ作業。チーム全体でのフィードバックと問題解決)の実践: Mob Constructionは、AI-DLCにおけるチーム開発の手法で、AIが生成した成果物に対してチーム全員でレビューとフィードバックを行い、より良い解決策を議論する取り組みです。 リスク管理時には、AIが洗い出したリスクをチームでレビューしました。「このリスクは優先度が高い」「この対応策では不十分」などフィードバックを行い、チームでリスク対応策をブレインストーミングし、実装の優先順位を議論して決定しました。 AIがリスクの洗い出しや台帳作成を処理している間に、チームメンバーは技術的な実現可能性の議論、過去の経験からの知見共有、より効果的な対応策の検討に集中できました。これにより、作業効率が向上しただけでなく、チーム全体の技術力向上にもつながりました。 4. 直面した課題とAI-DLCでの解決 課題1:技術選定の複雑さ Amazon Linux 2023への直接移行とECSコンテナ化、どちらを選択すべきか判断が難しい状況でした。 AI-DLCの活用: 複数の移行オプションを比較した技術調査レポートを生成 各オプションのメリット・デメリット、工数、コストを整理 チームでの議論の材料として活用 結果: 長期的な視点でECSコンテナ化を選択する判断ができました。 課題2:リスク管理の網羅性 OSマイグレーションには多くのリスクが潜んでおり、見落としがないか不安でした。 AI-DLCの活用: 技術的なリスクを網羅的に洗い出し リスク管理台帳の初稿を作成 リスク評価マトリックスを生成 結果: 30個以上のリスクを識別し、優先順位をつけて対応策を検討できました。 課題3:ドキュメント作成の負荷 移行計画書、技術調査レポート、運用手順書など、多くのドキュメントが必要でした。 AI-DLCの活用: ドキュメントの初稿を自動生成 フォーマットの統一 情報の整理と構造化 結果: ドキュメント作成の時間を大幅に削減し、内容のレビューと改善に時間を使えました。 5. 研修を通じて得られた気付きや学び AI-DLCを使った開発スタイルについての気付きと今後の活用 AIは技術的な提案も行う 当初はOSリプレイスを検討していましたが、AI-DLCが生成した技術調査レポートでECSコンテナ化を提案され、最終的にその方針を採用しました。AIは単に指示されたタスクをこなすだけでなく、より良い選択肢を提示することもあると実感しました。 今後の業務では、新しいプロジェクトの技術選定時に、AIに複数のオプションを比較させることで、より客観的な判断材料を得られると考えています。 プロンプトの重要性 AIに何を依頼するか、どのようにコンテキストを与えるかで、成果物の質が大きく変わります。効果的なプロンプトを書くスキルが、今後ますます重要になると感じました。 今回の研修で効果的だったプロンプトの要素: 役割の明確化 : 「あなたは熟練したクラウドエンジニアです」のように、AIに期待する専門性を明示 具体的なタスクの指定 : 「aidlc-docs/inception/migration_plan.md ファイルにステップを記載してください」のように、成果物の形式と保存場所を明確に指定 作業プロセスの指示 : 「計画を作成したら、私のレビューと承認を求めてください」のように、レビューのタイミングを明示 制約条件の設定 : 「独自の判断や決定は行わないでください」のように、AIが勝手に判断しない範囲を明確化 日常業務では、ドキュメント作成や技術調査の際に、これらの要素を含めたプロンプトを作成することで、AIの支援を最大限活用できます。 レビューの重要性 AIが生成した内容をそのまま使うのではなく、必ずレビューして修正する必要があります。AIの提案を批判的に評価する能力が求められます。 コードレビューと同様に、AIが生成した成果物のレビューを習慣化することで、品質を担保しながら効率化を実現できます。 チーム開発での学び 役割分担の明確化 AIが得意なタスクと人間が得意なタスクを明確に分けることで、効率的に作業を進められました。 AIが得意なタスク: 情報の収集と整理(既存システムの構成情報、移行オプションの比較) ドキュメントの初稿作成(技術調査レポート、リスク管理台帳) 網羅的な洗い出し(リスク項目、依存関係) 定型的な作業(タスク分割、フォーマット統一) 人間がやるべきタスク: 最終的な意思決定(移行方針の選択、優先順位の決定) ビジネス要件との整合性確認 技術的な実現可能性の判断 AIが生成した成果物のレビューと修正 チーム内での議論とコンセンサス形成 この役割分担により、AIがルーティンタスクを処理する間に、人間は問題解決や意思決定に集中できました。 OSマイグレーションという課題から得た学び 活用事例が少ない領域でのAI活用 一般的な開発タスクではない領域でも、AI-DLCは有効に活用できることがわかりました。特に、複数の移行オプションを比較する技術調査レポートの生成や、30個以上のリスクを網羅的に洗い出す作業では、AIの支援が大きな価値を発揮しました。 段階的なアプローチの重要性 大きな移行プロジェクトを複数のユニットに分割し、依存関係を整理することで、リスクを管理しながら進められることを学びました。 6. まとめ この研修を通じて、AIがルーティンタスクを処理する間に、チームは問題解決や意思決定に集中できる「ダイナミックなチームコラボレーション」を実践的に学ぶことができました。 また、OSマイグレーションという活用事例の少ない領域でも、AI-DLCは有効に活用できることを実証できました。 LLMは、単なるコード生成ツールではありません。AI-DLCという開発手法を通じて、LLMを開発のライフサイクル全体で活用することで、チームの生産性と創造性を高めることができます。今後の業務でも、この経験を活かして、より効率的で質の高い開発を実現していきたいと思います。 最後に、LIFULLではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
アバター
3行まとめ 仕様書を渡すとリスク分析からテストケース生成までやってくれるよ ステップ単位で人が軌道修正して精度をあげて(維持して)いるよ 指摘内容や成果物からナレッジを抽出してPRを出すので賢くなる仕組みだよ 3行まとめ はじめに つくったもの ポイント① 仕様書の理解から始める ポイント② 人間が軌道修正する ポイント③ 使うほど賢くなるように 「ドメイン知識」と「テスト技術」に分けた理由 実際どうなの? テストケースのフォーマット 課題:プロンプトやナレッジの管理・改善 おわりに はじめに こんにちは、クオリティアーキテクトグループでQAエンジニアをしている星野です。 この記事は LIFULL Advent Calendar 2025 の記事になります。 QA活動でLLM、活用してますか? 今回はテスト分析からテスト実装(テストケースの作成)までを効率化するためにつくったAIエージェントの話をします。 ただ単に「テストケースを作って」「テスト観点を出して」とお願いするだけではなく、 人間が軌道修正しながら精度を上げていく仕組みと、 その修正内容から学習して次に活かす仕組みも入れてみたので、その話をします。 残念ながらプロンプト群は公開できませんが、アプローチや設計思想は参考になるかもしれません。 つくったもの 仕様書を渡すと、以下のステップでテストプロセスを進めます。 仕様書の理解 - 曖昧な表現の確認や不足・矛盾を指摘、プロダクトリスクを洗い出す テスト観点の抽出 - ドメイン知識やテスト知識を活用してテスト観点を生成 ユーザーレビュー - 人間が確認して修正指示(最大5回まで) テストケース生成 - JSON形式でテストケースを出力 知識の抽出・保存 - ここまでの指摘・修正内容からナレッジを抽出してPRを出して蓄積 ポイントは3つあります。 ポイント① 仕様書の理解から始める いきなりテスト観点を抽出させたりテストケースをつくらせません。まず仕様書を理解させるところから始めます。 まず大前提として、完璧な仕様書は存在しません。少なからず間違いや曖昧な表現、後々変更される内容を含んでいます。 また、仕様書には書かれていない暗黙の前提、ドメイン特有の重要な項目や不安、課題だとかリスクがありますよね。 それらをどう認識されているかわからないままスタートをすると、そのズレは徐々に影響が大きくなって成果物がトンチンカンな内容になりがちです。 なので、それらを先に確認し、あわよくば後続のプロセスに活かします。 たとえば、 システム的にケアされていてテストの必要がないもの 仕様以前の要求、サービスそのものの目的や理念 非機能要件として求めるレベル感 仕様書に書かれていない制約や前提 こういったものを最初に確認しておくと、後のテスト観点抽出がブレにくくなります。 なので、このステップでは以下のようなことをやっています。 機能概要・対象範囲の要約 不足・矛盾・曖昧な点の確認 プロダクトリスクの特定 ユーザーにはLLMの解釈が正しいか確認を促して指摘を(必要なら仕様書を修正して)もらってから次に進みます。 ポイント② 人間が軌道修正する LLMも人間同様に誤りを生みますし、コンテクストが大きいと過去の指示や内容と矛盾したりもします。 なので、ステップごとにレビューポイントを設けています。 さきほどの仕様理解の最後に入れていた確認と同様のものを他ステップでも行います。 ユーザーからの承認を得なければ次のステップに進まないようにLLMに指示をしています。 また、修正は最大5回までにしています。 修正回数が多い時には中断を促すメッセージを出すんですが、これは仕様書側に矛盾や不足が多かったり、必要な情報があまりにも足らなくて精度が出ないおそれがあるからです。 精度が悪いと最終成果物も誤りだらけでレビューが大変に(そもそも使えないものに)なりますし、それ以前にインプットの品質に懸念があるということは、コードベースなど他の成果物も少し不安がありますよね。 そのため、ドキュメント自体の見直しを促すようにしています。 ポイント③ 使うほど賢くなるように 仕様書に書かれない暗黙的な情報が多いって話を前述しました。狙いはそこです。 毎回初手で同じような補足を入れるのは面倒ですし、人によって差がでます。 また、テストにとってドメイン知識はかなり重要で、しかしそれはアウトプットされない限りは各人の記憶に隠れてしまいます。 だからこのシステムでは、レビューを挟んだとき、そこで得た指摘をベースにナレッジ化、PRを作成し本体へフィードバックしてくれる仕組みにしました。 具体的には、 人間が指摘・修正した内容を記録 「なぜ修正が必要だったか」「どう修正したか」を抽出 ナレッジとして蓄積(「ドメイン知識」と「テスト技術」に分類) 次回のテスト分析で活用 という流れです。 ナレッジを含むこのエージェントのプロンプト群はリポジトリで管理されているので、CLIで動くLLMはPRも出してくれます。 脳内のドメイン知識を書き出す負担をユーザーから取り除き、かつ他のユーザーもそのナレッジを利用できるようになります。 こういう情報はわざわざ書き出すのは大変ですし変更を追うのも難しかったものですが、 これなら使う人が多ければ多いほど、使えば使うほど蓄積されてメリットがスケールしていくんです。 仮に指摘がないなら、それは人間に納得のいく精度の出力を出すために今のナレッジで足りているということなので、余計なものを蓄えずに済むようになっています。 「ドメイン知識」と「テスト技術」に分けた理由 「ドメイン知識」と「テスト技術」にわけたのは、汎用的に使える考え・知識なのか、特定のシステムや体制によるものなのかを区分するためです。 利用するのは様々なチームで、その中にもいくつものテストベースがあります。すべてをひとつにまとめてしまうと「あっちではこれが必要だけどこっちでは重要視していない」みたいな衝突が起きてしまいます。 似ている目的や名前のページが存在していても、ドメイン知識として仕様書にあるリポジトリ名やサービス名からテストベースを判断でき、必要な情報だけを参照できるようにしています。 実際どうなの? 現時点ではいい感じに使えています。 私自身はもう基本的にこのエージェントを使用しており、成果物に少し手直しを加える程度で運用できています。 使いながら気になったらプロンプト自体をその場で直してしまえるので、改善のサイクルも早くて快適ですね。 テストケースのフォーマット 社内で使ってもらう・自分が使うにあたって、テストケースは標準化されたフォーマットから離れると定着しにくい・使いにくいため、JSONで出力するようにしました。 今のフォーマットは 以前の記事 に書いたようにスプレッドシートベースなので、GASでJSONからインポートできる機能をつけました。 ついでに最終成果物からナレッジを得て学習できるようにエクスポート機能も実装し、具体的なテストデータとかpath、手順などの仕様書に書かれない前提が輸入しやすくする狙いがあります。 また、JSONフォーマットと各カラムの目的と記載内容を言語化したドキュメントも配布することで、私がつくったプロンプトではなく独自に改善を行っている方にも利用しやすくしました。 囲い込んでユーザーを増やすよりも自由度を大事にしています。 課題:プロンプトやナレッジの管理・改善 今は自動的に飛んでくるPRをレビューして、問題(偏った判断基準や誤った省略など)がなさそうかを見ています。 ユーザーが少ないうちはこれでいけるんですけど、全ものづくりメンバーが使い出したらこれをどうするかは悩んでいます。 たぶんCLIにAIレビュアーを入れて重複削除や一貫性の担保をすることになると思います。 また、ナレッジが肥大化してきたときに正しく扱えるのか、制御できるのかが少し懸念です。 システムが昔よりもメモリ不足を気にしなくなったように、LLM側が進化してコンテクストウィンドウのオーバーフローを気にしなくてもよくなればいいんですけどね。 おわりに LLMを使ったテストプロセスの改善について、アプローチや設計思想を紹介しました。 「人間が軌道修正する仕組み」と「使うほど賢くなる仕組み」を入れることで、実用的なツールになったかなと思っています。 LLM活用のアプローチとして、何か参考になれば幸いです。 以上です。 お読みいただきありがとうございました。 最後に、LIFULLでは一緒に働いていただける仲間を募集しています。 カジュアル面談も実施しておりますので、もし興味があればそちらもご覧いただければと思います。 hrmos.co hrmos.co
アバター
はじめに iOSDC Japan 2025とは 印象に残ったセッション ユーザー数10万人規模のアプリで挑んだトップ画面のUI刷新 ABEMAモバイルアプリがKotlin Multiplatformと歩んだ5年 ─ 導入と運用、成功と課題 iOSエンジニアキャリア設計入門 〜”先進性”をキャリアの武器へ〜 カスタムUIを作る覚悟 イベントの雰囲気 まとめ はじめに こんにちは!LIFULL HOME’S iOSアプリエンジニアの遠藤・佐藤です。 今回は、2025年の9月19日(金) 〜 9月21日(日)の3日間で開催された、iOSに関連した技術をコアテーマにしたテックカンファレンス「iOSDC Japan 2025」に参加してきました。この記事では、3日間で行われたセッションの中から私たちの印象に残ったセッションやイベントの様子について振り返ります。 iOSDC Japan 2025とは iOSDCは、記念すべき第10回目の開催を迎えました!これまでは早稲田大学理工学部 西早稲田キャンパスにて開催されていましたが、第10回となる今回は会場がグレードアップし、有明セントラルタワーホール&カンファレンスにて開催されました。トークテーマはiOSに関する内容を始め、Vision Proやクロスプラットフォーム、AIといった多岐にわたる分野を網羅し、LT大会や多数の企画も充実していました。 iOSDC Japan 2025の公式サイトはこちら iosdc.jp トーク動画の視聴はこちら www.youtube.com 印象に残ったセッション ユーザー数10万人規模のアプリで挑んだトップ画面のUI刷新 speakerdeck.com この発表では、ユーザー数10万人超のアプリにおいて、WebViewベースであったトップ画面をネイティブ実装に刷新した事例が紹介されていました。従来の操作感を損なわずに段階的に移行を進めたプロジェクトで、多くのユーザーを抱えるアプリのUI刷新の事例として非常に参考になる内容でした。 特に印象的だったのは、ユーザー体験を最優先にした移行戦略です。段階的な移行を進める中で、各フェーズごとにユーザーからのフィードバックを収集し、課題を一つずつ解決していったそうです。具体的な指標として「ロールバック率(新画面から旧画面に戻したユーザーの割合)」を計測し、改善を重ねていった点が紹介されていました。 こういったフィードバック収集と改善の結果、最終的にロールバック率5%未満という高い受け入れ率も記録しつつ移行を完遂されており、ユーザーとの対話を徹底したUI刷新の成功事例として、とても勉強になりました。(遠藤) ABEMAモバイルアプリがKotlin Multiplatformと歩んだ5年 ─ 導入と運用、成功と課題 speakerdeck.com こちらの発表では、ABEMAにおいて約5年間にわたってKMP(Kotlin Multiplatform)を運用してきた具体的な事例を紹介されており、実際に業務でKMPを扱う私にとって、とても興味深い発表でした。 2020年ごろから検証を開始し、段階的に導入を進めてきた経緯を詳しく説明して下さっていました。現在では、サービスの中心となる画面に関連するロジックの多くがKMPによって共通化されているとのことで、大規模サービスでの長期運用がどのように実現されているのか、その実態を知ることができました。 また、KMPの導入プロセスや実際に生じた課題、そしてチームでの実装効率がどう変化したかなど、実践的な内容が取り上げられていました。特に、処理をどこまで共通化すべきかという判断基準や、Swift6やSwiftUIの導入とKMPをどう両立させたかなど、現場で直面する具体的な課題への取り組みを紹介されていて、多くの学びを得ることができました。(遠藤) iOSエンジニアキャリア設計入門 〜”先進性”をキャリアの武器へ〜 speakerdeck.com このセッションでは、ネイティブアプリエンジニアを取り巻く最新の市場動向を知ることができました。スマートフォンアプリ開発全体では、クロスプラットフォームであるFlutter(Dart)の勢いが増しており、SwiftとFlutterの両方を使うエンジニアも増加しているという明確なトレンドを解説してくださいました。 AIの活用によって開発効率は急速に向上していますが、企業側からのニーズは引き続き高い水準を保っているそうです。 今後のキャリア戦略としては、iOSとAndroidの両OSのネイティブ知識に加え、共通化の判断ができる能力がより重要になると感じました。 ちなみに、弊社のLIFULL HOME’Sアプリでも、Kotlin Multiplatformを使ってバックエンドの共通化を行い、ネイティブの特性を活かしつつ開発効率の向上に取り組んでいます。詳細については、過去の記事( LIFULL における Kotlin Multiplatform(KMP) - LIFULL Creators Blog )でご紹介していますので、ぜひご覧ください!(佐藤) www.lifull.blog カスタムUIを作る覚悟 speakerdeck.com カスタムUIは、標準UIが持つアクセシビリティや多様な入力への対応を、すべて自力で担う必要があるという、そのコストと難しさを改めて痛感させられる内容でした。 iOS/iPadOSアプリ開発における基本的な考え方は、標準APIを最大限に活用することです。安易にナビゲーションやコントロールをカスタム化してしまうと、ユーザー体験を損なうリスクがあることを認識しておく必要があります。カスタムUIを選択するということは、単に実装する技術的な問題ではなく、UIデザイナーとエンジニアが静的なデザインを超えて密に連携すること、そして時には勇気ある撤退も視野に入れるという「覚悟」が不可欠だと学びました。 後日、このセッションの動画をエンジニアだけでなく、企画職やデザイナー職も交えて視聴し、それぞれの視点からの学びを共有しました。カスタムUIの選択が、プラットフォームへの深い理解、UX、そしてチーム全体の総合的な判断に関わる重要な問題であるという認識を、チーム全体で共有できたことは非常に貴重な機会となりました。(佐藤) イベントの雰囲気 ルーキーズLTでは会場全体が登壇者の好きな色にサイリウムの色を切り替えて、応援していました!一人一人に合わせた応援で登壇者を後押しする、温かい演出が印象的でした! サイリウムを振ってLT発表者を応援📣 会場の1フロアほぼ全体にスポンサー企業ブースが立ち並び、各社の技術紹介やノベルティ配布、クイズなどで大盛況でした🥳 ビンゴ形式のスタンプラリーで各社のブースを巡る企画もあり、楽しみながら企業の取り組みを知ることができる工夫が印象的でした! スポンサー企業ブース1 スポンサー企業ブース2 ちなみにday1、day2ともに、朝にドーナツが配られていました〜🍩 会場が変わっても、早朝から用意してくださった運営の皆さんの熱量と気配りに感謝です! 4Fの展示ルーム前にドーナツがデプロイされました! 是非ご賞味ください 🍩 #iosdc pic.twitter.com/ulEsr2t0Wo — iOSDC Japan (@iosdcjp) 2025年9月21日 まとめ 今回iOSDCに参加して、様々なセッションを通じて、各社の取り組みや技術選定の背景など、日頃の開発現場の姿を垣間見ることができました。 また、昨今はFlutterやKotlin Multiplatformをはじめとしたマルチプラットフォーム開発が活発であったり、比較的新しいアーキテクチャを取り入れる事例も見受けられるなど、モバイルアプリ開発が依然として目まぐるしく変化していることを改めて実感しました。 弊社のアプリチームでも実際にSwiftUI、Kotlin Multiplatformを取り入れた開発をメインで行っていることもあるので、今回得た視点、知見を日頃の開発業務にぜひ活かしていこうと思います。 最後に、LIFULLでは一緒に働いていただける仲間を募集しています。単にサービス開発をするだけでなく、自らの知見を深め、エンジニアやプロダクトマネージャーとしてのキャリアを築く上で大切な事を得る機会が多くある職場です。カジュアル面談も実施しておりますので、もし興味があればそちらもご覧いただければと思います。 hrmos.co hrmos.co hrmos.co
アバター
こんにちは。フロントエンドエンジニアの根本です。 LIFULL HOME'Sのプロダクト開発とスポーツ関連の新規事業開発に携わっています。 今回は、PdM兼開発担当として携わった「注文住宅サイトの画像検索機能」について、開発の裏側をご紹介します。 画像検索機能とは? 開発前の事前調査 AIを活用したプロダクト設計 なぜAI活用に踏み切ったのか AI導入における挑戦と工夫 AIとプロダクトの付き合い方 チームを支えるプロダクトビジョン 今後について 最後に 画像検索機能とは? この機能は、 プレスリリース でもご紹介した通り、ハウスメーカーや工務店が持つ施工事例画像と、LIFULL HOME'Sが独自に収集しているユーザー投稿画像を使い、理想の家のイメージに合う会社を直感的に探し出せる、新しい検索体験を提供するものです。 開発前の事前調査 機能開発に先立ち、今回も職種横断での事前リサーチを実施しました。リサーチ活動の詳細については、こちらの記事もぜひご覧ください。 職種横断チームでのUXエンジニアとしての働き方 - LIFULL Creators Blog RESEARCH Conference 2024に登壇しました - LIFULL Creators Blog AIを活用したプロダクト設計 なぜAI活用に踏み切ったのか 注文住宅には、「和モダンスタイル」や「北欧スタイル」など、多様な家づくりのスタイルが存在します。従来、検討者の方々はそうしたスタイルを意識しながら検索をしていました。 しかし、LIFULL HOME'Sが抱える膨大な施工事例画像をすべて手動でスタイル分類するのは非常に困難でした。また、クライアント様にも大きなご負担がかかることが予測されました。 そこで、この課題を解決する手段として、AIを用いたスタイル自動判定の活用を検討し始めました。 AI導入における挑戦と工夫 AI判定の品質を100%保証することは難しく、開発過程では多くの挑戦がありました。 特にコストと品質のバランスを考慮しながら、プロンプト設計の検証を繰り返していました。 たとえば教師データの粒度を変えて分類精度を比較したり、「木の家」「北欧」といったスタイルを誤判定しないよう詳細なスタイル定義をプロンプトに盛り込むパターンを試すなど、さまざまなアプローチを検討しました。その過程自体が今後の改善につながる重要な知見になっています。 こうした技術的な試行錯誤と並行して、私たちが最も懸念したのは誤った判定によるクライアント様へのご不便でした。 そこで、もしAIが誤ったスタイルを判定した場合でも、クライアント様ご自身でスタイルを簡単に修正できる機能を合わせて開発しました。 スタイル判定結果を確認後、すぐに更新できるような導線を設計することで、ご負担を最小限に抑えられないかと考えました。この判定品質は今後も継続して改善を進めてまいります。 AIとプロダクトの付き合い方 AIは、クリエイティブな発想や複雑な意思決定をサポートしたり、単純作業を代替したりするツールとして、すでに有益な役割を果たしていると思います。 今回、プロダクトの一部としてAIを取り入れて痛感したのは、現時点ではAIに100%の精度を求めるのは難しく、人間の最終的な判断や修正が必要不可欠だということです。 だからこそ、AIの判断を補完する機能や、万一のエラーに備えた代替手段を用意することで、ユーザーにより安全で信頼性の高いサービスを提供することが重要であると考えます。 チームを支えるプロダクトビジョン 「"好き"から始める家づくり」を実現できる世界を作る この画像検索機能の開発は、PdM、デザイナー、エンジニア、全員の挑戦から生まれたプロダクトであり、それを支えたのがプロダクトビジョンです。 私たちの開発は、単に機能を実装することだけを目的としていません。チーム全員で定めた「「"好き"から始める家づくり」を実現できる世界を作る」というプロダクトビジョンの実現を目指しています。 開発中には、AI品質に関する膨大な検証や、ユーザーの使いやすさなど、多くの課題に直面しました。しかし、全員が同じビジョンを共有することで、技術的な困難や仕様の変更にも柔軟に対応し、チーム一丸となってプロダクトの初期リリースを迎えることができました。 しかし、プロダクトビジョンの実現にはまだ遠く及びません。注文住宅検討者にとって素敵な出会いを創出し、ハウスメーカー・工務店様には会社のファンになったユーザー様をマッチングできるように今後も邁進いたします。 今後について 本機能はリリースしたばかりです。今後は、利用意向調査やユーザビリティ調査を実施しながら、ユーザーにとってより使いやすいプロダクトへと磨き込みを続けます。 特に技術面では、AI判定精度の向上と、機能の拡張を必須命題として取り組んでまいります。 最後に LIFULLではともに成長できるような仲間を募っています。 よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
アバター
はじめに こんにちは。プロダクトエンジニアリング部でAndroidのネイティブアプリエンジニアをしている久野です。 2025年9月10日から12日にかけて開催された「DroidKaigi 2025」に現地参加してきました。 この記事では、カンファレンスの現地の様子や、特に印象に残ったセッションについてご紹介します。 はじめに DroidKaigi 2025 について 特に印象に残ったセッション 1. 基礎から学ぶ大画面対応 2. 共有と分離 Compose Multiplatform 本番導入の設計指針 3. テストコードはもう書かない。JetBrains AI Assistantに委ねる非同期処理のテスト自動設計・生成 4. スマホ新法、何が変わる?公正取引委員会担当者が語る特定ソフトウェア競争促進法 おわりに DroidKaigi 2025 について DroidKaigiは、Android技術をテーマにしたエンジニア向けのカンファレンスです。 今年もオフラインでの開催となり、多くのAndroid開発者が集まりました。 また、DroidKaigiの大きな魅力の一つは、セッションの動画がYouTubeに公開される点です。しかも、驚くほど早いスピードでアップロードされます。これにより、会場に参加できなかったとしても、後から動画で学習できるのは本当に素晴らしいことだと思います。私も昨年まではオンラインで視聴しており、大変お世話になりました。 もちろん、現地参加には代えがたい価値があります。どのセッションに人が集まっているかを見ることで技術的なトレンドを肌で感じられたり、企業ブースで他社の方々と交流してモチベーションが上がったりと、多くの刺激を受けました。 この記事で紹介するセッションにも、後から見返せるようにYouTubeのリンクを併記しておきます。 特に印象に残ったセッション ここからは、私が聴講した中で特に印象に残ったセッションをいくつか紹介します。 1. 基礎から学ぶ大画面対応 資料 speakerdeck.com セッション概要 Androidアプリにおける大画面対応の基本的な考え方から実践的な実装方法まで解説されていました。実際に「Large screen differentiated」の認定を受けているアプリの開発知見なので、今後のアプリ開発で大画面対応をする際に、ぜひ参考にさせていただきたい内容でした。 特に学びになった点・感想 タブレット対応でヘッダー画像が画面を埋めてしまう問題に対し、画像のサイズを制限し左右にグラデーションを追加するという解決策が印象的でした。大画面に対応しつつ、ユーザー体験を損なわない工夫が非常に実践的だと感じました。 また、大画面では両手での利用が多くなることを想定し、 Navigation rail を導入する点や、 LazyVerticalGrid と WindowSizeClass を利用して画面サイズに応じてUIを切り替える点も、すぐにでも取り入れたい実践的なテクニックで普段の業務で活かしていきたいと思いました。 2. 共有と分離 Compose Multiplatform 本番導入の設計指針 資料 speakerdeck.com セッション概要 Compose Multiplatform (CMP) を本番環境へ導入した際の設計指針に関するセッションでした。会場では3〜4割の人がKMP(Kotlin Multiplatform)開発経験者で、CMP(Compose Multiplatform)を触ったことがある人は1割程度という状況でした。 特に学びになった点・感想 セッションでは、CMPの採用理由やチーム構成など、導入の裏側まで詳しく解説されていました。 特に印象的だったのは、現状Androidエンジニア1名で両プラットフォームの機能開発を行っているという点です。AndroidエンジニアだけでiOSアプリ開発までカバーできるのは、マルチプラットフォームならではの大きな強みだと改めて感じました。 また、91.5%のコードを共通化できた一方で、共通化が難しかった残りの8.5%(デバイス機能や入出力周り)についても詳しく解説されており、とても有益でした。 作りたい機能がある場合、まずはKMP対応ライブラリを探すのが重要という点は、すぐに実践できると感じました。KMPライブラリの検索サービス klibs.io や、JetBrains公式のサンプルプロジェクトは今後の開発で大いに参考になりそうだと感じました。 3. テストコードはもう書かない。JetBrains AI Assistantに委ねる非同期処理のテスト自動設計・生成 資料 speakerdeck.com セッション概要 JetBrains AI Assistantを活用して、非同期処理を含むAndroidのテストコード作成を自動化する手法についてのセッションでした。業務でテスト作成にAIを使い始めたため、より良いテストコードの書き方を学びたいと思い聴講しました。 特に学びになった点・感想 セッションでは、Androidテストにおける現実的な課題として、非同期処理の複雑さやメンテナンスコストの高さが挙げられていました。 特に、非同期処理のテストではスレッドの差し替え、非同期な値の監視、依存のモック化といった定型的な作業が発生しがちです。このような作業こそAIに任せるべき、という考え方にとても共感しました。 セッションで紹介された、5つの評価軸(正確性、網羅性、再現性、保守性、速度・コスト)で「どこまでAIに任せるか」を判断するというアプローチは、自分の業務に置き換えても非常に参考になりました。 単純なCRUD(作成・読み取り・更新・削除)処理のテストでは、正常系や定型パターンはAIに任せられるものの、異常系や境界値のテストは人間の修正およびレビューが不可欠であること。また、WorkManager のような複雑な処理でも、雛形作成はAIに任せられるが、拡張性や保守性を考慮した修正やレビューは人間が行うべき、という切り分けが明確で分かりやすかったです。 AIにテストの雛形作成を任せることで開発速度を上げつつ、品質担保や拡張性が求められる部分では人間がしっかり修正、レビューする、というAIとの協業スタイルが、今後のテスト開発の鍵になるということを学びました。 4. スマホ新法、何が変わる?公正取引委員会担当者が語る特定ソフトウェア競争促進法 資料 セッション概要 2025年12月18日に施行予定の「スマートフォンにおいて利用される特定ソフトウェアに係る競争の促進に関する法律」(通称スマホ新法)について、公正取引委員会の担当者の方から直接解説を聞けるという、技術カンファレンスとしては珍しいセッションでした。多くの開発者が注目しており、会場は満席でした。 特に学びになった点・感想 OSの標準機能(通信、音声入力など)の利用可能性向上や、アプリ外での商品提供(ウェブサイトでのイベント告知など)の拡大といったトピックは、Android開発者として非常に興味深く、勉強になりました。 これまでスマホ新法について自分の理解が浅い部分がありましたが、このセッションを通じて、法律が制定された背景や目的(「なぜ」「何を」するのか)を深く知ることができました。 普段なかなか詳しく学ぶ機会のないテーマだからこそ、担当者の方から直接お話を聞けたのは非常に有意義な体験でした。 おわりに 2日間にわたって参加したDroidKaigi 2025、非常に多くの学びと刺激を得ることができました。 大画面対応やCompose Multiplatformといった実践的な技術セッションから、AIによるテスト自動化、そしてスマホ新法という新しい領域の動向まで、多岐にわたるテーマに触れることができ、Android開発のモチベーションがとても高まりました。 特に、今年はセッション・企業ブースを問わず、 生成AI と Kotlin Multiplatform、 Compose Multiplatform に関する話題が非常に多かったのが印象的でした。業界全体の大きなトレンドを肌で感じることができました。 オンラインでの参加も手軽で素晴らしいですが、やはり現地では、こうした会場の雰囲気や他の参加者とのコミュニケーションを通じてとても勉強になることを改めて実感しました。 最後になりますが、LIFULL では一緒に働いていただける仲間を募集しています。今回希望してイベントに参加をしたように、エンジニアが成長できる機会が盛りだくさんの職場です。カジュアル面談もやっていますので、よろしければこちらもご覧ください。 hrmos.co
アバター
はじめに LIFULLにて基盤グループのマネジメントをしている磯野です。 2025年8月28日に開催された「 Amazon Q Developer Meetup #2 Amazon Q Developer を業務で活用した成果共有と最新情報 Update 」に参加し、LIFULLでの活用事例について発表させていただきました。 当日は株式会社マイナビ様の事例発表もあり、他社での活用状況を知ることで多くの共通点や共感できる部分がありました。また、懇親会では各社の担当者の方々と直接お話しでき、様々な活用方法や課題について情報交換することができ、今後更にいろいろ試せそうで非常に有意義な時間を過ごさせていただきました。 本記事ではイベントで発表した内容をもとに、Amazon Q Developerの導入から社内展開、そして多職種での活用事例について、LIFULLでの実践例をご紹介します。エンジニアだけでなく、サービス企画やデザイナーまで幅広い職種で活用が広がっている現状と、その具体的な事例をお伝えします。 はじめに 導入から拡大まで 導入のきっかけ 拡大戦略 1. 草の根で広げる 2. 波に乗る 現在の利用者数 事例紹介① スライド作成自動化 課題:スライド作成の効率化 解決策:HTMLスライドの生成 HTMLスライドのメリット 自動化ワークフロー 事例紹介② サービス企画・PMでの活用 1. プロトタイプ作成 社内への影響 2. 施策のアイデア出し 設定するコンテキスト 制約条件ドキュメントの育成プロセス 3. 効果測定レポート自動化 Before vs After 品質向上への取り組み その他の活用事例 社内MCPサーバー Qランキング ローカルMCP 通知機能 社内AIサービスとの連携 プロジェクト専用エージェント設定(検証段階) エージェント用設定ファイル 起動方法 実際の動作例 デザイナーでの活用(検証段階) プロトタイピング サービス改善 まとめ 導入から拡大まで 導入のきっかけ 昨年10月から今の部門のマネジメントを任されており既存の課題である対応スピードの改善を主要なミッションの1つとして持っています。 とくに、LIFULL HOME'Sの主要サービスのAWSアカウントやそこで動くインフラの管理をしている関係で、自動化やIaCの推進をしてくことが急務だったため、AIエージェントを活用していきたいと考えていました。 具体的には以下のようなことを意識して選定しています ほとんどコードを書かない基盤グループでCDKでの開発を推進する CLIでの作業が基盤グループの業務にマッチしていた AWSコンソールや認証への統合など、AWS環境との親和性が高い 利用していく中で、開発だけでなく調査や分析もLinuxコマンドを活用しながら処理でき、想像以上に便利だったため、「これはイケる!」という手応えを感じました。 拡大戦略 その手ごたえを受けて、もっと広く活用してもらえるのではないかと考えた結果、 Amazon Q Developerの社内展開を以下の2つのアプローチで進めました: 1. 草の根で広げる 近くのエンジニア部門のマネージャ、リードエンジニアに自分の使っているところを見せて使ってもらう 乗り換えを受け入れられるように予算・登録・利用方法を整備する 2. 波に乗る Qのアップデートを積極的に取り入れる MCP対応 / Claude Sonnet 4対応 企画の検証の1つとして使ってもらう この草の根 + 波に乗る戦略により、利用者が着実に拡大していきました。 特にマネジメント層が前向きに導入を進めてくれた部門では部門の総会などで活用事例を共有してくれるなど周りの後押しにも支えられています。 また、Q自体のアップデートのタイミングも本当に素晴らしく、ほしいときに欲しい機能がリリースされてくれたため強い後押しになりました。 導入検証開始 → Q CLIがmcpに対応 エンジニア全体に拡大開始 → Q CLIがClaude Sonnet 4に対応 サービス企画の導入検証開始 → 上記2つがIDE版に対応 直近ではエージェントの作成機能が追加されるなどまだまだ追加されるものは多く、今後にもとても期待しています。 話が少しそれてしまいますが、個人的に最近のアップデートで最高だったものはログインのawscliへの統合です。 これによりQへのログインはせずにAWSにログインするだけで利用できるため、朝のログイン作業が劇的に楽になりました。 # 環境変数を設定 export AMAZON_Q_SIGV4=1 export AWS_PROFILE=[プロファイル] # AWS SSOでログイン aws sso login # Q Chatを開始 q chat 現在の利用者数 現在ではエンジニアを中心にサービス企画やデザイナーなどに浸透し、その範囲においては9割以上のメンバーが登録済みの状態です。 ここから先は当日発表した事例についての説明です。 当日はスライド自動作成はLIFULL HOME'Sの流通領域のエンジニアチームにてマネジメントをしている渡邉から、サービス企画・PMでの活用事例はパーソナライズやレコメンド機能などのPMをしている井上から発表させていただきました。本記事は私が取りまとめて執筆しています。 事例紹介① スライド作成自動化 課題:スライド作成の効率化 管理職になってスライド作成機会が激増する中で、以下の課題を抱えていました: デザインに自信がない 細かい調整が苦手 時間がかかりすぎる 従来は4時間かけてPowerPointと睨めっこしていたのが、Amazon Q Developerを使うことで数分で完成するようになりました。 解決策:HTMLスライドの生成 最初はpptxをAIに作らせようと考えましたが、「伝える」上でフォーマットにこだわる必要はないと気づき、 Amazon Q DeveloperにHTMLでスライドを生成してもらう アプローチを採用しました。 HTMLスライドのメリット コードブロック対応 : スクローラブルなデザイン、コピー機能付き Git管理 : バージョニングが容易、チーム共有も簡単 高い自由度 : デザインや画面幅の制約が少ない、レスポンシブ対応 統一感 : GUIDELINE.mdで自動適用、再現性のあるデザイン 自動化ワークフロー GUIDELINE.mdで定義された3ステップで自動化を実現: アウトライン生成 : README内容を読み解いてOUTLINE.md作成 スライド生成 : アウトラインを元にHTMLスライド作成 ファイル出力 : 絶対パスで表示、ブラウザ確認可能 実際に今回のスライドも5分程度で生成することができています。 例えば、"自動化ワークフロー"の実際の生成されたページは以下のようになっています。 自分でパワーポイントでやってもできる自信はないです。 HTMLでのスライドの為ここに貼れないのが残念ではありますが、スライド作成についての詳細は以下を是非ご覧ください www.lifull.blog 事例紹介② サービス企画・PMでの活用 1. プロトタイプ作成 DeNAさんが企画書にプロトタイプを必須化というニュースにインスパイアされ、早速トライしたところ、 1時間程度でかなり良いプロトタイプが完成 しました。 社内への影響 「企画でもプロトタイプが作れる!」というインパクトから利用者が急増し、以下の効果が生まれました: プロトタイプを作成するプロジェクトが 増加中 従来の企画書・仕様書に代わる 新しいコミュニケーション手段 として定着 デザイナー・エンジニアとのコミュニケーションが 格段にスムーズ に 「動くもの」で認識合わせを行うことで、認識齟齬を大幅削減できています。 2. 施策のアイデア出し ChatGPTなどでもアイデア出しは可能ですが、コーディングエージェントのメリットは コンテキストの活用 です。 設定するコンテキスト プロダクトの概要・ターゲットユーザー・仕様など 技術的制約、予算制約、時間制約などの 制約条件(特に重要) 制約条件がないと実現性度外視のアイデアばかりが出てくるため、 制約条件の提供がアイデア精度向上の鍵 となります。 制約条件ドキュメントの育成プロセス 実践的な3ステップで制約条件ドキュメントを育成: まずAmazon Q Developerにアイデアをたくさん出してもらう 「◯◯という理由でできない」を逐一伝えていく 「今回伝えた制約条件をドキュメントにまとめておいて」 制約条件ドキュメントを育てることで、かなり精度の高い(ハズレの少ない)アイデア出しができています。 3. 効果測定レポート自動化 Before vs After 従来の作業プロセス(半日〜1日の手作業) - データの抽出 - 分析・考察の作成 - ネクストアクションの検討 - 他の人に読みやすく整理 Amazon Q Developerで効率化(1〜2時間で完結) - データをテキストで貼り付け - 自動で分析・考察を生成 - 次の施策案も自動提案 - MCP経由で直接投稿 結果として、 80%の作業時間削減 を実現しています。 品質向上への取り組み 初回から完璧な結果は得られないため、以下の問題に対処: 問題1 : 事実と推測が混在、推測を事実かのように断言 問題2 : 根拠の低い、説得力のない仮説を立ててくる 問題3 : ネクストアクション(施策)が10個以上出てきて絞り込めない 逐一修正指示を行い、その内容を ルールとして蓄積 し、次回以降の効率化につなげています。 その他にも企画業務全般で活用が広がっています。さらなる生産性向上に向けて、企画職全体で取り組みが進んでいます。 その他の活用事例 以上が主な活用事例ですが、他にも社内では活用していくにあたっていろいろと進めていますので簡単な事例として紹介させてもらいます。 おそらく各担当が今後ブログなどで記事を作成してくれると思います。 社内MCPサーバー 社内システムとの連携を実現するMCPサーバーを構築し、Amazon Q Developerから直接社内システムにアクセスできる環境を整備しています。 インストール型ではなくサーバーで稼働させることで職種の隔てなく簡単に導入できるようになっています。 具体的なシステムとしては Jira/Confluence, データベースのテーブル定義情報などです。 Qランキング 利用状況の可視化と競争要素の導入により、利用促進を図っています。 ランキング出力部分もAmazon Q Developerに指示して作成してもらっているので、こういった活用により自分のランキングもかなり上位になっています。 (TOP3には入れるんですが1位にはなれないのでまだまだ活用が足りないようです) ローカルMCP 通知機能 タスク完了の通知をMCP経由でOSの通知に送信 通知でも弱いのでOSの音声再生に送信 ※Qの処理時間が長くなると忘れてて20分後に通知がくる場合があって、MTG始まってたりするとざわつくので最近止めてます 社内AIサービスとの連携 使い道は模索中 将来的な拡張性を見込んだ基盤構築 開発者体験の向上を追求しています。 プロジェクト専用エージェント設定(検証段階) 最近追加されたエージェント機能により、開発だけでなく、調査や運用でも活躍できる特化エージェントを設定できます。手順書があれば細かいコードを書かなくてもエージェントが助けてくれる環境を構築中です。 以下は最近作ってみた「静的サイトをS3+CloudFrontで公開するためのエージェント」の例です。 エージェント用設定ファイル 基盤グループで稀に発生する静的サイトの公開をエージェントでできるようにしています。 もともとはエージェント関係なくQ用にコンテキストを作成していたので、エージェントはこのファイルを読み込む設定をしただけで数分で完成しています。 定義ファイルを簡単に記載しつつ、詳細なコンテキストは別ファイルとして渡しています。 起動方法 設定ファイルに指定した名前を指定してプロジェクト内で起動します。 実際の動作例 このように手順書やAI用のコンテキストがあれば、簡単に特定機能に特化したエージェントを作成できるので可能性は無限大だと思います。 事例1で紹介したプレゼンテーション作成のエージェントも今後はリポジトリ上でエージェントとして管理していけるように調整中です。 デザイナーでの活用(検証段階) まだこれからの段階ですがデザイナーでもPMでの活用をうけて検証を開始しています。 それによりAIを活用し素早いユーザー価値提供を目指しています。 プロトタイピング HTML/CSSでプロトタイプ作成 簡単な動作確認 FigmaとのMCP連携 サービス改善 企画からデザインのガイドライン化 企画から直接HTMLへ反映 デザインプロセスの省力化 まとめ Amazon Q Developerは、コーディングだけではなく、 設計からタスク定義、そしてエンジニア以外の生産性を革新的に向上させるプラットフォーム として機能しています。 LIFULLでは、エンジニアから始まった活用が、サービス企画、デザイナーまで広がり、各職種の特性に合わせた活用方法を見つけることで、組織全体の生産性向上を実現しています。 今後もさらなる活用を目指し、新しい可能性を探求していきます。 ※本記事はスライド用HTMLを出力する前のmarkdownファイルをもとにQ CLIでブログ用に出力し手動で整形して提供しています。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
アバター
こんにちは。プロダクトエンジニアリング部の江口です。主に賃貸部門の開発を担当しています。 このたび、LIFULL HOME'Sの賃貸詳細ページにおけるサーバーサイドの処理速度を改善し、 99パーセンタイルを60%改善しました 。 本記事では、このパフォーマンス改善をどのように実現したのか、具体的な技術的アプローチについて解説します。 背景 分析から見えたパフォーマンスボトルネック 改善のためのアプローチ 成果 終わりに 背景 サーバーサイドの処理遅延は、ユーザー体験だけでなく収益にも悪影響を及ぼします *1 。特に、平均処理時間は良好でも99パーセンタイル(p99) *2 が高い場合、一部のユーザーは大きな遅延に遭遇し、不満を感じてサイトから離脱してしまいます。そのため、p99を改善することは離脱率の低下に直結し、長期的には収益にも良い影響をもたらします。 こうした課題は多くのWebサービスで共通していますが、LIFULL HOME'Sの賃貸詳細ページでも例外ではありませんでした。詳細ページのサーバー処理時間のp99は、平均値と比較して大きく遅延している状態でした。 賃貸詳細システムにおけるサーバーサイド処理時間の比較 そこで、詳細ページにおけるボトルネックを特定し、p99の改善活動に取り組みました。 分析から見えたパフォーマンスボトルネック まず、アプリケーションのメトリクスやトレースから、ボトルネックとなっている処理の特定を試みました。分析を進める中で、特に以下の点が明らかになりました。 計測不能な処理時間の存在 : トレースのスパンとして計測されない、実体の不明な処理に時間がかかっている箇所が見られました。 イベントループの遅延 : 最大1秒にも及ぶイベントループの遅延が判明しました。これは、他のリクエスト処理もブロックし、サービス全体の応答性能を低下させる要因となりえます。 賃貸詳細システムのトレース イベントループの最大遅延(秒) これらのデータから、イベントループのブロッキングがアプリケーションロジックに起因する可能性が高いと判断しました。そこで、CPUの使用状況とメモリ消費を継続的に監視できるPyroscope(継続的プロファイラ)を導入し、詳細な調査を開始しました。Pyroscopeによる継続的プロファイリングの結果、イベントループのブロッキングとCPU占有を引き起こしていた可能性のある処理を見つけることができました。 URLエンコードの同期的な高負荷処理 : APIへのリクエスト送信前に、特にセッション情報のような長い文字列をURLエンコードする処理が、同期的に実行されていました。この処理はCPUを長時間占有することで、イベントループをブロックし、アプリケーション全体の応答性を低下させる要因となる可能性があります。 セッション情報のデシリアライズ : バックエンドAPIから取得するセッション情報は、PHPのシリアライズ形式で格納されています。このPHP形式のデータをアプリケーションで利用可能な形式にデシリアライズする処理もまた、CPUを大量に消費する同期処理でした。この処理がイベントループをブロックし、パフォーマンスの低下につながる可能性がありました。 同期的なサーバーサイドレンダリング : 賃貸詳細システムのHTMLレンダリングにはPreactを使用しています。サーバーサイドレンダリング(SSR)の際、tsxデータから仮想DOMノードを生成し、それをHTML文字列に変換する処理が同期的に実行されていました。この一連の処理はCPUを長時間占有することで、他のリクエスト処理の応答性を阻害する要因となりえます。 これらのボトルネックは、いずれも同期的な高負荷処理が原因でイベントループをブロックし、アプリケーション全体のパフォーマンスに影響を与えていたと考えられます。これら以外にも多数のボトルネックが存在しますが、代表的なものは上記の3点でした。 改善のためのアプローチ イベントループのブロッキングを抑制する一般的なアプローチとしては、CPUバウンドな処理の高速化やWorker Threadsへの処理のオフロードなどがあります。 試行錯誤の結果、URLエンコードの同期的な高負荷処理がイベントループをブロッキングする主要な原因の一つであることが判明しました。この処理を、外部ライブラリの qs からネイティブのURLSearchParamsに置き換えることで、イベントループのブロッキングが大幅に改善されました。特に、大規模なクエリパラメータを処理する場合、URLSearchParamsはqsと比較して最大で4倍以上高速に処理できることが確認できました。 // 変更前(qs) private convertQueryString(param: object) { return qs.stringify(param, { sort : ( a : string , b : string ) => { return a. localeCompare (b); } , arrayFormat : 'comma' , } ); } // 変更後(URLSearchParams) private convertQueryString(param: Record< string , unknown >) { const urlSearchParams = new URLSearchParams (); const sortedKeys = Object . keys (param). sort (); for ( const key of sortedKeys) { const value = param[key]; if (value == null ) continue ; if ( Array . isArray (value)) { urlSearchParams. append (key, value. join ( ',' )); } else { urlSearchParams. append (key, String (value)); } } return urlSearchParams. toString (); } URLSearchParamsに修正後の最大イベントループ遅延 この変更によって高速化できた理由は、主に「ネイティブ実装との速度差」と「処理のオーバーヘッド削減」にあります。 まず、URLSearchParamsはNode.jsにC++等で実装されたネイティブAPIであり、最適化されたマシンコードで極めて高速に動作します。対照的にqsはJavaScriptで実装されているため、実行速度に根本的な差が生まれます。さらに、qsはネストされたオブジェクトなど複雑なケースに対応する汎用的なライブラリであり、その分、内部には多くの条件分岐といったオーバーヘッドが含まれます。今回の実装は、必要な処理に特化してネイティブAPIを直接呼び出すため、こうしたオーバーヘッドが一切ありません。 これらの要因が組み合わさり、CPUを占有する同期処理の時間が劇的に短縮され、イベントループのブロッキングが解消されたと考えます。 成果 上記のURLエンコード処理の高速化のリリースによって、p99を60%改善することができました。 サーバーサイド処理時間(p99)の改善 終わりに 今回のパフォーマンス改善は、可観測性の向上を目指すところから始まりました。メトリクスや継続的プロファイリングツールの導入だけではなく、必要に応じて手動でスパンやメトリクスを追加することで、p99の高さを生み出す根本原因をデータから特定しました。 また、既存ライブラリが自分たちのユースケースにマッチしているか検証する重要性も再認識しました。汎用的なライブラリがパフォーマンスのボトルネックとなる場合があり、ネイティブAPIのような特化した代替手段を検討することで、劇的な改善につながることがあります。 ウェブアプリケーションの速度改善は、まるで謎解きのようです。メトリクス、トレースデータ、ログといった手がかりを丹念に観察し、パフォーマンスのボトルネックを特定していく作業は、個人的にとても楽しい時間です。そして、改善策を適用した結果、システムのレスポンスタイムが向上したり、プロファイルデータが変化したりするのを直接データで確認できるのは、何よりも魅力的だと感じています。 これからも、ユーザーの皆さんにより快適な体験を提供できるよう、システムのパフォーマンス改善に情熱を持って取り組んでいきたいです。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co *1 : https://glinden.blogspot.com/2006/11/marissa-mayer-at-web-20.html *2 : 応答時間の分布において、観測された応答時間の99%がこの値以下に収まる点を指します。
アバター
こんにちは、LIFULLでシニアエンジニアをしている渡邉です。普段はLIFULL HOME'Sの流通領域のエンジニアチームにてマネジメントをしています。 みなさんは業務の中でスライドを作る場面ってどのくらいありますでしょうか? 私は管理職になってから業務上ビジョンシェアリングの機会や総会等での発表の機会が増えました。 それに伴い以前にも増して圧倒的にスライドを作成する作業の時間が増えました。 私の場合、話したいことは思い付くし、文章に起こすこともできるのですが、それをスライドにするのが手間がかかる上スライドを作るセンスにも自信がありません。 これが回り回ってかなりストレスになっていました。 そんな課題感から、この作業をどうにか簡略化できないかを検討し、弊社で主に使われているAmazon Q Developerを使ってスライド作成を自動化するしくみを検討しました。 結果として、 従来数時間かかっていた作業が数分で完了 するようになり、生産性が大幅に向上しました。 今回は、その具体的な手法と実装について詳しく紹介します。 背景:管理職のスライド作成問題 管理職になると、ビジョンシェアリング、プロジェクトキックオフ、経営陣向け戦略発表など、さまざまな場面でスライドを作成する機会が増えます。 しかし、以下のような課題がありました。 時間がかかりすぎる : 1つのスライドセットに数時間程度必要 デザインセンスの不足 : 見栄えの良いスライドが作れない 構成の悩み : 伝えたいことをどう整理すればよいかわからない 繰り返し作業 : 似たような構成のスライドを何度も作成 これらの課題を解決するため、Amazon Q Developerを活用した自動化システムを構築することにしました。 解決アプローチ:LLMにHTMLを書かせる 今回採用したアプローチの核心は、 LLMにパワポのスライドのように動作するHTMLを生成させることで、擬似的なスライドを作成する ことです。PowerPointやKeynoteではなく、HTMLベースのスライドにすることで以下のメリットがあります。 自由度の高いデザイン : CSSを使った柔軟なレイアウト 一瞬での生成 : テンプレートに縛られない迅速な作成 簡単なデプロイ : 静的ファイルとして簡単にホスティング可能 バージョン管理 : Gitでの管理が容易 HTML故の自由度 : HTMLなのでスクローラブルなデザインにすることも可能 テキストのコピーのしやすさ : コードブロックをスライドに落とし込んだ場合テキストのコピーが容易 パワポライクな画面にも適用 : 普段のスライドアプリケーションと同様にキーボードでスライドを捲るデザインにもできる Amazon Q DeveloperはモデルとしてClaude Sonnet4を利用しているのでコードを作成することが得意です。 そのためスライドを作成するよりもスライドのように機能するHTMLを書かせる方がかなえたい要求を満たしやすいです。 実際に生成されたスライド風HTML 実装手順 1. Amazon Q CLI プロファイルの作成 まず、スライド作成専用のプロファイルを作成します。 /profile create slide 2. コンテキストファイルの設定 .aws/amazonq/rules/slide/ ディレクトリを作成し、コンテキストを紐づけます。 /context add "~/.aws/amazonq/rules/slide/*.md" 3. ガイドラインファイルの作成 最も重要なのが GUIDELINE.md の作成です。このファイルにスライド作成のルールやデザイン指針を詳細に記載します。 この名前に特に縛りはありませんが、私の場合GUIDELINE.mdとしています。 GUIDELINE.mdの主要な内容 私が実際に作成したコンテキストは以下の内容で設定して使っています。 # スライド作成用のコンテキスト ## 目的 - このコンテキストは、ビジョンシェアリングや発表資料に適したスライドを作成するために必要な情報と手順を提供します。 ## 作業の進め方 1. ユーザーから与えられた文章やREADMEの内容やURLの内容を読み解いた上で、発表資料としてわかりやすくシンプルな形になるようにアウトラインを生成します。 2. アウトラインを生成する場合には必ず、"##アウトラインの作成手順"に沿ってOUTLINE.mdを作成してください。 3. OUTLINE.mdの作成とブラッシュアップが完了した後で、OUTLINE.mdの内容を元にパワーポイントのスライドのような挙動をするHTMLファイルでスライドを生成します。 ## デザイン - #ed6103のカラーをメインの色として、その色を利用する上で違和感のないよう作成をお願いします。 - 主張したい文字や情報については文字を大きくしたり色を変更したりなど見た目でわかりやすくしてください。 - 背景は可能な限り白にして欲しい - 全体の配置として余白はあまりないように作ってください。 - 一枚目のスライドのタイトルは大きい文字でわかりやすく作ってください。 ## スライドで注意するポイント - 1スライド1メッセージもしくはシンプルな見た目のデザイン - 一枚目にはタイトルと発表者指名を記載(渡邉陸斗) - 読み上げないで説明できる内容 - 視覚効果(アイコン、グラフ)を効果的に使用 - 数字でアピールできるものに関しては必ず取り入れる - 堅い文章になりすぎないようにしつつも社会人として問題のない内容で記載 - フォントはできるだけ画面共有しても見やすいように大きめに作成する24px以上 - 必ずキーボード操作もできるように作成して欲しい - スクローラブルなブロックがある場合にはスクロールできることも記載して欲しい - スクローラブルなブロックがコードブロックの場合にはコピーボタンを記載して欲しい。 実際の使用フロー ステップ1: 伝えたいことを文章化 まず、README.mdやドキュメントに伝えたいメッセージや思い、数字を文章としてまとめます。たとえば、今回の記事の元となったREADME.mdは以下のような内容でした。 # 概要 AmazonQを使ってスライドを簡単に生成できるようにしたのでその方法の共有 ## 背景 管理職になって圧倒的にスライドを作成する時間が増えた。 話したいことは思いつくし、文章に起こせるけどそれをスライドにするのは時間がかかる。 あと絶望的にスライドを作るセンスがない。 そんな課題感からスライドの作成だけを楽にしたかった。 ## 成果 かなり雑に文章を書いてもちゃんとしたスライドに起こしてくれてビジョンシェアリング用の資料や発表用資料として生成してくれます。 普段なら4時間くらいかかっていた作業が数分で完了するので、生産性が上がっています。 ステップ2: アウトライン生成 Amazon Q Developerに文章を読み込ませ、スライドごとの構成要素をまとめた OUTLINE.md を生成してもらいます。 この段階で、スライドの全体構成と各スライドで説明したいことを整理します。 修正が必要であれば手を加えて情報の過不足をチェックしてください。 ステップ3: HTML生成 OUTLINE.md の内容に沿って、パワーポイントのようにキーボードで操作できるHTMLスライドを生成します。 生成されるHTMLには以下の特徴があります。 レスポンシブデザイン : さまざまな画面サイズに対応 キーボード操作 : 矢印キーでスライド切り替え可能 統一されたデザイン : ブランドカラー(#ed6103)を使用 読みやすいフォント : 24px以上の大きなフォントサイズ ステップ4: デプロイ 生成されたHTMLファイルをS3などの静的ホスティングサービスにアップロードします。 これによって周りの人にも資料を提供できます。 LIFULLではHTMLをホスティングするためのサービスを内製していますので、それを利用することで簡単にデプロイできます。 実際のプロンプト例 スライドを作成する場合に 実際に使用しているプロンプトの例を紹介します。 このREADME.mdの内容を元に、Amazon Q Developerを使ったスライド作成自動化について の発表用スライドを作成してください。 重要なポイント: - LLMにHTMLを書かせることで自由度の高いスライドが作れること - 4時間の作業が数分になったこと - 実際のプロンプトやコンテキストの内容も含めること まずはOUTLINE.mdを作成してください。 成果と効果 このしくみを導入した結果、以下の成果を得ることができました。 時間短縮効果 従来 : 3~4時間程度 現在 : 数分程度 短縮率 : 約90% 品質向上 統一されたデザインテンプレート 読みやすいフォントサイズとレイアウト 一貫したブランディング 柔軟性の向上 HTMLベースなので細かいカスタマイズが可能 CSSで自由なデザイン調整 JavaScriptでインタラクティブな要素も追加可能 まとめ Amazon Q Developerを活用したスライド作成自動化により、 数時間の作業を数分に短縮 できました。 特に、LLMにHTMLを直接生成させるアプローチにより、従来のプレゼンテーションツールでは実現困難な自由度の高いスライドを短時間で作成できるようになりました。 このしくみの成功要因は以下の3点です。 詳細なコンテキスト設計 : GUIDELINE.mdでの明確な指針 段階的な生成プロセス : アウトライン → HTML の2段階アプローチ HTMLベースの選択 : 柔軟性とデプロイの簡単さ 管理職として、限られた時間の中で質の高いプレゼンテーション資料を作成する必要がある方には、ぜひこのアプローチを試していただきたいと思います。 他にも報告レポートやブログ執筆用のプロファイルを生成することで作業を効率化することも可能だと思います。 Amazon Q Developerの可能性を最大限に活用することで、創造的な業務により多くの時間を割くことができるようになるでしょう。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
アバター
こんにちは、LIFULLでシニアエンジニアをしている渡邉です。普段はLIFULL HOME'Sの流通領域のエンジニアチームにて、マネジメントをしています。好きなCI/CDツールはGitHub Actionsです。 以前、こちらの記事で、私たちのリリースフロー改善への取り組みをお話しました。 www.lifull.blog あれから数ヵ月が経ち、私たちのGitHub Actions集約型リリースフローは想像以上に進化を遂げました。今回は、その変化と新しく追加された機能について、実際の開発現場での体験を交えながらお伝えします。 🚀 何が変わったのか?主要なアップデート 1. セットアップが驚くほど簡単になりました 2. Chrome拡張機能で承認作業が格段に楽になりました 3. 企画担当者の方でも迷わず使えるガイドを作りました 4. リリース忘れを防ぐ機能を追加しました 5. 開発タスクの管理が自動化されました 6. 🎯 自動リリース機能でリリーサーの負荷がほぼゼロに 7.使いやすさへのこだわり 見た目でわかる設計 自動化されたリリースプロセスにおける課題を解決するためのしくみ グローバルチームにも対応 実際の開発現場での変化 導入前の悩み 導入後の声 🎉 実際の導入効果:数字で見る劇的な改善 リリース頻度の大幅向上 承認からリリースまでの時間短縮 企画担当者にもわかりやすい 自動マージ機能の効果 通知システムの効果 あらゆるリポジトリ形態に対応 柔軟な導入アプローチ ユースケースに合わせた導入方法の提供 自動リリースがもたらした革命的変化 リリーサーの役割の変化 組織全体への影響 これからの展望 まとめ 🚀 何が変わったのか?主要なアップデート 1. セットアップが驚くほど簡単になりました 以前は手動での設定が必要でしたが、今では対話形式の inquirer を用いたCLIツールを用意しました。 npm install npm run setup これだけで、チームに必要なワークフローを選択しながら自動生成できます。「どの機能を組み合わせればよいかわからない」という悩みから解放されます。 CLIを実行した様子 2. Chrome拡張機能で承認作業が格段に楽になりました リリース承認の煩雑さを解決するChrome拡張機能を開発しました。 ボタン一つで承認完了 : /G長承認OK や /PJ承認OK のコメントがワンクリック 新リリースフローの対象リポジトリにて、承認に必要なドキュメントのリンクを表示 拡張機能により提供されるもの もう「承認コメントってどう書くんだっけ?」と悩む必要はありません。 具体例 : /G長承認OK というコメントを手動で入力する代わりに、PR画面に表示される「G長承認」ボタンをクリックするだけで承認が完了します。 3. 企画担当者の方でも迷わず使えるガイドを作りました 技術に詳しくない方でも安心してリリース承認ができるよう、専用ガイドを整備しました。 確認すべきポイントを4つに絞った分かりやすい説明 豊富なスクリーンショットで視覚的にサポート 「ここだけ見れば大丈夫!」という安心感を提供 具体例 : 「Epic Issueの確認」「ビジネス観点のチェック」「PJ承認コメント」「承認状況確認」の4ステップで、技術的な知識がなくても確実に承認作業を完了できます。 4. リリース忘れを防ぐ機能を追加しました 新しく追加したRelease PR Reminder機能で、リリース日にPRが長時間未マージの場合、自動でSlack通知が送られます。 Google Calendarと連携してリリース日を自動判定 通知タイミングは調整可能(デフォルト1日) リリーサーへの確実な通知でリリース漏れを防止 5. 開発タスクの管理が自動化されました Sub-Issue自動管理システムにより、開発タスクの進捗管理が飛躍的に向上しました。 自動でタスク分解 : PRに紐づく開発作業を自動で細分化 進捗の可視化 : 全タスク完了時に自動でラベル付与 承認者も安心 : 開発作業の完了状況が一目でわかる 具体例 : PRを作成すると「QA仕様書作成」「デザインチェック」「開発チェックシート」などのSub-Issueが自動生成され、各タスクの完了に応じてPR上の進捗バーが更新されます。 subissueによるタスク分解 開発タスクの進捗状況の確認 6. 🎯 自動リリース機能でリリーサーの負荷がほぼゼロに 今回の最大の進化は、 完全自動リリース機能 の実装です。これにより、リリーサーの作業負荷が劇的に軽減されました。 自動リリースのしくみ : リリース条件がすべて満たされた場合、自動的にmasterブランチへのマージ ステージング環境テスト・プロダクション環境テストが完了したPRのみが対象 ステージング環境テスト、プロダクション環境テストが終了していない場合には開発者への訴求をGitHub上で自動通知 リリース後の開発者への通知も自動化 1日経っても未マージのリリースPRの自動検出と通知 リリーサーのタスク変化 : 従来 : 手動でのPRマージ、リリースしたことを開発者へ通知、確認作業の管理 現在 : 例外的なケースのみの対応(通常時はほぼ作業なし) メリット : 人的ミスの排除 : 手動操作によるミスが完全になくなりました リリース時間の短縮 : 条件が整い次第、即座にリリースが実行されます リリーサーの負荷軽減 : 定型作業から解放され、より重要な業務に集中できます 24時間対応 : 人の都合に関係なく、いつでもリリースが可能です 7.使いやすさへのこだわり 見た目でわかる設計 ラベルによる可視化 : 承認状況がPRのラベルで即座にわかる 進捗バー : タスク完了率を視覚的に表示 ステータスアイコン : 一目で状況を把握できる 自動化されたリリースプロセスにおける課題を解決するためのしくみ 自動導入 : リリースフローのワークフローを導入する際にCLIを利用して導入することで、手続的にリポジトリナイズされた設定を施したうえで導入が可能 権限管理 : 承認、マージする際には適切な権限を持つ人のみが実施可能 自動チェック : リリースに必要な開発タスクや、リリースチェックなど実施すべき項目が適切に行われていることをワークフローにより自動的にチェック 通知の強化 : あらゆるリリースに関する通知を自動化し、人間による通達漏れを排除 リリース依存性の担保 : リリース順序が決まっているマイクロサービスにおけるリリース順序が逆転しないようにするための依存先のリリースストップ機能 強行リリース : 何らかの理由によりワークフローが失敗し続ける際や、承認をまたずリリースしたいものがある場合のための承認者による強制リリースの実行方法の確立 グローバルチームにも対応 日本語・英語両対応のドキュメント 国際的なチームでも安心して利用可能 実際の開発現場での変化 導入前の悩み 「承認コメントの書き方がわからない...」 「どのタスクが終わっているか把握できない...」 「リリース日なのにPRがマージされてない!」 「企画の人にGitHubの使い方を説明するのがたいへん...」 「リリーサーが忙しくてリリースが遅れる...」 導入後の声 「ボタン一つで承認が終わるなんて!」 「タスクの進捗が自動で更新されるから楽」 「リリース忘れがなくなった」 「企画の人も迷わず承認してくれる」 「リリーサーを待つ必要がなくなった!」 🎉 実際の導入効果:数字で見る劇的な改善 リリース頻度の大幅向上 導入後、 1日に2回のリリース が可能になりました。従来は承認プロセスの複雑さから週1-2回程度だったリリースが、自動化により大幅に増加。これにより、ユーザーへの価値提供スピードが格段に向上しています。 リリース数の変化 承認からリリースまでの時間短縮 承認からリリースまでの時間が劇的に短縮 されました。従来は承認後数日かかっていたリリースが、現在では承認と同日、場合によっては数時間以内にリリースが完了するケースも増えています。 リリースまでのリードタイムの変化 企画担当者にもわかりやすい 専用ガイドとChrome拡張機能により、 技術的な知識がなくても確実に承認作業を完了 できるようになりました。 自動マージ機能の効果 開発チームでは 自動マージ機能 が特に高く評価されています。 自動マージには、承認が完了したPRが自動的にステージング環境でマージされる機能と自動的にリリースを行う機能の2つが用意されています。 条件の整ったPRが自動的にマージされることで、リリーサー、開発者の待ち時間が大幅に削減され、より重要な開発作業に集中できます。 通知システムの効果 リアルタイム通知システム により、リリース状況の把握が格段に向上しました。関係者全員がリリースのタイミングを正確に把握できるようになり、連携ミスが大幅に減少しています。 あらゆるリポジトリ形態に対応 私たちが特に重視したのは、 どのような形態のリポジトリでも問題なく使える汎用性 です。 モノリスアプリケーション : 単一リポジトリでの大規模開発 マイクロサービス : 複数の小さなサービスに分かれた構成 フロントエンド・バックエンド分離 : 異なる技術スタックでの開発 ライブラリ・SDK : ほかのプロジェクトから利用されるコンポーネント どのような開発スタイルでも、チームが同じ操作感でリリース管理を行えるよう設計しています。 柔軟な導入アプローチ 私たちが特に意識したのは、「チームの状況に合わせて必要な機能だけを選んで導入できる」ということです。 ユースケースに合わせた導入方法の提供 承認フローの改善 : G長・PJ承認をGitHub上で完結 開発タスク管理 : Issue駆動開発でタスク管理を自動化 リリースチェック : 本番・検証環境での確認作業を体系化 完全自動化 : リリーサー不要の自動リリースを実現 メンテナンス性 : 自動バージョンアップ機能の提供 どの段階でも確実に開発体験が向上するよう設計しています。 自動リリースがもたらした革命的変化 リリーサーの役割の変化 従来のリリーサーの1日 : 午前中: リリース予定PRの確認 昼ごろ: リリースの実施と開発者への通知 夕方: リリースチェック作業の確認 現在のリリーサーの1日 : 午前、午後: システムからの自動通知を確認(必要時のみ) 例外ケース発生時のみ対応 通常時はほかの業務を行い、リリース作業は実施せず 組織全体への影響 開発速度の向上 : リリーサーの都合を待つ必要がなくなり、定刻にリリースできるようになりました 品質の安定 : 人的ミスが排除され、リリース品質が向上しました コスト削減 : リリース作業にかかる人的コストが大幅に削減されました ストレス軽減 : リリース日の緊張感が大幅に軽減されました これからの展望 私たちの目標は変わりません。 リリース関連作業の一元管理 自動チェックによる作業負荷軽減 最終的なリリーサー不要の実現 今回のアップデートで、この目標の大部分を達成できたと感じています。特に自動リリース機能により、「リリーサー不要」という最終目標にかなり近付きました。残りの部分も、社内のプロダクト運用チームからのフィードバックを元に継続的に改善していきます。 まとめ 今回のアップデートにより、私たちのGitHubリリースフロー自動化は、単なる「効率化ツール」から「開発体験を根本から変えるシステム」へと進化しました。 特に自動リリース機能の導入により、 リリーサーの作業負荷がほぼゼロになった ことは、私たちの想像を超える効果をもたらしました。実際の導入現場では、 1日2回のリリース が可能になり、 承認からリリースまでの時間が劇的に短縮 されるなど、具体的な成果が現れています。 技術者も、企画者も、リリーサーも、全員が「使っていて気持ちよい」と感じられるシステム。それが私たちの目指した姿であり、今回のアップデートで大きく近付けたと確信しています。 もしチームでリリース作業に課題を感じているなら、ぜひ一度試してみてください。きっと、リリース作業に対する考え方が変わるはずです。 私たちは今後も、実際のユーザーフィードバックをもとにした継続的な改善を続けていきます。より多くのチームが、この「未来のリリース体験」を享受できるよう、取り組みを続けてまいります。 今後も一歩ずつ改善を積み上げることで、リリースのさらなる加速化につなげていければと思います。 最後に、LIFULL ではともに挑戦し成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
アバター
こんにちは、テクノロジー本部コーポレートエンジニアリングユニットの籔田綾一です。今回は、情シス部門におけるKGI・KPIマネジメントの導入と実践について、その経緯と成果を共有します。私たちの試みが、同じ課題を抱える皆さんの参考になれば幸いです。 はじめに:情シス部門の成果を定量的に計測するには 「情報システム部門で、成果を定量的に示すことができるのだろうか?」 これは、多くの情シス部門が抱える課題ではないでしょうか。日々のインフラ運用、ヘルプデスク対応、セキュリティ対策、そして数々のプロジェクト推進など、情シス業務は多岐にわたります。その貢献を経営層や他部門に示す際、定性的な説明に終始してしまうケースも少なくありません。売上のような明確な指標がないため、部門の価値をどのように証明すれば良いのかという悩みがあるかと思います。 このような経緯から、 「情シス全体で採用できる定量的指標を自分で作ってみよう」 と考えました。 はじめに:情シス部門の成果を定量的に計測するには 「情報システム部門で、成果を定量的に示すことができるのだろうか?」 最初のステップ:指標の「物差し」を統一するアイデア 戦略と連動させるための工夫:ウェイト付けの導入 とにかく1以上を目指そう!:具体的な行動変化 固定費削減への意識と行動の変化 開発タスク進捗向上への意識と行動の変化 振り返り:やってみて分かったこと、そして今後の課題 まとめ:情シス部門の成果は定量的に計測できる まず、数十社もの大手有名企業の情シス責任者の方々にヒアリングを実施しました。その結果、多くの企業で 「部門全体の成果を定量的に測る指標があれば良いとは思うものの、実際には作れていない」という共通認識 があることが分かりました。個別の指標は存在するものの、それらを統合して部門全体の成果として示す仕組みは確立されておらず、正直なところ、私自身も半ば諦めかけていました。 しかし、それでも何か方法はないかと1年ほど模索する中で、ふと解決の糸口を思いつきました。 最初のステップ:指標の「物差し」を統一するアイデア 私が最初に直面したのは、「異なる性質の指標をどう統合し、定量的に評価するか?」という壁でした。固定費、従業員満足度、プロジェクト進捗率、問い合わせ対応時間。これらは単位も性質も全く異なります。 そこで考えたのは、 「物差しが違うなら、統一してしまえ」 という思い付きです。つまり、単位が異なる指標であっても、基準となる数値を設けて比較できるようにすれば良い、という発想です。 例えば、固定費削減であれば、年間予算ならば「1」を基準値とし、10%削減なら「1.1」、逆に10%オーバーなら「0.9」といった具合に表現します。開発進捗率も同様に、完了すべきタスク数(目標値)を分母、完了済みのタスク数を分子とし、「1」以上であれば目標達成とみなしました。これらを個々のKPIとみなし、その平均を部門全体のKGIとして捉えてみました。 戦略と連動させるための工夫:ウェイト付けの導入 設定したKPIが常に全社戦略と整合しているとは限りません。そこで、各KPIにウェイト設定を取り入れてみました。全社戦略における各項目の重要度に応じて、個々のKPIに重み付けを行うイメージです。 部門全体の目標達成度合い(仮に「戦略スコア」とします)をKGIとして、個々のKPIスコアにそれぞれのウェイトを掛けて算出されるようなイメージを表形式で示します。 KPI の種類 KPI名 予算/実績 KPI 重要度 ウェイト KGI コスト関連 固定費削減率 1億/9000万 1.1 高 0.4 0.44 開発進捗 開発タスク消化率 100件/90件 0.9 中 0.3 0.27 業務効率化 工数削減時間 110時間/100時間 1.1 中 0.2 0.22 その他(必要に応じて) システム安定稼働率 100%/100% 1 低 0.1 0.1 KGI(戦略スコア) 1.03 このように、各KPIに戦略上の重要度に応じたウェイトを設定し、それぞれの達成度を考慮することで、部門全体の戦略的な貢献度を評価することができます。そして、このウェイト付けにより、情シス部門の活動が、単なる日々の業務遂行ではなく、企業の戦略目標達成にどのていど貢献できているか定量的に示せます。 とにかく1以上を目指そう!:具体的な行動変化 KPIを評価にも組み込み、KPIマネジメントを半年間運用してみたところ、メンバーの行動に明らかな変化が現れました。 固定費削減への意識と行動の変化 これまで、具体的な数値目標として意識されていなかったコストに対して、「自分たちのKPI」という意識が芽生え、コスト削減に向けた具体的な行動が自発的に生まれるようになりました。 不要アカウントの削減: 無駄なコストを減らすため、各メンバーが主体的に利用状況を調査し、不要なアカウントの削除を提案・実行。 通信料の見直し: モバイル通信料の利用状況について、高額な利用をしているユーザーに対して利用状況の確認や改善提案を行う。 不要なサービスの見直し: 契約しているサービスの利用状況を調査し、利用頻度の低いサービスや重複しているサービスがないか積極的に洗い出し、解約に向けた検討を始める。 PC調達方法の再検討: PCのライフサイクルコストを意識するようになり、リースという選択肢を検討し始めました。コスト削減への意識がなければ、そもそもリースを検討することはなかった。 開発タスク進捗向上への意識と行動の変化 開発タスク消化率をKPIに設定したことで、「期日までにタスクを完了させる」という意識がより向上し、チーム内の協力体制が強化されました。 タスク管理の意識向上: 各メンバーが自身のタスクの進捗状況をより意識的に管理 積極的な協力体制: チーム内で互いに助け合い、タスクの遅延を防ぐための協力体制が生まれる あと一歩を頑張る: あと0.1で達成といった状況が見えるため、リスケよりもゴールしようと頑張る 振り返り:やってみて分かったこと、そして今後の課題 今回のKPIマネジメント導入を通じて、異なる指標を統一化し定量的に評価する難しさ、適切なウェイト付けの重要性、そして何よりも、目標を「見える化」することによる組織の変化の大きさを実感しました。 一方で、KPIの設定やウェイトのつけ方、目標値の妥当性、そして部門全体への浸透には、まだ改善の余地があると感じます。今後も定期的にKPIを見直し、より実効性の高いマネジメントサイクルを確立していく必要があると感じました。 まとめ:情シス部門の成果は定量的に計測できる 情シス部門は、決して「成果が測れない部門」ではありません。 「情シス部門の成果を定量的に計測する」という課題に対し、KPIマネジメントという試みを通じて、数字の裏にあるメンバー一人ひとりの意識と行動のポジティブ変化こそが、KPIマネジメントの成果であると強く感じています。 最後に、LIFULL ではともに挑戦し成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co 籔田綾一 LIFULLテクノロジー本部 コーポレートエンジニアリングユニット ユニット長 2010年入社、技術マネージャーとして商品開発を多数手掛けたのち、 Salesforce(CommunityCloud)を用いたB向けポータルサイト立ち上げ、オンライン受注システム構築。 社内のSaleforce組織を統合、機関システムとSalesforceを連携しマーケティング、CRM、販売管理と一気通貫のシステム構築。 現在は情報システム部門の責任者として社内システム刷新に取り組んでいます。
アバター
こんにちは、テクノロジー本部コーポレートエンジニアリングユニットの籔田綾一です。 LIFULLのエンジニアマネージャーの取り組みの一つ「 エンジニアキャリアクリニック 」について紹介します。 組織マネジメントの一環として、参考になれば幸いです。 「最高のチームをつくる」ために エンジニアキャリアクリニックの流れ リピーターも増加中! 参加者の声 まとめ 「最高のチームをつくる」ために それにはメンバー同士の相互理解と、一人ひとりの成長をサポートする環境が不可欠です。 そこで私たちは、メンバーがマネージャーの人となりを知り、気軽に相談できる場として「エンジニアキャリアクリニック」を開設しました。 この「クリニック」では、エンジニアマネージャーが「先生」となり、キャリアパス、技術的な課題、チームでの悩みなど、様々な相談に親身に対応します。 単なる自己紹介ページでは堅苦しく、話しづらい雰囲気になってしまうため、「クリニック」という形式を採用することで、より親しみやすく、相談しやすい環境を目指しました。 最初は数名の有志で始めました。今では、様々な経験を持つ全マネージャーが「先生」として登録し、活発に利用されています。もちろん、弊社のCTOも「先生」として参加し、メンバーの相談に乗っています。 「エンジニアキャリアクリニック」では、キャリアパスの相談だけでなく、日々の業務で直面する技術的な課題や、チームでのコミュニケーションに関する悩みなど、幅広い相談を受け付けています。 オンラインで簡単に予約ができ、それぞれのマネージャーの専門分野や経験に応じて、自分に合った「先生」を選ぶことができます。相談後には、必要に応じて具体的なアクションプランを検討し、フォローアップも行っています。 実際に、「クリニック」を利用したメンバーからは、「CTOと直接話せて刺激になった」「普段聞けない話が聞けて有益だった」「先生の経験談が参考になりモチベーションが上がった」といった声が寄せられています。 エンジニアキャリアクリニックの流れ 「先生」の顔写真と得意分野や相談にのれそうなこと(業務外も含め)を紹介するページを用意 メンバーは「先生」を選んで「予約」 当日中に返信、スケジュール調整 オンラインもしくは対面で30〜1時間ほど1on1を実施 事後に簡単なアンケート実施 予約が入ったら断らない、というスタンスで運営しており、業務であまり関わらないマネージャーにも相談しやすくなっています。 飲みながら、、といった予約も可能です! リピーターも増加中! 定期的な案内の際、はじめは「悩んでる人は相談しよう」のようなニュアンスで促していました。しかし、メンバーからは「いろんな人の考えや経験を聞くことで、キャリア形成のヒントが得られる」「人脈を広げることができる」といったポジティブな意見が多く聞かれるようになりました。そこで、最近はこれらのポジティブなメッセージを強調して案内するようにしています。 その結果、利用者は徐々に増え、今では月に4名ほどが「クリニック」を利用しています。 また、実施後のアンケートも高評価で、毎回同じ「先生」ではなくいろんな先生に予約するクリニックのリピーターも多くなってきました。全利用者の半数以上が2回以上利用しています。 先日は「キャリアを語る会」として、マネージャーが自身の経歴や経験を語る会も開催しました。今後もQに一度開催することで、特に若いメンバーがより利用しやすい雰囲気づくりを目指していきます。 参加者の声 「CTOとサシで話ができていい刺激になった」という声や、 「普段聞けないお話を聞けて非常に有益でした。★5つ」 「先生の考えや経験談がとても参考になりモチベが上がりました!」 「予約したらすぐに調整連絡が来てビックリです。次回も利用しようと思います。」 といった声をいただいてます。 まとめ 「エンジニアキャリアクリニック」では、メンバーが活躍するためのキャリア形成における一助となることを目指しています。 みんなが自分らしく成長出来るように、といった気持ちで「先生」が話を聞いて、語ってもくれます。 そして、困ったときだけでなく日常的に「クリニック」として活用してもらいつつ「最高のチーム」へ近づければと思います。 LIFULL ではともに挑戦・成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co 籔田綾一 LIFULLテクノロジー本部 コーポレートエンジニアリングユニット ユニット長 2010年入社、技術マネージャーとして商品開発を多数手掛けたのち、 Salesforce(CommunityCloud)を用いたB向けポータルサイト立ち上げ、オンライン受注システム構築。 社内のSaleforce組織を統合、機関システムとSalesforceを連携しマーケティング、CRM、販売管理と一気通貫のシステム構築。 現在は情報システム部門の責任者として社内システム刷新に取り組んでいます。
アバター
こんにちは!クオリティアーキテクトグループ(以下、QAG)でQAエンジニアをしている片野です。 好きなテスト技法はデシジョンテーブルテストです。 QAGでは横断組織として自動テストやツール開発、プロセス改善などの仕組みの構築に取り組んでいます。 今回は、テストの情報を用いた課題発見の取り組みについて紹介します。 QA組織の紹介 背景 やったこと 具体的な効果 品質ダッシュボードの概要 概要: 品質ダッシュボードを使う理由 活用の仕方 システム構成 今後の展望 まとめ QA組織の紹介 LIFULLでは、設計・実装を行う開発エンジニアがテストも担当しています。 QAエンジニアはプロジェクトを横断してテストの支援をしています。 背景 横断QA組織の立場からは、以下の状況により社内全体の状況把握が難しく、プロジェクトへのプロアクティブな支援が困難になっています。 社内には、さまざまな開発や保守のプロジェクトが同時に動いており、QAGがすべてのプロジェクトへ均等な支援を行えない 各プロジェクト・組織の品質面での課題や改善点がQAGから見えにくい 各プロジェクトの課題は相談を受けてから気付くことが多く、QAGは受動的な対応になりがち やったこと QAGでは、品質課題の解決に向け、品質ダッシュボードを作成しました。 このダッシュボードは、GoogleのLooker Studioを活用していて、様々なデータソースと連携してデータの可視化や分析ができます。 今回は特に、テストケースが格納されているQUBEのデータをLooker Studioに接続し、グラフや表を作成することで、テストの状況を可視化しました。 ※QUBEとはLIFULL独自のテスト設計書全社共通フォーマット。 www.lifull.blog QUBEのサンプル画像 具体的な効果 テストの状況を可視化することにより、冗長なテストケースの削減に効果がありました。 例えば、開発環境やステージング環境など、テストで見るべき観点が異なる環境でも、同じ内容のテストを実施しているケースが見受けられました(特に、テストケース数が多い(テストケース数が上位20%の)プロジェクトにおいて多く見られました)。 それらのテストケースの削減を実施した結果、最大で35%程度の削減をすることができました。 品質ダッシュボードの概要 概要: 品質ダッシュボードは、開発サイクルにおけるテスト関連アクティビティに関する指標を可視化します。 品質ダッシュボードを使う理由 データを可視化することにより、メトリクスベースのフィードバックループ(品質目標の設定→計測→改善)を運用できます。 フィードバックループを回すことで、品質目標(QCD)に近づけたり、品質目標が満たされている状態を保持できます。 活用の仕方 経験や勘ではなく、データに基づいた合理的な意思決定によって、以下のような活用を納得感を持って実現できます。 品質課題の特定 プロセス改善の効果測定 システム構成 今後の展望 今回はテストの効率化に焦点を当てていますが、効率化しすぎるとバグの見逃しにつながる可能性があります。 そのため、開発規模に対してテストケース数が適切かを判断するため、従来型(ウォーターフォール開発でよく使われるメトリクス)のテストケース密度やバグ密度の活用も検討しています。 また、開発生産性についてはアジャイルメトリクスを活用し、QCDの総合的な判断に役立てたいと考えています。 まとめ メトリクスの活用は始まったばかりですが、今回の取り組みを通して、新たな発見もありました。 それは、開発環境やステージング環境など、テストで見るべき観点が異なる環境でも、同じ量のテストを実施しているケースが見受けられたことです。 これは、テストが効率的に行われていないことを示唆しています。 このように、テストのメトリクスを分析することで、ソフトウェア開発の効率や品質を高められると考えています。 最後まで読んでいただきありがとうございました。 LIFULL ではともに挑戦・成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
アバター
こんにちは、グループデータ本部データサイエンスグループの清田です。 昨年の DEIM 2024 に引き続き、「 第17回データ工学と情報マネジメントに関するフォーラム(通称DEIM 2025) 」に参加・登壇してきましたので、その様子を報告いたします。 www.lifull.blog 過去最大の開催規模 生成AI・大規模言語モデル研究の盛況 DEIMの参入障壁を下げる取り組み LIFULLのスポンサー活動とデータセット提供 今後の展開:新たなデータセット提供に向けて おわりに 過去最大の開催規模 DEIM 2025は、2025年2月27日(木)から3月4日(火)にかけて開催されました。昨年に引き続き「直列ハイブリッド」形式を採用し、2月27日(木)から3月1日(土)までがZoom Eventsを用いたオンライン開催、3月3日(月)と4日(火)は福岡国際会議場でのオンサイト開催となりました。 現地会場の福岡国際会議場(福岡市博多区) 今回のDEIMは過去最大規模となる840名もの参加者を集め、データ工学および情報マネジメントに関する研究コミュニティの活況を示しました。429件の一般発表が行われ、そのうち口頭発表とポスターによるインタラクティブ発表の両方が行われたものが386件(うちデモ発表46件)、口頭発表のみが43件という構成でした。 生成AI・大規模言語モデル研究の盛況 昨年に引き続き、今年のDEIMでも生成AI、特に大規模言語モデル(LLM)の利用に関する多数の研究発表が行われていました。5つのトラック(「自然言語処理・機械学習基礎」、「ビッグデータ基盤技術・データセキュリティ・プライバシ」、「情報検索・情報推薦・ソーシャルメディア」、「メディア処理・HCI・人間中心情報マネジメント」、「高度なデータ利活用・ドメイン応用」)すべてにおいて、生成AIの活用事例や研究成果が報告されていました。 特に印象的だったのは、従来の自然言語処理や情報検索の技術がLLMによってどのように変革されつつあるかについての議論です。単にLLMを使うだけでなく、その特性を理解した上で従来技術と組み合わせる研究アプローチが多く見られました。 DEIMの参入障壁を下げる取り組み 今回私が参加して特に印象に残ったのは、兵庫県立大学の大島裕明先生が企画された「DEIMの参入障壁を下げるBoF」です。BoF(Birds of a Feather)セッションとは、同じ興味や関心を持つ参加者が集まって自由に議論する場で、今回はDEIMをより多くの人にとって参加しやすい学会にするためのアイデアが活発に交換されました。特に初めての参加者が楽しく参加できる仕掛けについてのディスカッションが非常に参考になりました。例えば、「初心者向けのオリエンテーションセッション」など、具体的かつ実現可能なアイデアが多く提案されていました。 学会は研究発表の場であると同時に、研究者同士のネットワーキングの場でもあります。特に学生や若手研究者にとって、参入障壁を下げる取り組みは非常に重要であり、DEIMコミュニティの今後の発展につながる貴重な議論だったと感じました。 LIFULLのスポンサー活動とデータセット提供 LIFULLは今回も、前回に引き続きゴールドスポンサーとして出展しました。DEIM 2025には、プラチナスポンサー7件、ゴールドスポンサー15件、シルバースポンサー2件という多くの企業・団体からの支援があり、産学連携の場としても機能していました。 私たちLIFULLからは、 LIFULL HOME'Sデータセット の2015年11月の提供開始からの利用実績の分析結果を技術報告として発表しました。現在までに170件を超える研究成果が発表されており、学術研究コミュニティへの貢献が着実に実を結んでいることを報告できました。 また、新たなデータセットの提供を予定していることについても告知しました。AIや情報処理に関する研究は、多くの研究者がアクセスできる共有データ資源の存在に支えられて発展してきました。生成AI技術の隆盛により、「データを独占的に保有すること自体が巨大な利益につながる」という状況も生まれている中で、オープンなデータ資源の提供は今後も重要な役割を担っていくと考えています。 今後の展開:新たなデータセット提供に向けて 現在、私たちLIFULLでは新たなデータセットの提供開始に向けて準備を進めています。このデータセットは、これまでの不動産情報に加えて、より多様な分野の研究に活用できる内容となる予定です。 生成AIの発展や社会実装が急速に進む中、高品質なデータセットの価値はますます高まっています。LIFULLは今後も、研究コミュニティへの貢献を通じて、社会課題の解決に向けた技術開発を支援してまいります。 来年のDEIM 2026でも、より多くの研究者との交流を深め、データ工学や情報マネジメントの分野の発展に寄与していきたいと考えています。 おわりに DEIM 2025は、オンラインとオンサイト、それぞれの利点を活かした運営により、全国各地からの参加が可能となり、多様な立場の参加者による有意義な議論が展開されました。 LIFULLでは、今後も学会イベントのサポートを継続するとともに、データサイエンスやAI技術を活用した社会課題解決に取り組む仲間を募集しています。豊富な研究開発資源を活かしながら、多様な社会課題の解決に向けた研究開発やプロダクト創出に一緒に取り組んでみませんか? データサイエンスグループでは「活用価値のあるデータを創出」し「データを活用した新たな機能やサービスの研究開発」を加速してくださるデータサイエンティスト(R&D)を募集しています。 興味を持っていただけた方は、 カジュアル面談 も行っていますのでお気軽にご連絡ください。 hrmos.co
アバター
こんにちは、テクノロジー本部の布川です。 普段は社内のシステム基盤の運用を担当しています。 先日の記事にありましたように、社内でモノづくりイベント『創民祭』が開催されました。 www.lifull.blog 今回は参加してみて感じたことなどを共有させていただきます! 創民祭参加のきっかけ 創民祭参加につながるアプリ開発のモチベーションが高まったのは、入社2年目のSET研修がきっかけでした。 SET研修とは、新卒2年目のエンジニアを対象に、3人程度のチームでプロダクトをスクラッチ開発しながら、それぞれの技術課題を克服していく研修プログラムです。 業務で基盤運用に関するサービスの設計や実装に携わりつつも、基本的な設計思想やアプリを拡張性高く保って開発する方法などに関して知識と経験の不足を感じていたところ、SET研修で多くを学ぶことができました。 今年度のSET研修参加者からの記事もよろしければ読んでみてください。 www.lifull.blog その後は業務中や、同期の仲間がプライベートで制作しているアプリの開発を手伝ったりする中でSET研修で学んだことの有用性を感じたことが、創民祭に出展するアプリの開発に繋がりました。 アプリの着想と作品概要 今回創民祭で出展したアプリは、一日の満足感を高めることがコンセプトになっています。 生活の中でその日の気分の記録を取っていたところ、行った方が良いと感じていることを行い、行わない方が良いと感じていることを行わなかった時ほど、良い一日だったと感じる傾向が高いことが分かりました。 そのため、良い習慣を継続しつつ、良くない習慣(スマホの見過ぎ)を防ぐことをサポートするアプリを作ることにしました。 特定のスマホアプリの利用時間を目標以内に抑えつつ、良い習慣を一定以上実行できた日を「良い一日」としてカウントし、その達成状況と日々のムードログや日記を比較して効果を測定することを目的としています。 制作したアプリ 開発プロセス 業務外の時間で、半年ほどかけてアプリのコンセプト作りから機能要件決め、システムや画面の設計を少しずつ行いました。 画面の設計が終わった頃、試しにChatGPTに画面のスクリーンショットを渡してみたところ、想像以上に高いクオリティのSwiftコードが出力されたので、これは創民祭に間に合うかもとそこから一ヶ月弱でViewやロジックの実装を行いました。(この時はまだAIエージェントの存在を知らず、ChatGPTからエディタへコピペを繰り返すことに・・・) その後、完成を急ぐあまりシステムの設計が十分に反映されていなかったため、創民祭が終わってから数ヶ月かけて実装をクリーンな形に改善しました。 成長と普段の業務への影響 今回の開発を通して、最初の段階で拡張しやすい基礎となる設計を、時間を掛けて用意しておくことの大切さを改めて学びました。 昔の個人開発では行き当たりばったりの実装で行き詰まることが多く、それが原因で開発を諦めてしまう経験もありましたが、今はAIエージェントによる拡張を繰り返しても分かりやすさや変更のしやすさがほとんど失われず、その学びの成果が出ていると感じます。 普段の業務でも、設計段階で複雑さを排除しシンプルさを追求することで、その後の開発効率が向上することを実感しています。 また、創民祭を通して色々な方にフィードバックを頂けたおかげで、アプリのコンセプトに対する思索が深まったり、UI/UXに関する課題が明らかになり、とても良い機会となりました。 今後の目標 色々な方に頂いたFBをもとにアプリの形を整えた上で、使ってもらえるようにリリースすることを目指しています。 創民祭への参加は、業務外の技術習得に関してFBを受けられたり、社内でのコミュニケーションや新たな発見につながる取り組みです。LIFULLには、技術やものづくりに対する想いが強いメンバーが集まっているからこそ、こうした場が活発に機能していると感じます。 今回の参加でも色々な発表に触れることができ、業務の質の向上とより良いプロダクト開発につながる貴重な経験となりました。これからも職種を問わず社内の方々とさらに盛り上げていければと思います。 最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。 hrmos.co hrmos.co
アバター
KEELチーム の相原です。 前回のエントリは「小さい経路最適化ミドルウェアを実装してあらゆるAZ間通信を削減する」でした。 www.lifull.blog 今回は、MCPサーバを比較的安全に動かすために色々やってた話を書きたいと思います。 MCPについて MCPサーバのリスク なるべくローカルで動かさない ローカルではせめてDockerで動かす 無理やりHTTP Transportに対応する セッションの開始 コマンドの起動 ペイロードを受け取るためのエンドポイントの実装 まとめ MCPについて MCPはModel Context Protocolの略で、Anthropicが標準化を主導するLLMとその外部を繋ぐプロトコルです。 github.com これによりGitHubやPlaywrightといった外部のツールをLLMが自律的に利用できるようになり、OpenAIもサポートを表明したことから大きな注目を集めています。 要は標準化されたFunction Callingということになるでしょう。 我々KEELチームでは2年ほど前から無限にスケールする汎用AI(仮)を開発してきており、これは内部でFunction Callingするエージェントを複数協調させて動かすことであらゆるタスクを解決することを狙っています。 www.lifull.blog Function Calling(MCPサーバ)を横に大量に並べて使おうとするとトークン消費が無視できなくなるはずで、当時我々はそれを解決するためにマルチエージェントとして複数のエージェントを協調させることを選びました。 (最近の A2A の動きを見ていても、まだこの設計でやっていけそうだなと感じています) また、エージェントを簡単に開発できるフレームワークも提供することで、社内のContributorが自由に社内システムとの連携を実装できるようにもなっています。 ということもあり、我々目線ではMCPの登場は「コミュニティが成熟すればFunction Calling実装の手間が省けるな~」くらいの受け止め方だったわけですが、今となっては職種問わず多くの人がローカルでMCPサーバを動かそうとし、社内システムのMCPサーバも開発されるようになってきました。 そろそろMCPが無視できなくなってきており、(本業はマルチテナントなKubernetesクラスタの開発ではありつつ)プラットフォーマーとして動くことにしました。 MCPサーバのリスク 2025年5月現在のよくあるMCPサーバの実行方法は npx や docker run 経由のものだと理解しています。 Docker for Desktop有償化とかCPU Virtualizationが必要であることを考えると、職種問わず利用者が多いのは npx でしょうか。 多くの場合どちらも素朴に最新のMCPサーバの実装を取得してきて、実行ユーザの権限でstdio Transport経由でMCPホストが利用します。 そしてその場合、 悪意のあるMCPサーバを誤って実行してしまうと、踏み台として利用されたり npx 経由での実行では環境変数やファイルを奪われてしまうというリスクがあります。 最近ではMCPホストのUIからは見えない形でLLMに対して悪意のある指示をする攻撃手法が発見されるなど、MCPサーバを対象にした攻撃は巧妙化しつつあり注意が必要です。(それでも私たちはAuto-approveを使ってしまうわけですが) invariantlabs.ai とりあえずバージョンは固定したいですし、バージョンがプログラムの中身を一意に識別するものでないことを考えると、最低限 docker run ではDigestでの参照や、他もChecksumまでやれると理想でしょう。 ただそれだけではまだ不十分だと感じていて、欲を言えばWebAssemblyやDenoのようなサンドボックス上で明示的にCapabilityを与えた上で実行したいところです。 一方でこれらはMCPサーバの実装に依存する部分が多くあり、その中で比較的安全にMCPサーバを運用できないかと色々やってきました。 なるべくローカルで動かさない そもそも各々がローカルで大量のMCPサーバを実行することが会社的には少し厳しさがあると感じています。 もちろんPlaywrightのMCPサーバのように手元のデスクトップ環境を操作したい場合はローカルで動かす必要がありますが、社内システム用のMCPサーバやOrganizationレベルで権限を持ったGitHubなどのMCPサーバは、社内共通で一つ動かしておいてそれを各々が参照するだけでよい可能性があります。 MCPにはいくつかのTransport実装があり、よく使われる(?)stdio TransportはMCPホストが直接コマンドとしてMCPサーバを実行し、その名の通りstdioを介してMCPサーバとやり取りをします。 一方で、Server Sent Eventsを利用したTransport実装やそれを改良した Streamable HTTP Transport と呼ばれるHTTPに載ったものもあり、これらはリモートにあるMCPサーバを利用可能です。 そのため、LIFULLではこれらのTransportを実装したMCPサーバはまとめて前述のマルチテナントなKubernetesクラスタで運用して提供するようにしています。 apiVersion : apps/v1 kind : Deployment spec : template : spec : automountServiceAccountToken : false securityContext : fsGroup : 65532 seccompProfile : type : RuntimeDefault containers : - name : mcp securityContext : privileged : false allowPrivilegeEscalation : false capabilities : drop : - ALL readOnlyRootFilesystem : true runAsUser : 65532 runAsNonRoot : true seccompProfile : type : RuntimeDefault <snip> Kubernetesのベストプラクティス(Pod Security Standards)に沿って厳格な権限で動かしています。 明示的に許可しない限り、MCPサーバはrootfsへの書き込み権限すら与えられません。 また、LIFULLではすべてのコンテナにIstioのプロキシを入れることを義務付けており、MCPサーバにも同様にIstioのプロキシを入れて、許可した接続先以外にはリクエストできないようにしています。 apiVersion : networking.istio.io/v1beta1 kind : Sidecar spec : egress : - captureMode : DEFAULT hosts : - istio-system/istiod.istio-system.svc.cluster.local - istio-system/otel-agent.istio-system.svc.cluster.local outboundTrafficPolicy : mode : REGISTRY_ONLY <snip> www.lifull.blog これにより、ローカルで動かす必要がないMCPサーバをプラットフォームから提供することで、リソースの効率化とともに比較的安全に運用することができました。 ローカルではせめてDockerで動かす とはいえ全てのMCPサーバをリモートで動かせるわけではありません。 前述のようにデスクトップ環境に依存するようなMCPサーバはローカルで動かす必要がありますし、Personal Access Tokenを利用せざるを得ないものも同様です。 その際は、Kubernetesで実現していたものと似たような隔離空間をDocker Composeを使って実現することを推奨しています。 # docker-compose.yaml services : tcp-proxy : image : ghcr.io/lifull/keel/proxy:latest command : - https://example.com - --mode - tcp - --address - 0.0.0.0:443 networks : default : {} internal : ipv4_address : 172.16.255.1 my-mcp-server : image : ghcr.io/lifull/keel/my-mcp-server:latest cap_drop : - ALL environment : - PERSONAL_ACCESS_TOKEN=${PERSONAL_ACCESS_TOKEN} extra_hosts : - "example.com:172.16.255.1" networks : internal : {} networks : internal : internal : true ipam : driver : default config : - subnet : 172.16.0.0/16 default : driver : bridge Dockerはデフォルトでbridgeネットワーク上でコンテナを動かし、bridgeを介してホストのネットワークからインターネットに出ていきます。 そのため、悪意のあるMCPサーバをDockerコンテナとして動かしてしまうとそのまま外にリクエストされてしまうわけです。 そこで、この例ではDocker Compose内に閉じたネットワーク internal を作成し、MCPサーバにはその internal のみを割り当てることでインターネットに疎通できないようにしています。 しかし、ものによってはGitHubのAPIを叩くなどインターネットに疎通する必要があるものもあります。 その際はTCP Proxyを同じく internal ネットワーク内に用意したうえで、そのTCP Proxyのみをbridgeネットワークにも割り当てることで、許可した接続先にのみリクエストできるようにしています。 extra_hosts を利用することでMCPサーバのプログラムを一切変更することなく、安全にTCP Proxyを経由させることが可能です。 いちいち解説するまでもないと思いますが、TCP Proxyの実装はこんな感じです。 TCP Proxy func runTCPServer(listener net.Listener, target *url.URL, a *Args) error { shutdown := make ( chan struct {}, 1 ) semaphore := make ( chan struct {}, a.MaxConnections) wg := sync.WaitGroup{} go func () { for { local, err := listener.(*net.TCPListener).AcceptTCP() if err != nil { select { case <-shutdown: return default : continue } } semaphore <- struct {}{} wg.Add( 1 ) go func () { defer func () { <-semaphore wg.Done() }() defer local.Close() remoteAddress := target.Host if target.Port() == "" { switch target.Scheme { case "http" : remoteAddress = net.JoinHostPort(target.Hostname(), "80" ) case "https" : remoteAddress = net.JoinHostPort(target.Hostname(), "443" ) } } remote, err := net.DialTimeout( "tcp" , remoteAddress, 10 *time.Second) if err != nil { return } defer remote.Close() c := make ( chan struct {}, 2 ) f := func (c chan struct {}, dst io.Writer , src io.Reader ) { _, _ = io.Copy(dst, src) c <- struct {}{} } go f(c, remote, local) go f(c, local, remote) select { case <-c: case <-shutdown: local.CloseWrite() } }() } }() quit := make ( chan os.Signal, 1 ) signal.Notify(quit, syscall.SIGTERM) <-quit time.Sleep(time.Duration(a.Lameduck) * time.Second) close (shutdown) listener.Close() wg.Wait() return nil } docker-compose.yaml を配布するだけで便利なMCPサーバを社員に行き届かせることができるので一石二鳥ですね。 一つトレードオフとして、この場合MCPサーバは docker compose up でまとめて起動するため、必然的にMCPクライアントからはstdio TransportではなくHTTP Transportを利用することになります。(JetBrainsのJunieはHTTP Transportに未対応だったりします) 無理やりHTTP Transportに対応する 一方で、一つ目のリモートで動かすにせよ二つ目のDocker Composeで動かすにせよ、MCPサーバがHTTP Transportに対応していることが前提となります。 しかし野良のMCPサーバの中にはstdio Transportしか実装していないものがそれなりにあります。 そういったMCPサーバでは上記のアプローチが取れないので工夫が必要です。 そこで、我々KEELチームはstdio Transportにのみ対応しているMCPサーバを無理やりSSE Transportに対応する小さいプロキシを開発しました。 バイナリとして簡単に配布できるようにGoで書いています。 mcp-stdio-proxy package main import ( "bufio" "context" "encoding/json" "errors" "flag" "fmt" "log" "net" "net/http" "os" "os/exec" "os/signal" "runtime/debug" "sync" "syscall" "time" "github.com/google/uuid" ) type MCPMethod string // https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-03-26/schema.ts const ( CancelledNotification MCPMethod = "notifications/cancelled" InitializeRequest = "initialize" InitializedNotification = "notifications/initialized" PingRequest = "ping" ProgressNotification = "notifications/progress" ListResourcesRequest = "resources/list" ListResourceTemplatesRequest = "resources/templates/list" ReadResourceRequest = "resources/read" ResourceListChangedNotification = "notifications/resource/list/changed" SubscribeRequest = "resources/subscribe" UnsubscribeRequest = "resources/unsubscribe" ResourceUpdatedNotification = "notifications/resource/updated" ListPromptsRequest = "prompts/list" GetPromptRequest = "prompts/get" PromptListChangedNotification = "notifications/prompt/list/changed" ListToolsRequest = "tools/list" CallToolRequest = "tools/call" ToolListChangedNotification = "notifications/tool/list/changed" ) type MCPMessage struct { JSONRPC string `json:"jsonrpc"` Method MCPMethod `json:"method"` ID any `json:"id,omitempty"` } type session struct { id string responseQueue chan [] byte requestQueue chan [] byte } func main() { var address string var terminationGracePeriodSeconds int var lameduck int var keepAlive bool var verbose bool flag.StringVar(&address, "address" , "0.0.0.0:8080" , "" ) flag.IntVar(&terminationGracePeriodSeconds, "termination-grace-period-seconds" , 10 , "The duration in seconds the application needs to terminate gracefully" ) flag.IntVar(&lameduck, "lameduck" , 1 , "A period that explicitly asks clients to stop sending requests, although the backend task is listening on that port and can provide the service" ) flag.BoolVar(&keepAlive, "http-keepalive" , true , "" ) flag.BoolVar(&verbose, "verbose" , false , "" ) flag.Parse() args := flag.Args() if len (args) == 0 { log.Fatalf( "command not specified" ) } name := args[ 0 ] var arg [] string if len (args) > 1 { arg = args[ 1 :] } sessions := &sync.Map{} mux := http.NewServeMux() mux.HandleFunc( "GET /sse" , func (w http.ResponseWriter, r *http.Request) { w.Header().Set( "Content-Type" , "text/event-stream" ) w.Header().Set( "Cache-Control" , "no-cache" ) w.Header().Set( "Connection" , "keep-alive" ) flusher, ok := w.(http.Flusher) if !ok { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } cmd := exec.Command(name, arg...) stdin, err := cmd.StdinPipe() if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } stdout, err := cmd.StdoutPipe() if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if err := cmd.Start(); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } s := &session{ id: uuid.New().String(), requestQueue: make ( chan [] byte , 65536 ), responseQueue: make ( chan [] byte , 65536 ), } sessions.Store(s.id, s) go func () { scanner := bufio.NewScanner(stdout) for scanner.Scan() { s.responseQueue <- scanner.Bytes() } }() defer func () { sessions.Delete(s.id) _ = cmd.Process.Kill() _ = cmd.Wait() close (s.requestQueue) close (s.responseQueue) }() _, _ = fmt.Fprintf(w, "event: endpoint \n data: %s \n\n " , fmt.Sprintf( "http://%s/messages?sessionId=%s" , r.Host, s.id)) flusher.Flush() for { select { case data := <-s.requestQueue: _, _ = stdin.Write(data) case data := <-s.responseQueue: _, _ = fmt.Fprint(w, fmt.Sprintf( "event: message \n data: %s \n\n " , data)) flusher.Flush() case <-r.Context().Done(): return } } }) mux.HandleFunc( "POST /messages" , func (w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get( "sessionId" ) if id == "" { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } sany, ok := sessions.Load(id) if !ok { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } s := sany.(*session) var rawMessage json.RawMessage if err := json.NewDecoder(r.Body).Decode(&rawMessage); err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } var mcpMessage MCPMessage if err := json.Unmarshal(rawMessage, &mcpMessage); err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if verbose { log.Printf( "%s: %s" , id, mcpMessage.Method) } s.requestQueue <- rawMessage s.requestQueue <- [] byte ( " \n " ) w.Header().Set( "Content-Type" , "application/json" ) w.WriteHeader(http.StatusAccepted) _, _ = w.Write([] byte (http.StatusText(http.StatusAccepted))) }) mux.HandleFunc( "GET /healthz" , func (w http.ResponseWriter, r *http.Request) { w.Header().Set( "Content-Type" , "text/plain; charset=utf-8" ) w.WriteHeader(http.StatusOK) _, _ = w.Write([] byte (http.StatusText(http.StatusOK))) }) listener, err := net.Listen( "tcp" , address) if err != nil { log.Fatalf( "failed to listen: %+v" , err) } server := &http.Server{ Handler: mux, } server.SetKeepAlivesEnabled(keepAlive) go func () { defer func () { if err := recover (); err != nil { log.Printf( "panic: %+v \n %s" , err, debug.Stack()) } }() if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatalf( "failed to listen: %+v" , err) } }() quit := make ( chan os.Signal, 1 ) signal.Notify(quit, syscall.SIGTERM) <-quit time.Sleep(time.Duration(lameduck) * time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(terminationGracePeriodSeconds)*time.Second) defer cancel() if err := server.Shutdown(ctx); err != nil { log.Fatalf( "failed to shutdown: %+v" , err) } } 開発当時はStreamable HTTP TransportがなかったのでSSE Transportにのみ対応しています。 それでは実装を軽く解説していきます。 セッションの開始 SSE Transportはこちらの仕様の通り、 /sse のようなSSEのエンドポイントで初回のリクエストを受けると、ペイロードを受け取るための別のエンドポイントを event: endpoint として返す必要があります。 github.com mux.HandleFunc( "GET /sse" , func (w http.ResponseWriter, r *http.Request) { w.Header().Set( "Content-Type" , "text/event-stream" ) w.Header().Set( "Cache-Control" , "no-cache" ) w.Header().Set( "Connection" , "keep-alive" ) flusher, ok := w.(http.Flusher) if !ok { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } <snip> _, _ = fmt.Fprintf(w, "event: endpoint \n data: %s \n\n " , fmt.Sprintf( "http://%s/messages?sessionId=%s" , r.Host, s.id)) flusher.Flush() <snip> } これはSSEがサーバからクライアントにメッセージを送信するための一方向の仕組みであるためです。 developer.mozilla.org そのためSSE Transportでは2つのHTTPエンドポイントを実装することで、MCPクライアントとの双方向通信を実現しています。 もう一つのエンドポイントの実装は後述するため、ここではSSEのエンドポイントのみ解説します。 sessions := &sync.Map{} mux.HandleFunc( "GET /sse" , func (w http.ResponseWriter, r *http.Request) { <snip> s := &session{ id: uuid.New().String(), requestQueue: make ( chan [] byte , 65536 ), responseQueue: make ( chan [] byte , 65536 ), } sessions.Store(s.id, s) <snip> defer func () { sessions.Delete(s.id) <snip> close (s.requestQueue) close (s.responseQueue) }() <snip> } UUIDを発行してセッションを開始しています。 ここで開始されたセッションはMCPクライアントに通知したペイロードを受け取るためのエンドポイントからも利用するため、一つ上のスコープで sessions として保持します。 コマンドの起動 セッションの開始と同時にstdio Transportを実装したMCPサーバの起動もしましょう。 注意点として、stdio Transportで実装されたMCPサーバは内部で状態を持つため、多くの場合セッションごとにMCPサーバを起動する必要があります。 sessions := &sync.Map{} mux.HandleFunc( "GET /sse" , func (w http.ResponseWriter, r *http.Request) { <snip> cmd := exec.Command(name, arg...) stdin, err := cmd.StdinPipe() if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } stdout, err := cmd.StdoutPipe() if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if err := cmd.Start(); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } go func () { scanner := bufio.NewScanner(stdout) for scanner.Scan() { s.responseQueue <- scanner.Bytes() } }() defer func () { _ = cmd.Process.Kill() _ = cmd.Wait() <snip> }() <snip> for { select { case data := <-s.requestQueue: _, _ = stdin.Write(data) case data := <-s.responseQueue: _, _ = fmt.Fprint(w, fmt.Sprintf( "event: message \n data: %s \n\n " , data)) flusher.Flush() case <-r.Context().Done(): return } } } stdio TransportのMCPサーバの標準出力を s.responseQueue を介してMCPクライアントに通知し、セッション開始時にMCPクライアントに通知したペイロードを受け取るためのエンドポイントから送信される s.requestQueue から受け取ったリクエストは stdin.Write(data) で標準入力に書き込んでいます。 セッション終了時にコマンドを終了することも忘れないようにしましょう。 ペイロードを受け取るためのエンドポイントの実装 mux.HandleFunc( "POST /messages" , func (w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get( "sessionId" ) if id == "" { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } sany, ok := sessions.Load(id) if !ok { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } s := sany.(*session) var rawMessage json.RawMessage if err := json.NewDecoder(r.Body).Decode(&rawMessage); err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } var mcpMessage MCPMessage if err := json.Unmarshal(rawMessage, &mcpMessage); err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if verbose { log.Printf( "%s: %s" , id, mcpMessage.Method) } s.requestQueue <- rawMessage s.requestQueue <- [] byte ( " \n " ) w.Header().Set( "Content-Type" , "application/json" ) w.WriteHeader(http.StatusAccepted) _, _ = w.Write([] byte (http.StatusText(http.StatusAccepted))) }) こちらはシンプルで、クライアントに通知したメッセージをもとに sessionId が送られてくるため、そこからセッションを取得し、送られてきたペイロードをそのまま s.requestQueue に流しています。 前述の通り s.requestQueue に送信されたペイロードはセッションごとに起動しているコマンドの標準入力に書き込まれます。 ここまでのシンプルな実装で、stdio Transportのみに対応したMCPサーバを無理やりSSE Transportに対応させることができました。 後はこんな感じで使えばよさそうです。 FROM ghcr.io/lifull/keel/mcp-stdio-proxy:main AS mcp-stdio-proxy FROM ghcr.io/github/github-mcp-server@sha256:fdf04e33b437c523d2f091b7bf8dc3724c88dbf9913d6568f12f0fcf48aaff95 COPY --link --from=mcp-stdio-proxy /usr/local/bin/mcp-stdio-proxy /usr/local/bin/mcp-stdio-proxy ENTRYPOINT ["/usr/local/bin/mcp-stdio-proxy", "/server/github-mcp-server", "stdio"] まとめ このようにLIFULLでは比較的安全にMCPサーバを動かしています。 過渡期ゆえの混沌のような気もしますが、ほどほどのハックで秩序を手に入れられた気がします。 SSE Transportには負荷分散がしづらいという問題があり、それを受けてStreamable HTTP Transportが開発されるなど、MCPはまだ進化の途上です。 セキュリティに気を配りながらこれからもMCPを使っていきたいです。 コミュニティの実装も大分増えてきていて、我々が開発する無限にスケールする汎用AI(仮)にもMCPクライアントを実装して、Function CallingとMCPのエコシステムを統合したマルチエージェントで真の汎用AIを目指しています。 プラットフォーマーとしてのLLMにまつわる活動に興味がある方がいれば、是非カジュアル面談させてください! hrmos.co
アバター
グループデータ本部データサイエンスグループの嶋村です。 今回は、弊社が運営する不動産・住宅情報サービス「LIFULL HOME'S」において、 数理最適化技術を適用 して事業指標の向上に挑んだプロジェクトについて紹介します。どのようにプロジェクトを推進したのかや、推進する過程で得た知見についても、お伝えしたいと思います。 プロジェクトの概要 フェーズ1「企画段階」での取り組み フェーズ2「データ準備段階」での取り組み フェーズ3「モデル開発段階」での取り組み フェーズ4「効果検証段階」での取り組み おわりに プロジェクトの概要 LIFULL HOME'Sでは、不動産会社から物件広告をお預かりし、物件を探すエンドユーザへ届けるサービスを展開しております。不動産会社は自社の物件広告を多くのユーザに閲覧してもらいたいと希望する一方、エンドユーザは自分にぴったりでより魅力的な物件を見つけたいという双方の要望があります。そこで双方の視点を考慮し、 全体として最適な広告表示の制御を実現 するべく、数理最適化モデルを適用するという挑戦をしました。 本プロジェクトでは 事業部と研究開発組織であるデータサイエンスグループが連携する取り組み となり、私はプロジェクトマネージャとして全体を推進する役割を担いました。プロジェクトの進め方として、 データサイエンティスト協会 および 情報処理推進機構(IPA) が公開しているデータサイエンス領域に関するタスク構造図(プロジェクトフロー)を参考にしました。以下の図はそのタスク構造図をベースに自分なりに解釈して作成した AIプロジェクトフロー になります。 プロジェクトを推進する上では、取り組むべきことが多岐にわたり、関係者の数も多いことが、難しさの一つでもありました。特に、社内でAI関係のプロジェクトに関する知見やノウハウが豊富に蓄積されている訳ではないため、どのようにプロジェクトを進めれば良いのかの 進め方のイメージが関係者でバラバラ でもありました。そのため、誰がどのような役割をどの段階で遂行すれば成功するのかを共有するために、下記に示すように チーム体制図や期待する役割を図で可視化 をして関係者に共有をしました。 ここからは4つの各フェーズでの取り組みについて、どのように進めたのか、どのような難しさがあったのか、どのように乗り越えたのか、をご紹介していきます。なお、本来はフェーズ1から順番にフェーズ4まで進めるのが想定の流れではありますが、プロジェクトの目標期日の関係で各フェーズで独立に進められるところを見つけ、可能な限り並行して進める工夫をしました。 フェーズ1「企画段階」での取り組み まず、最適化対象となった箇所において、 どのような課題感があるのか事業部へのヒアリング から始まりました。ここでは過去データからの事実や考察(推察)など客観的な情報と主観的な情報が混ざるため、それらを整理していきました。そして、ヒアリングした情報から実際にデータを確認して、どのような状況になっており、どのような課題があるのか特定をしていきました。 そして、課題に関連する指標を向上させるために、何が必要なのか要素を分解していきました。最適化をしたとして、 どのようなメカニズム で指標が向上するのか、といったモデル化をしました。ここでのモデル化は、フェーズ4における評価方法にも関係しますし、そもそもフェーズ3でどのような最適化モデルを作成するのか、そのためにフェーズ2でどのようなデータが必要なのか、と全てのフェーズに関連するため、 非常に重要なプロセス と捉えています。 次に、どのような最適化であれば許容されるのか、最大化したい目的関数や制約条件に関して合意形成に取り組みました。なお、この段階ではあまり専門用語を使うと会話のハードルを引き上げてしまうため、可能な限り数理最適化を意識させるような 専門用語は使わないように留意 しました。本プロジェクトは関係者が多く、色々な思惑があるため、この部分が かなり苦労したプロセス になりました。また、掘り下げてヒアリングをしていくと、時には具体的な要望を引き出せる一方で強い制約にもなってしまうため、本当に気にしているポイントはどこなのか、どのような世界観を実現したいのかを重視してヒアリングするように努めました。その意志を尊重し、最適化問題として定式化した上で、その目指したい方向性に近づけるための最適化であることを 丁寧に粘り強く説明 を重ねました。 最後に、どのように最適化を適用するのか、 データフローやシステム全体の概要を整理 しました。下図はその一例です。なお、プロジェクトを推進するにあたって作成した図は、関係者とのコミュニケーションをする上で終始使うことが多かったです。関係者が多い中で、一丸となって推進するためには、全員で共通のゴールイメージを持ち、そのゴールに辿り着くために何が必要か共通のイメージを持てることが重要です。図を描くということは、時間を要する一方で、自分自身の思考整理に役立ちますし、共通認識を醸成するのにすごく有効な手段であると考えており、私は重要視しています。 フェーズ2「データ準備段階」での取り組み フェーズ1で検討した施策内容に基づき、フェーズ2では後段のフェーズで必要なデータを準備します。単に「データ」と呼ぶだけだと何を指すのか、 人それぞれ「データ」の解釈が異なる こともあるため、どのような種類のデータがあるのかという整理が重要なプロセスでした。下図に整理した結果を示しておりますが、大別すると4種類のデータがあり、それぞれ誰が作るのか、誰が扱うのか、が異なります。これらを意識せず「データ」とだけ表現してしまうと混乱を招くため、この整理をしたことで認識齟齬の発生を防ぐことができました。 フェーズ2では、主にデータソースからデータマートを作成しました。まず、データマートとして何が必要なのか要件を整理した上で、データソースとして何を使うのか調査検討をしていきました。そして、データマートとして加工していくためのパイプライン処理を実装していきました。様々なデータソースからデータマートが作成されるため、タイムスケジューリングも重要でした。最適化の実行タイミングは早ければ早いほど良かったため、何時にどのデータが揃い、どの時点でデータ作成ジョブを実行すれば、何時にデータマートが完成するのか、関係部署との調整をしていきました。 また、データソースの特徴を理解していくにつれて、 イレギュラーなデータが存在 することもわかってきました。たとえば、欠損値や、暫定的に入力された仮の値が入っている、人間が見ると解釈できるものの意図しない箇所に値が入っている、などがありました。しかし、そのままでは最適化ができなくなってしまうため、どのような値で補完すれば最適化をする上で問題がないか、最適化側の要件を踏まえて対応策を練りました。 結果的に、フェーズ2はプロジェクト全体の中でも 時間を要したフェーズ となりました。改めて、データを活用しやすい形かつ正しく形で維持するための、データ整備の重要性を実感しました。 フェーズ3「モデル開発段階」での取り組み フェーズ3では、フェーズ1で決定した目的や要件、フェーズ2で準備したデータをもとに、 数理最適化モデルの作成 に取り組みました。下図は数理最適化処理の全体の流れを表しており、大別すると4段階の処理がありました。 この流れの中で、③の数理最適化はもちろん重要なのですが、その 成否を分けるのは①の定数推定 でした。①の定数推定が誤っていると、どのように最適化をしたとしても、結果的に誤った制御になってしまうためです。①では、推定に用いるデータはどのような特性があるのか、探索的データ分析(EDA)から始めました。何をするにしても「 まずはデータの特性を理解する 」というのは基本中の基本です。その上で、どのような特徴量を作り、どの程度の推定精度になるのか、評価実験を重ねました。また、時系列で見た時に値が0が多いが突発的に値が上昇する指標については、値自体を推定が難しく、0か非0なのかを推定する問題にするなど工夫も重ねました。 ②の前処理は現実時間で③の数理最適化を実行するための工夫になります。愚直に解こうとすると、 現実的な時間で求解 できず、最適化結果を制御に使うことができない状況でした。そのため、問題の分割や、数理最適化を適用しなくても良い領域を定義するなど、 計算量を削減する工夫 をしました。 ③の数理最適化では、ビジネス的な制約(要望)を考慮した上で、定式化に取り組みました。定式化をする上では、非線形性をどのようになくしていくか、求解可能性をどう担保するのか、という観点で苦労をしました。さらに、事業構造を理解した上で、どのように定式化するか、 数理モデリングの難しさ を特に感じました。数理モデリングというのは 具体的な事象を抽象的に単純化して表現 するので、「本当にこれで適しているだろうか」という不安がつきまといました。定式化の検討をしては、事業部側の意向を考慮できているか、意図した最適化になるかの確認をし、 喧々諤々と議論 を重ねました。 ④のオフライン評価では、 フェーズ4で本番適用をして良いのかを判断 するための重要な段階でした。仮定を設定し、過去実績を用いたシミュレーションにより、オフライン評価をしました。どの程度の効果があるのかを事前検証するために、 従来手法と今回の最適化手法の比較評価 をしました。オフライン評価を通じて、想定通りの制御になっているか、どの程度の機会効果が見込めそうかを改めて確認しました。③で定式化をした時と想定通りの評価結果になった時はとても心地良さを感じ、チーム内で喜びを分かち合い、これで本番のオンライン評価でもいけると確信を得ました。 フェーズ4「効果検証段階」での取り組み フェーズ4では効果検証をするために、まずは 数理最適化システムのプロトタイプ実装 をし、その上で 評価実験 をしました。 フェーズ3で作成したのは数理最適化処理のコアな部分だったため、それをシステムとして動作させるための環境整備や運用体制の整備をしました。また、どのような異常が発生しうるか、その際にどのように対処するかも、整理していきました。 弊社では施策評価をA/Bテストで評価しており、今回もA/Bテストをしました。向上させたい指標(Goal metrics)と、低下を防ぎたい指標(Guardrail metrics)を決め、さらに意図した制御をしているか確認するための指標(Other metrics)を定義しました。今回の評価実験では下図に示すように、既存手法であるAと最適化手法であるBで干渉し合うという性質がありました。そのため、 A/Bテストだけでは判断がつかない可能性 があったため、Other metricsを注視すると事業部側と事前に合意形成をしました。 A/Bテストを通じて、 意図通りの制御になっていることが確認 でき、数理最適化システムは本番適用されることになりました。一方、当初の想定通り、A/Bテストでは明確にGoal metricsで差が出ず、「良さそうだが本当に効果があったのか」と はっきりしない状態 でもありました。 そこで、 A/Bテスト適用前と適用後の前後比較 をすることで、効果の推計に挑みました。A/Bテスト適用前と適用後では、外部環境も異なることになるため、単純な比較では正しく推計ができません。そのため、過去の実績データがA/Bテスト適用前と並行トレンドであることを仮定した上で、 差分の差分法を用いて効果推計 をしました。 前後比較による効果推計の結果、上図のイメージ図のように、向上させたい指標で明確な差が生じることを示すことができました。 おわりに 今回の記事では、 数理最適化 というアプローチを用いて、多くの関係者の要望や運用上の制約を考慮した 全体最適制御に挑戦 した取り組みを紹介しました。多様なニーズに応えることができる画期的な数理最適化システムを実現できたと自負しております。数理最適化は今後のLIFULL HOME'Sにおけるサービス改善の基盤となる技術であることを示すことができ、データサイエンスを活用した 新たな技術応用の可能性を示唆 する結果となりました。 また、プロジェクトを成功に導くにあたり、どのようなプロセスでプロジェクトを推進してきたのか、についても紹介をしました。各フェーズでどのように 創意工夫 をしてきたのか、少しでも読者のみなさまの参考になると嬉しいです。 引き続き、数理最適化をはじめとする有用な技術の開発や活用をしていき、より魅力的なLIFULL HOME'Sの実現に向けて邁進していきたいです。今後の取り組みも発信していきたいと思いますので、是非楽しみにしていて下さい。 最後になりますが、データサイエンスグループでは「活用価値のあるデータを創出」し「データを活用した新たな機能やサービスの研究開発」を加速して下さる シニアデータサイエンティスト(R&D) を募集しています。 興味を持っていただけた方は、 カジュアル面談 も行っていますのでお気軽にご連絡ください。 hrmos.co
アバター
エンジニアの志賀と申します。 LIFULL HOME'S の新築分譲マンション領域の開発を担当しています。 私の所属している開発チームでは、LIFULLのベトナム海外拠点であるLIFULL Tech Vietnam(以下LFTV)と協力しながら開発を行っています。 過去LFTVのエンジニアメンバーと仕事を進める中で、ブリッジエンジニアを通していました。 そのため開発者どうしはお互いをほとんど知らず、コミュニケーションもレビューのやりとり程度で、コミュニケーション量が不足していると感じていました。 相手のことを知る、理解しようとすることは円滑に業務を進めるための第一歩だと考えていたので、この課題を解消するために行った取り組みを2点紹介します。 英語での自己紹介会の実施 まず実施した取り組みは、開発メンバーどうしの英語による自己紹介会です。 あらかじめフォーマットを用意し、英語で事前に記入したシートを用い各自に発表してもらいました。 フォーマットとして、私たちのチームでは以下の項目を取り入れました。 自分の顔がわかる写真とアイコン ニックネーム、呼んでほしい呼称 趣味 経歴 得意なこと 仕事のやり方 大切にしている価値観 チームメンバーは自分にどんな成果を期待していると思うか? 自由記述 内容はチームによってカスタマイズして良いと思いますが、取り入れて特に良かった内容を紹介します。 お勧めの自己紹介項目 自分の顔がわかる写真とアイコン 直接会う機会がない場合、チャットツール等のアイコンと実際の顔が一致しないということは、よくあることだと思います。 顔がわかることで、コミュニケーションを取る際の安心感が変わってくると考えているので、この情報は可能であれば必ず入れたいポイントになります。 ニックネーム、呼んでほしい呼称 ニックネームで呼び合えると距離がぐっと近くなりますし、自己紹介中にもコメント等でニックネームを投稿することで話しやすい空気を作りやすかったため、この項目はとてもお勧めです。 大切にしている価値観 仕事を進めていく上で相手が大事にしているものを知ることは、コミュニケーションを取る際にも非常に重要だと思います。 この項目があることで、今後のやりとりを円滑に進める手助けになるかと思います。 また自己紹介会の進め方とお勧めの2つのTipsがあるので、そちらも紹介します。 自己紹介会のTips 画面共有時に一工夫する 自己紹介会をする上でちょっとしたTipsとして、自己紹介をする人は必ず自己紹介用のフォーマットを画面共有し、今話している部分を選択等でハイライトすることをお勧めします。 英語が得意ではないメンバーが多い場合話し手、聞き手両方とも慣れていないため、今話している部分が追いやすいようにしてあげるだけでも内容に集中できるかと思います。 相手の母国語を使ってみる 自己紹介中に、挨拶等相手の母国語を含めてコミュニケーションをとると、お互いの距離感が縮まりやすかったです。 私たちのチームではベトナム後を母国語としているメンバーだったので、以下のような単語を織り交ぜることができます。 おはよう:Chào buổi sáng(チャーオブオイサーン) こんにちは:Chào buổi trưa(チャーオブオイチューア) こんばんは:Chào buổi tối(チャーオブオイトーイ) お元気ですか:Bạn khỏe không(バン コエ コンプ) ありがとう:Cảm ơn(カムゥン) 朝会用のスプレッドシート改善 私の所属している開発チームの朝会では、以下のような流れで実施しています。 アイスブレ−ク タスクの共有 目標の数値の確認 共有事項 上記の中でタスクの共有をメインに実施しているのですが、これまではLFTV側のメンバーはブリッジエンジニアのみが参加し、LFTV側のエンジニアの状況を共有する形で進めていました。 そこでブリッジエンジニアなしでも、LFTV側のエンジニアが朝会に参加できるように、朝会でタスク共有する際に利用していた、Googleスプレッドシートに一工夫することで実現しました。 GOOGLETRANSLATEの利用 やったことはシンプルで、「GOOGLETRANSLATE」を導入したことです。 今までは事前に日本語でスプレッドシートに各自タスクを記入していたのですが、その隣のセルに「GOOGLETRANSLATE」を利用して日本語を英語とベトナム語で翻訳したものを表示するように変更しました。 そうすることで、記入自体は今までと同様のコストでブリッジエンジニアの助けなしでタスク共有が可能になりました。 具体的に私のチームでは以下のように関数を利用しています。 = if ( セル番号 ="" , " ※左の列をベトナム語と英語に翻訳する数式アリ " ,GOOGLETRANSLATE ( GOOGLETRANSLATE ( セル番号, " ja " , " en " ) , " en " , " vi " ) & CHAR ( 10 ) & " --------------------- " & CHAR ( 10 ) & GOOGLETRANSLATE ( S50, " ja " , " en " )) 上記以外に工夫した点として、アイスブレークの内容やシートに記載されているタイトル等にも、同様に「GOOGLETRANSLATE」を適用されるようにしたことです。 アイスブレークの内容自体は必ず伝えなければいけない内容ではないと思います。 ただ、逆の立場で考えた時に不慣れな言語で会話が成り立っていると疎外感につながってしまうと考え、少しでも話題を共有できるようにアイスブレークの内容も極力共有できるようにしています。 また、細かい部分ではありますがシート内のタイトル等も翻訳されるようになっており、どこを見ても複数の言語で確認できる状態になっていることで言語の壁を感じないような環境を目指しています。 実施したことは非常にシンプルですが、ツールをうまく活用することで、以前よりも言語の壁を感じることなくコミュニケーションをとることができるようになりました。 最後に 私たちの開発チームで上記を実施することで、海外拠点のメンバーと毎朝顔を合わせてコミュニケーションをとることができています。 まだまだコミュニケーションをとる上での課題はありますが、第一歩は踏み出せていると思うので、引き続き改善をしていこうしています。 最後に、LIFULL ではともに成長していける仲間を募集しています。 海外拠点のメンバーと仕事ができる、貴重な環境でもありますので、興味がある方はよろしければこちらのページもご覧ください。 hrmos.co hrmos.co
アバター