Findy Toolsのデータ基盤を1ヶ月前倒しで新規構築した話

はじめに

この記事はFindy Advent Calendar 2024 21日目の記事です。

adventar.org

データソリューションチーム、エンジニアの土屋(@shunsock)です。本日は、Findy Toolsのデータ基盤を構築したので、その内容を共有します。

Findy Toolsは、2024年1月23日にリリースされた開発ツールのレビューサイトです。利用者は開発者向けツールのレビューやアーキテクチャの記事を閲覧、投稿できます。

Findy Toolsのデータ基盤のシステム設計の紹介

システム設計の目標、要件

今回の構築では、「Findy Toolsを訪れたユーザーの行動ログとデモグラフィックデータを組み合わせて分析可能にする」という目標がありました。Findy Toolsではユーザーの行動ログにユーザーidやリファラーを保存しています。また、ユーザー情報をはじめとしたデータを保存するデータベースがあります。今回は、これらを繋ぎ合わせた分析を可能にすることが目標です。

この目標を分解していきます。

まず、分析可能にするためには、分析対象であるデータが保存されている必要があります。行動ログとデモグラフィックデータは別々の場所にあるので、それぞれを集める必要があります。

また、データ分析をする主体はビジネスサイドの方で、BIを通じてデータを閲覧します。SQLのロジックついてはデータエンジニアが管理します。従って、データの加工が必要です。さらに、日々データは更新されます。よって加工されたデータも更新する必要があります。

以上をまとめると次のシステムが必要であることがわかります。

  • 分析対象のデータを一定期間で取得する処理
  • 取得したデータを一定期間で加工する処理

システムには既存のリソースなどの前提や組織方針などの制約があります。ファインディの場合、ウェブアプリケーションがAWS、データ基盤・機械学習基盤をGoogle Cloudでそれぞれ稼働しています。今回扱う行動ログデータとデモグラフィックデータはそれぞれ、ECSとRDS(Aurora MySQL)にありました。また、データウェアハウスはBigQueryであることが必須要件でした。これは、社内の分析担当者がBigQueryのUI/UXに慣れていることに起因します。最後に同期の期間ですが、即時性を求められるデータは今のところないため、1日ごとのバッチ処理でデータを転送することにしました。

以上の前提と制約からシステムの要件が次であるとわかります。

  • データの転送処理
    • 行動ログの転送: ECSのコンテナに出力されている行動ログをBigQueryに1日ごと転送 ※
    • データベースの転送: RDSのデモグラフィックデータをBigQueryを1日ごとに転送
  • データの整形
    • BigQueryに保存されたデータを1日ごとに加工処理を実行

※ 実際には、これより短い間隔で送信されるシステムになりました。

システム設計図

次の写真がFindy Toolsのデータ基盤のシステム設計図です。

Findy Toolsのデータ基盤

データの取得、加工の仕組み

データの取得

はじめに、ユーザーの行動ログの転送方法を説明します。Findy Toolsでは、ユーザーの行動ログを次のように取得しています。

  1. Next.js(フロントエンド)からRails API(バックエンド)にHttp Requestを送信
  2. Rails APIがログを出力

よって、このログをBigQueryに転送する手法を検討しました。工数がかからないことと送信に必要な追加費用がBigQuery APIのみであることからfluentbitを採用しました。fluentbitは、ログを収集し、閾値やバッファの条件に応じて送信するソフトウェアです。

次に、データベースの転送方法を説明します。Findy Toolsの場合、データベースとしてAWS Aurora MySQLを採用しています。

Aurora MySQLからBigQueryに送信する手法はいくつかありました。

方法 構築難易度 コスト メリット 採用判断 理由
Private Linkを使う セキュリティ強化、直接送信可能 不採用 今回のケースでは受けられる恩恵が小さい。
Private SubnetにECSを立てる シンプル構成、コスト効率が良い 採用 実現可能性が高く、コスト効率が良い。
3rd Party ETLツールを使う 導入が楽 不採用 費用が高い。

Private Linkを使う方法は構築難易度が高いものの、今回のケースでは受けられる恩恵が小さいことから採用しませんでした。また、3rd PartyのETLツールを使う方法は相当な費用が必要と判明し、断念しました。よって、Private SubnetにECSを立てて送信する方法を採用しました。

ECS上でデータの転送するソフトウェアは社内で実績のあったEmbulkを採用しました。

今回の構築では次のような手順でデータを送信します。

  1. CloudWatch Eventsが設定した時刻にECSを起動
  2. ECSがEmbulkを起動
  3. EmbulkがAurora MySQLからデータを取得
  4. EmbulkがBigQueryにデータを送信

データの加工

データの加工にはdbtを採用しました。dbtは、SQLを使ってデータを加工するため、データエンジニアがSQLを書くことができれば、簡単にデータを加工できます。似たツールで非エンジニアでも触りやすいGUIを持つDataformがありますが、今回はGit操作になれているメンバーが触ることを考慮してdbtを採用しました。

dbtの実行はGitHub Actions上で行っています。具体的な手順は次の通りです。

  1. GitHub ActionsのScheduleによってワークフローが発火
  2. dbtがlake層に入ったデータを加工

エラー処理、アラートの仕組み

今回作成したインフラストラクチャでは、次のようにエラー・アラートを通知しています。

Google Cloud, AWSのエラー通知の仕組み

行動ログの転送のエラー処理

行動ログの転送では、エラーの発生箇所がいくつか考えられます。

  • Next.jsからRails APIへのHttp Requestの失敗
  • Rails APIのログ出力の失敗
  • fluentbitの停止・暴走によるログ転送の失敗
  • BigQueryへの送信の失敗

今回データソリューションチームでは、BigQueryへの送信の失敗の監視を担当することになりました。(他はウェブアプリケーションの開発チームが担当)BigQueryへの送信の失敗は、Cloud Loggingに出力されるBigQuery APIの応答を監視することで実現できます。

  1. Cloud LoggingにBigQuery APIのログが出力される
  2. Cloud Monitoringでエラーのログを検知する
  3. Slackに通知を送る

データベースの転送のエラー処理

データベースの転送では、エラーの発生箇所がいくつか考えられます。

  • Aurora MySQLからデータを取得する際のエラー
  • BigQueryにデータを送信する際のエラー

これらのエラーが発生した場合、AWS ECSのログにエラーが出力されます。そのため、CloudWatch Logsに出力されるログを監視することでエラーを検知し、Slackに通知を送ることができます。

具体的には、次の手順でアラートを設定しました。

  1. CloudWatch Logsに出力されたログをCloud Watch Metrics Filterでエラー検出
  2. CloudWatch Alarmでアラートを発火
  3. SNS・Chatbot経由でSlackに通知

この手法の場合、全ての実装をTerraformで管理できるため、運用が楽です。他の手法として、CloudWatch Logsに出力されたログをLambdaでSNSに送信するという方法もあります。しかし、この方法は、Lambdaのソースコードの管理(GoやPythonなど)が必要になるため、運用が複雑になります。よって、今回はTerraformで管理する方法を採用しました。

インフラストラクチャの管理

Terraformによるリソース管理の仕組み

Findy Toolsのデータ基盤ではTerraformを用いてリソースを管理しています。 各環境は、environmentsディレクトリに分割しています。Findy Toolsのデータ基盤では、productionstagingの2つの環境を管理しています。

Terraformのワークスペースは、機能ごとに分割しています。例えば、Embulkのバッチ処理に関連するリソースは次のように管理しています。

.
├── environments
│   ├── production
│   │   └── embulk
│   └── staging
│       └── embulk
└── modules
    └── embulk

このように管理することで、機能内の変更(ここではembulk ワークスペース)が他の機能(他のワークスペース)に直接影響を与えないため、運用が安定します。つまり、機能の変更や破棄が容易になります。

一方で、共通のリソースが必要になることがあります。その場合は、プロバイダのモジュールを作成して、共通のリソースを管理します。例えば、Google Cloudのリソースは次のように管理しています。

.
├── environments
│   ├── production
│   │   ├── embulk
│   │   └── google
│   └── staging
│       ├── embulk
│       └── google
└── modules
    ├── embulk
    └── google

ここで作成したリソースは複数のワークスペースで共有できるため、運用には注意が必要です。

uvによるPythonパッケージ管理の仕組み

今回の構築では、データ加工ツールとしてdbt(Data Build Tool)を採用しました。dbtはETLツールのTransformationの部分を担当します。

dbt-coreはPythonで書かれており、周辺ツールもPythonのエコシステムで提供されています。そのため、Pythonのパッケージ管理ツールの選定が必要でした。

今回は、Pythonのパッケージ管理ツールであるuvを採用しました。Pythonのパッケージ管理をする方法はvirtual envPyEnv + PoetryDockerなどいくつか選択肢があります。uvを利用するとPythonのインタープリタとパッケージを1つのツールで管理できるので、環境構築が非常に楽です。

前述したものの中だとDockerもインタープリタとパッケージを1つのツールで管理可能です。今回は、Dockerfileのメンテナンスが必要であること、やりたいことがシンプルであることから、採用を見送りました。

$ # たったこれだけで環境構築ができる🎉
$ curl -LsSf https://astral.sh/uv/0.5.7/install.sh | sh
$ uv python install 3.12.6
$ uv python pin 3.12.6
$ uv sync

開発時に発生した課題と解決法

EmbulkのDockerをローカルで動かすことが難しい

EmbulkのリポジトリではDocker Containerを管理しています。開発当初、AWSのPrivate Subnetと通信する必要がありローカルで動かすことが難しいという課題がありました。

すぐに思いつく対策として、Docker Composeを使ってDBのMockを立ち上げる手が考えられます。しかし、今回はあえてローカル環境を作らない選択をしました。ローカル環境を作らないことにより、その分の開発工数を削減できます。

前述の選択により基本的にステージング環境で管理することになりました。ここで課題になるのが、ステージング環境のECRやデプロイやECSの実行に手間がかかることです。そこでコマンドを作成し、デプロイと実行に手間がかからないようにしました。

ステージング環境へのデプロイは次のコマンドで実行します。

$ ./command/push_image_to_ecr staging
[NOTICE] 🚀 ステージング環境にDockerイメージをpushします
[NOTICE] 🚪 ECRとDockerにログインします
Login Succeeded
[NOTICE] ✅ ECR にログインしました
[NOTICE] 🛠 Dockerイメージを作成します
[+] Building 1.4s (19/19) FINISHED
...
[NOTICE] ✅ Dockerイメージの作成が完了しました
[NOTICE] 🏷 イメージにタグを付与します
[NOTICE] ✅ イメージにタグの付与が完了しました
[NOTICE] 🚀 ECRにDockerイメージをpushします
...
[NOTICE] ✅ ECR へのDockerイメージのpushが完了しました
[NOTICE] 🎉 DockerイメージのビルドとECRへのpushが完了しました

ステージング環境での実行は次のコマンドで実行します。

$ ./command/run_embulk_on_ecs
[NOTICE] 🚀 EmbulkのECSタスクを起動します
{
    "tasks": [ ... ]
}
[NOTICE] ✅ EmbulkのECSタスクの起動に成功しました
[NOTICE] 🎉 EmbulkのECSタスクの起動が完了しました

また、Embulkで新しいテーブルを処理するためにはliquid.ymlファイルを新たに作成する必要があります。こちらも手間を省くために独自コマンドを作成しています。

$ ./command/generate_conf new_table
[NOTICE] 🔍 テンプレートファイルと出力先ディレクトリの存在確認
[NOTICE] ✅ テンプレートファイルと出力先ディレクトリの存在確認が完了しました
[NOTICE] 🚀 Embulkの設定ファイルを生成しています: new_table
[NOTICE] ✅ Embulkの設定ファイルの生成が完了しました: new_table
[NOTICE] 🎉 Embulkの設定ファイルを生成しました。ファイル名: new_table.liquid.yml

本番の手動デプロイも時間がかかります。Embulkのリポジトリではデプロイを自動化しています。

  • main branchにPull Requestを作成した時点で、stgへのデプロイ
  • main branchにMergeした時点で、本番環境にデプロイ

uvとdbtを組み合わせるとコマンドが長く入力の負担が大きい

uvは非常に便利です。しかし、dbtを組み合わせて使うとコマンドが長くなる問題がありました。

# コマンドの全体像
# 実際の開発の時は、modelを指定するので、これよりも長くなります
$ uv run dbt debug --profile *** --target dev --project-dir ./tools

そこで、Shell Script, Taskfileを使ってラッパーを作成し、コマンドを短くしました。

# コマンドの全体像
# 実際の開発の時は、modelを指定するので、これよりも長くなります
$ uv run dbt debug --profile *** --target dev --project-dir ./tools

# Shell Scriptとして実行
# 使い慣れている方が多い、Shell Scriptでラッパーを作成
# 少し短くなります🙌
$ bash scripts/dbt_wrapper.sh debug

# Go Taskで実行
# 元のコマンドに比べ、半分以下にすることができました🎉
$ task dbt -- debug

開発を振り返って

今回の取り組みでは、持続的開発が可能なデータ基盤とその開発環境を作成できました。また、当初の見積もりよりも一ヶ月早く実装できました。

今回のデータ基盤構築プロジェクトを振り返って、プロジェクト成功の鍵がいくつかあったと考えています。

実績のあるツールの活用による開発の効率化

構築にあたって、既存の知見が多いツールや実績のある技術を積極的に採用しました。それぞれが成熟したエコシステムを持っているため、課題が発生した際に素早く調査・解決できたのが大きな利点でした。

社内エンジニアとの連携

データ基盤構築は多くの技術やチームが関わるため、横のつながりを活用することが非常に重要でした。ファインディは横のつながりが強く、相談しやすい環境です。初見の技術で課題に直面した際、SREチームやデータソリューションチームのメンバーに相談することで、迅速に解決できました。特にTerraformやAWSの運用においては、社内のエンジニアの知見を活用したことで、手戻りや長時間の調査を防ぐことができました。

ラッパーや便利ツールの作成

ラッパーやコマンドの短縮化により、日常的な作業の効率が大幅に向上しました。日常の作業効率を上げた分、アーキテクチャの議論や意思決定に時間を割くことができたので、これらのツールに投資した時間は、長期的に見ると価値のあるものでした。

学びと次への展望

データ基盤のような横断的なプロジェクトでは、他チームとの連携やツールの選定が効率性に寄与すると学びました。また、技術としては、AWSの通知システムの構築手法やEmbulkの実用上の課題を認知したことが学びでした。特に、Embulkの起動時間が長いことは想定外でした。1 今後、転送するテーブル数が増えた場合は、ECSの並列化などの高速化をする予定です。

今後は作成したデータ基盤で、データの利活用を進めていく所存です。

今回の取り組みが、他のエンジニアにとっても何かしらのヒントとなれば幸いです。


ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers


  1. 調査により、Embulkの起動時間が原因であると判明しています。具体的には、JVMとJVM上のRubyランタイムの初期化の時間でした。なお、Embulk本体はJavaではありますが、プラグインはRubyで記述可能です。

2024 Accelerate State of DevOps Report 概説#2 『巨匠の不満から誕生した"LeanとDevOpsの科学"』

こんにちは。ソフトウェアプロセス改善コーチでFindy Tech Blog編集長の高橋(@Taka_bow)です。

さて、前回の続きです。

tech.findy.co.jp

順調に行っていたかに見えたState of Devops Reportですが、ここにきて大きな壁が立ちふさがります。それが、ソフトウェア界の巨匠Martin Fowler(マーティン・ファウラー)でした。

Martin Fowler
Martin Fowler(Image source: Wikimedia)

Martin Fowler氏とは

(良くご存じの方は飛ばしてください) Martin Fowlerを知らない方向けに簡単に紹介すると、ソフトウェア設計やアジャイル開発の分野で世界的に影響力を持つスーパーエンジニアです。「マイクロサービス」の提唱者であり、彼の著書『リファクタリング』は、コードの品質を向上させる方法論としてエンジニアのバイブルとなりました。

www.ohmsha.co.jp

個人的な印象は、最初は「UMLモデリング」に長けた方という印象だったのですが、徐々に「パターン」の話が多くなり、最終形態として「アジャイル」に話題が移っていった気がします。

また、この写真、エンジニアだったら見たことありますよね?

アジャイルマニフェスト(アジャイルソフトウェア開発宣言)の背景画になってるアレですね。 この、ホワイトボードを指さしている人物こそMartin Fowlerそのひとです。(と、言われています)

アジャイルマニフェストの署名者の一人であり、ブログや講演を通じてアジャイルを実践的に解説してくれる重要な立役者です。

巨匠の不満

さて、State of DevOps Reportの話に戻しましょう。Martin Fowler 本人によって、書籍「LeanとDevOpsの科学[Accelerate] 」*1の冒頭では、次のようなエピソードが寄稿されています。

本書に寄せて

2、3年前、あるレポートを読んでいたら、こんな文に出くわしたーー「今や我々は自信をもって断言できる。IT部門のパフォーマンスの高さには、生産性、収益性、そして市場占有率を高める効果があり、組織全体の業績と高い相関をもつ」。この手のレポートは即、ゴミ箱に投げ捨てるのが私の通常の反応である。大抵は「科学」を装ったたわごとにすぎないからだ。しかしそのとき読んでいたのは『2014 State of DevOps Report (DevOpsの現況に関するレポート2014年版)』であったため、私はためらった。著者の1人が私の同僚であり友人でもあるJez Humble氏で、私に負けず劣らずこの種のたわごとを嫌う人物であることを知っていたからだ(もっとも、正直言ってゴミ箱に投げ捨てなかった理由はもう1つある。あのレポートはiPadで読んでいたのだった)。

苛烈な文章です。紙だったら捨てられていたのでしょうか!Martinは、さっそくNicole ForsgrenとJez Humbleと3人で話す機会(電話会議)を作ります。

Forsgren氏は根気強く丁寧に研究の論拠を説明してくれた。その説明は、そういった調査・分析方法には詳しくない私にとっても十分な説得力があり、通常をはるかに上回るレベルの(学術論文で発表される研究さえ凌ぐ)厳密な分析が行われている、ということが理解できた。

実際に書籍が出版されるのは、この会議から4年後(2018年)になるのですが……2014年時点で、まずは一旦は納得したようです。

しかし……

そのため、私はその後もState of DevOpsレポートを興味深く読み続けていたが、その一方で不満も募ってきた。どの年度のレポートも研究の成果を公表するばかりで、Forsgren氏が電話で私にしてくれたような説明が一切ないのである。おかげでレポートの信頼性が大きく損なわれていた。推測だけに基づいた研究ではないことを示す根拠がほぼ皆無なのだ。

Martin Fowlerはレポートの方針に納得がいっていないようです。

そこで私も含めて内情を知る者が3人を説得し、研究の調査・分析手法を紹介・解説する本を執筆してもらった。

このように、Martin Fowlerの不満があったからこそ「LeanとDevOpsの科学[Accelerate] 」が存在すると言っても過言ではありません。

「LeanとDevOpsの科学[Accelerate] 」が技術者からすると慣れない文体で書かれているのは、あれが技術書ではなく「研究の調査・分析手法を紹介・解説する本」だからだと思われます。統計手法の解説本ですものね。

2015 State of Devops Report

Martin Fowlerから「根拠の提示がない」と言われた2015 State of DevOps Report ですが、前年と比べ分析に苦慮していた様子が伺えます。

まず、Change Failure Rate(変更失敗率)は、あいかわらずITパフォーマンスの主要な構成要素(construct)からは除外されています。レポートには具体的な事に触れられていませんでしたが、後日出版された「LeanとDevOpsの科学[Accelerate] 」には次のような記述がありました。

クラスター分析では「ハイパフォーマー」「ローパフォーマー」「ミディアムパフォーマー」のいずれの集団についても、この4つの尺度で有意な分類と差別化(チームのカテゴリー化)が行えた。ところが、この4つの尺度で1つの構成概念を得ようとすると問題が生じた。妥当性と信頼性を検証するための統計的仮説検定にパスしないのである。分析の結果、リードタイム、リリース頻度、サービス復旧までの所要時間の3つの尺度だけを使えば、妥当で信頼性のある構成概念が得られることが判明した。

ちょっと分かりにくい文章なので整理すると、ソフトウェアデリバリーのパフォーマンスを正確に測定するには、

  • 「リードタイム」「リリース頻度」「サービス復旧までの所要時間」の3つの尺度だけなら信頼性高く妥当な評価ができる。
  • 変更失敗率も重要な指標であり、これら3つの尺度と強い相関はある。が、独立した構成概念として扱うには適していない。

「変更失敗率」は予測しずらい異質な指標であり、分析や予測の際には補足的な要素として考慮する必要がありそうです。最新の2024 DORA Reportでも「変更失敗率」はイレギュラーな結果を残しています(別ブログで詳しく触れたいと思います)。

2015年のパフォーマンス・クラスターはSuper High、High、Medium、Lowに分けられ、それぞれ「4段階のスピード」で集計されました。

2015 State of DevOps Reportより

しかし、パフォーマンスの傾向を特定するまでには至っていません。かろうじて、高パフォーマンスチームと低パフォーマンスチームの間には、明らかに大きな差がある、ということを突き止めたに過ぎませんでした。

【高パフォーマンスチームと低パフォーマンスチーム間のITパフォーマンス指標の比較】

2015 (Super High vs. Low) 2014 (High vs. Low)
Deployment Frequency 30x 30x
Deployment Lead Time 200x 200x
Mean Time to Recover (MTTR) 168x 48x
Change Success Rate 60x 3x

DORAの設立へ

翌年2016年10月、Nicole ForsgrenとJez HumbleはDevOps Research and Assessment (DORA)を立ち上げます。これは、誰からの支援(投資)を受けずに立ち上げた会社だったそうです。そもそも、なぜ会社設立に至ったのか?

後日書かれたJez Humbleのブログをもとに解き明かしたいと思います。

medium.com

State of DevOps Reportの研究は地道に成果を上げ始め、その取り組みが実際のビジネス成果を改善することも証明し、多くの企業からデータを集めることにも成功していました。

一方で、組織が抱える2つの大きな問題も見えてきたそうです。その2つとは……

ビッグバン型変革の問題
多くの組織が陥った失敗パターンの代表例が「ビッグバン型変革」でした。これは、アジャイル手法と成熟度モデルを一度に導入しようとしたアプローチです。「どんな組織にも当てはまるテンプレート」として導入されたものの、実際の組織の状況とは相容れないことが多く、持続可能な改善にはなかなかつながりませんでした。
特に大きな問題は、現場で働く実務者の関与が不足していたことでした。日常業務の中で原則や実践を少しずつ試しながら改善していくプロセスが欠如しており、このことが変革の成功率を大きく下げる要因となっていました。

コンサルタントモデルの問題
多くの組織は改革のためにコンサルタントを採用し、現状分析と改善策の提案を求めました。しかし、このアプローチにも課題がありました。コンサルタントが話を聞けるのは、実際に作業を行っている現場の人々のほんの一握りに過ぎません。さらに、改善提案はコンサルタント個人の専門知識に大きく依存することになり、客観的に進捗状況を追跡することも難しくなりました。
実は、このやり方はコンサルティング会社にとっても理想的なビジネスモデルとは言えませんでした。なぜなら、優秀な人材を現場に配置しなければならないにもかかわらず、継続的な仕事が保証されているわけではなかったからです。また、このアプローチは規模の拡大も難しく、ビジネスとしての成長にも限界がありました。

これらの問題認識から、NicoleとJezは、アルゴリズムを活用して組織の改善領域を特定し、具体的な改善戦略を提案できるプラットフォームの必要性を認識しました。

特に注目すべきは、変革や改善を「どこから始めるべきか?」という組織からの本質的な問いに、データとアルゴリズムを用いて客観的に答えられる仕組みを作ろうとした点です。

この話題が出た時、Nicoleの目が輝きました。「ねえ、もしソフトウェアデリバリーのプロセス全体から何十人もの人々のデータがあれば、その質問に答えることができる。どのアルゴリズムを使うべきか、そしてそれをどのように修正すべきかも分かっている。これは制約理論(TOC)の問題に過ぎないのよ!最も賢く、効率的な方法で戦略を立てる方法を伝えることができるわ。」

私は一瞬考えて言いました。「待って、本当に?それならば作ってみよう!」そして、そこからDORAは誕生したのです。私たちは翌日からモックアップの作成に取り掛かりました。

Jez Humbleのブログから

2016年にDORAは設立されました。同年10月にはNicoleがフルタイムでの経営を開始し、DevOps Enterprise Summitでは最初の顧客となったCapital Oneとともに、DORAの正式な「お披露目」が行われています。

2016 - 2017 State of DevOps Report

かくして、Puppet + DORA の連名で発表された2016年と2017年のState of DevOps Reportは、制約理論をベースにしたアルゴリズムを活用し、数千の組織から得られたデータを統計的に分析することでDevOpsの実践と組織のパフォーマンスの関係性を客観的に実証しました。

【2016 パフォーマンス指標】

パフォーマンス指標 High Medium Low
Deployment Frequency オンデマンド(1日複数回) 週1回〜月1回 月1回〜6ヶ月に1回
Lead Time for Changes 1時間未満 1週間〜1ヶ月 1ヶ月〜6ヶ月
MTTR 1時間未満 1日未満 1日未満*
Change Failure Rate 0-15% 31-45% 16-30%

* ローパフォーマーは概して(統計的に有意なレベルで)成績が悪かったが、中央値はミディアムパフォーマーと変わらなかった。

【2017 パフォーマンス指標】

パフォーマンス指標 High Medium Low
Deployment Frequency オンデマンド(1日複数回) 週1回〜月1回 週1回〜月1回*
Lead Time for Changes 1時間未満 1週間〜1ヶ月 1週間〜1ヶ月*
MTTR 1時間未満 1日未満 1日〜1週間
Change Failure Rate 0-15% 0-15% 31-45%

* ローパフォーマーは概して(統計的に有意なレベルで)成績が悪かったが、中央値はミディアムパフォーマーと変わらなかった。

これらの結果を元に読み取った洞察が、あの有名な


「パフォーマンスの改善と、安定性と品質の向上との間に、トレードオフの関係はない」

というものです。

さらに、統計的に有意な形で改善できるケイパビリティ(組織全体やグループとして保持する機能や能力)を24個特定したのです。このケイパビリティモデルは、研究の進展とともに進化し続けています。

これこそが、DORA設立の背景にあった「どこから始めるべきか?」の答えでありFour Keysのパフォーマンス・スコアと、この相関図を見比べながら改革や改善の戦略を立てられるようしたことがDORA研究のもたらした最大の成果と言えます。

LeanとDevOpsの科学[Accelerate] 図A.1 本研究の全体の構成 を元に筆者が作成

また同じ年2016年に、Gene KimとJez Humbleは他の著者とともに"The DevOps Handbook: How to Create World-Class Agility, Reliability, & Security in Technology Organizations"(邦題:The DevOps ハンドブック 理論・原則・実践のすべて)を刊行します。 bookplus.nikkei.com

この本の内容は、DORAが提唱したケーパビリティモデルを補完する内容であり、DevOpsに必要な実践内容が書かれた本です。

2021年には2nd Editionが刊行されているのですが、残念ながら日本語訳はありません。

itrevolution.com

なお、2nd Edition ではDr. Nicole Forsgrenも新たに参加し、加筆しているという個人的には胸熱な展開になっています。

DORA激動の年

2018年は、DORAにとって激動の年でした。

まず、2018年3月27日、かねてより進めていた新しい書籍が発売となります。

Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations(邦題「LeanとDevOpsの科学」)です。

itrevolution.com

この本の出版でMartin Fowlerと約束を果たしたことになります。冒頭で述べた通り、この本はDORAの「研究の調査・分析手法を紹介・解説する本」として生まれました。

発行はIT Revolution Pressで、DORAの二人Nicole Forsgren、Jez Humble、そしてIT RevolutionのGene Kimの名前で発行されました。 ここにPuppet Labsのメンバーはいません。(謝辞の中には出てきますが)

そして「LeanとDevOpsの科学」が出版された翌月、書籍名(原題"Accelerate")を冠した

Accelerate State of DevOps Report

という、新しいプログラムを発表します。

これは、長年共に歩んできたPuppet社とのパートナーシップ解消を意味しました。DORAは、次のパートナーとしてGoogle Cloudを選択したのです。当時のインタビュー記事があります。

www.infoq.com

新しい Accelerate State of DevOps Reportは2018年8月29日にリリースされます。 筆者としてクレジットされたメンバーは3人だけでした。

  1. Nicole Forsgren PhD(ニコール・フォースグレン博士)
  2. Gene Kim(ジーン・キム)
  3. Jez Humble(ジェズ・ハンブル)

2018 Accelerate State of DevOps Reportの表紙

パートナーであるGoogle Cloudがダイヤモンドスポンサーであることはもとより、ゴールドスポンサーがAmazon Web Services、CA Technologies、CloudBees、Datical、Deloitte、Electric Cloud、GitLab、IT Revolution、Microsoft、PagerDuty、Pivotal、Redgate、Sumo Logic、Tricentis、XebiaLabsであり、非常に豪華です。

肝心の中身ですが、2018年のパフォーマンスの指標とレベルにはちょっとした変化が生じます。

それは、MTTRが"Time to restore service"という言い方に変わったことと、レベルに初めてEliteが登場したことです。

【2018 パフォーマンス指標】

パフォーマンス指標 Elite High Medium Low
Deployment Frequency オンデマンド(1日複数回) 1日1回〜週1回 週1回〜月1回 月1回〜6ヶ月に1回
Lead Time for Changes 1時間未満 1日〜1週間 1週間〜1ヶ月 1ヶ月〜6ヶ月
Time to restore service 1時間未満 1日未満 1日未満 1週間〜1ヶ月
Change Failure Rate 0-15% 0-15% 0-15% 46-60%

このように、リサーチは順調であったようですがDORAのCEO兼主任研究員であったDr. Nicole Forsgrenには大きな重圧がかかっていたようです。

この頃のことをJez Humbleはブログで次のように振り返っています。

経費を抑えた自力での会社運営には大きな代償がありました:それは燃え尽き症候群です。スタートアップは創業者を疲弊させることで有名ですが、私たちも例外ではありませんでした。特に最も大きな負担を背負っていたのはNicoleでした。CEOとして彼女は、会社の戦略、財務、そして事業運営全般に責任を負っていました。それだけではありません。CEOとして市場戦略の立案と実行をほぼ一人で担うだけでなく、State of DevOpsレポートの主任研究者、『Accelerate』の主執筆者、そして買収交渉の責任者も務めていました。彼女が言うように、これらの成果はすべて何年もかけて築き上げたものですが、驚くべき仕事の処理能力を持っていた彼女でさえ、膨大な時間の労働なしにはこれらを実現することはできなかったでしょう。これこそが投資を受けなかったことの代償でした - より大きなチームを雇って、この重荷を分散することができなかったのです。

Jez Humbleのブログから

2018年12月、Nicole ForsgrenはDORAをGoogleに売却する決断をしたのでした。

Puppet版State of DevOps Report

一方、Puppet社も2018年は独自のState of DevOps Reportを発行します。

しかし、これはDORA版とは異なるポリシーで編集されたものでした。2013年版に原点回帰するかの如く、DevOpsの進化の段階を特定し、DevOpsへの変革プロセスの定量化に舵を切っています。

Puppet版 2018 State of DevOps Reportについて語る人物は、あのAlanna Brownでした。

その後、Puppet社は、2022年4月にソフトウェア開発ツール大手Perforce Software, Inc. に買収されましたが、現在もState of DevOps Reportはシリーズを継続中であり、主にDevOpsに取り組む現場での実用性に重点を置いています。

2024 State of DevOps Report

GoogleになってからのState of DevOps Report

Googleの一員となったことで、安定した環境、持続可能な研究環境を手に入れたDORAは、途中COVID-19による2020 DORA Reportの中止もありましたが、晴れて今回で10冊10年目を迎えることができました。

ここでは2019年〜2023年までのトピックを駆け足で紹介します。

入れ替わる著者陣

2019年にGene Kim、2021年にはDr.Nicole ForsgrenとJez HumbleがDORAを去ります。現在は、他のDORAメンバーやGoogleのリサーチャーを中心に研究が引き継がれています。

Gene Kim は現在もIT Revolutionの代表であり、執筆活動や講演を精力的にこなしています。2019年に来日しており、DevOpsDays Tokyo 2019のキーノートセッションに登壇しています。(私は現地で拝聴できました!)

thinkit.co.jp

Jez Humble は現在もGoogleに席を置きSRE(Site Reliability Engineering)のエンジニアとして活躍する傍ら、カリフォルニア大学バークレー校(UC Berkeley)の教員も続けているようです。

www.ischool.berkeley.edu

Dr.Nicole Forsgrenは現在Microsoft Research のパートナーとして Developer Experience Lab を率いており、ACM Queue の取締役も務めています。最近は、科学を活用しソフトウェア開発者をより楽しくする研究を実践しています。DevExやSPACEフレームワークに関する研究論文を発表し、精力的に活動中です。

彼女の最近の論文について、以前解説ブログを書いたので貼っておきます。

tech.findy.co.jp

また、今年の6月に来日しファインディ主催の「開発生産性Conference 2024」にて基調講演をご担当頂きました。

Dr. Nicole Forsgren (開発生産性Conference 2024にて)

David Farleyの登場

2022年と2023年の2年間だけ、著名なエンジニアDavid(Dave) Farley(デイビッド(デイブ)・ファーリー)が関わりました。

https://www.infoq.com/images/profiles/d3g3wtOZD786r9wNCVgnorqQ8kZeZkz6.jpg?t=1734050734108
David(Dave) Farley(Image source: InfoQ.com)

彼は、Jez Humbleと共にベストセラー「Continuous Delivery」を書いた人物です。 www.kadokawa.co.jp

書籍「Modern Software Engineering」の筆者でもあり、文中、Dr. Nicole ForsgrenにまつわるエピソードやDORAの研究成果が引用されており、非常に参考になる本です。

bookplus.nikkei.com

Eliteレベルがまた消える

2022年の分析では、パフォーマンスレベルからEliteが消滅します。これは、最もパフォーマンスの高いクラスタが、2021年のEliteの特徴を示していなかったためと述べられています。翌年2023年は復活しました。

これは、クラスター分析の特性として仕方のない現象でもあります。クラスター分析は、データの分布やその年の回答者の特性に基づいて自然に分類を形成する手法であり、事前にクラスター数やその特徴を固定するものではありません。

そのため、特定のパフォーマンス層が少数であったり、他の層と重なりがある場合、ある年には明確なクラスターとして現れず、翌年に再び明確化することがあります。この柔軟性こそがクラスター分析の利点であり、データに忠実であることを意味しています。

Four Keysの定義が変更

2023年から、Four Key Metricsの一部名称と定義が変更され、より簡潔かつ実務的な表現に更新されました。次の表に、2022年までの定義と2023年の定義、および具体的な変更点をまとめています。

指標 2023年の定義 2022年までの定義 変更点
Deployment frequency (デプロイ頻度) 変更を本番環境に push する頻度。 主なアプリケーションまたはサービスで、組織が本番環境にコードをデプロイする頻度、またはエンドユーザーにリリースする頻度はどのくらいか。 定義が簡潔化され、本番環境への変更適用に焦点が絞られています。
Lead time for changes (変更のリードタイム) コードの変更を commit してからデプロイするまでの時間。 主なアプリケーションまたはサービスで、変更のリードタイム(commit されたコードが本番環境で正常に実行されるまでの時間)はどのくらいか。 定義が簡潔化され、コミットからデプロイまでの具体的な工程に限定。「本番環境で正常に実行される」という表現が省かれています。
Change failure rate (変更失敗率) デプロイにより障害が発生し、すぐに対処する必要が生じる頻度。 主なアプリケーションまたはサービスで、本番環境に変更を加えた、またはユーザーに変更をリリースしたとき、サービスの低下(サービス障害、サービスの停止など)が発生して、対策(修正プログラム、ロールバック、フィックス フォワード、パッチなど)が必要になった割合はどのくらいか。 「デプロイにより障害が発生し」という表現に変更され、障害の発生原因がデプロイに限定。具体的な対策例が削除されました。
Failed deployment recovery time (失敗デプロイの復旧時間)
(旧名称: Time to restore service (サービス復旧時間))
デプロイの失敗時に復旧にかかる時間。 主なアプリケーションまたはサービスで、サービスのインシデントや、ユーザーに影響を与える障害(計画外のサービス停止やサービス障害など)が発生した場合、サービスの復旧に通常どれくらいの時間がかかるか。 指標名が変更され、定義が「デプロイの失敗」に絞られました。例として挙げられていた「計画外のサービス停止やサービス障害」が削除されました。

ソフトウェア開発は、今も昔も、どこか実験的である

ここまでが去年までの振り返りとなります。

前回のブログで、Tom DeMarco はのIEEE Software誌を引用し「ソフトウェアの力で世界を変えるようなプロダクトを生み出すことの方が重要」というDeMarco自身の「気づき」について引用しました。実は、彼はその記事の最後で、こう締めくくっています。

Photo of Tom DeMarco by Hans-Rudolf Schulz

ソフトウェア開発は、今も昔も、どこか実験的である。 実際のソフトウェアの構築はそれほど実験的ではありませんが、その構想は実験的です。そして、この点にこそ私たちの焦点が当てられるべきでしょう。私たちは常にここに焦点を当てるべきでした。

(Tom DeMarco, 2009)

DORAの10年は、まさにDeMarcoが語った「ここに焦点」を当て続けた挑戦そのものだと感じます。日々の実践と実験の中から何が本当にチームを強くし、ソフトウェアをより良いものにするのかを探求し続けたその姿勢こそ、彼の言葉を現実のものとし未来を照らし出す希望そのものだと私は信じたいのです。

では、いよいよ2024 DORA Report の中身を見ていきましょう!

次回に続きます。


ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers

*1:Forsgren, N., Humble, J., & Kim, G. (2018). LeanとDevOpsの科学 [Accelerate]: テクノロジーの戦略的活用が組織変革を加速する. インプレス.

2024 Accelerate State of DevOps Report 概説#1 『"LeanとDevOpsの科学"の「科学」とは何か?』

こんにちは。ソフトウェアプロセス改善コーチでFindy Tech Blog編集長の高橋(@Taka_bow)です。

2024年10月23日、2024 DORA Accelerate State of DevOps Report、通称DORA Reportが公開されました。

2024 DORA Accelerate State of DevOps Report 表紙

このレポートは、ソフトウェア開発における運用と実践について、科学的な手法で調査・分析した結果をまとめたものです。 私は毎年このレポートを楽しみにしています。今年は10回目10年目の節目ということで、いつもより丁寧に読みました。

詳しい人でしたら、10年よりもっと長くないだろうか?と不思議がる方もおられるでしょう。

その辺の複雑な事情を含め、DORA Report 10年間の軌跡とその上に成り立つ最新レポートを解説したいと思います。全4回の予定です。

本記事ではv.2024.3をベースに解説します。なお、執筆時点で日本語版はまだリリースされていませんでした。また、正誤表を確認しなるべく最新の情報を参照するように努めました。
DORA Reportのライセンスは次の通りです。
“Accelerate State of DevOps 2024” by Google LLC is licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)

なお、DORA Report原文はGoogle Cloudのこちらのページからダウンロードできるので、ぜひ一次情報に触れてみてください。

DORAの知見を「実践」に活かすために

私の35年のエンジニア人生を振り返ると、かなりの時間「意味のない計測データ」の収集と加工に時間を費やしたと思います。なぜなら、(その時の)品質保証部門がプロダクトの「出荷」をなかなか認めてくれないからです。

いま思い出すと、私は被告側の弁護士のようであり、品質保証部門はまるで検察側のようでした。

裁判長は、事業部のトップです。

私はプロジェクトを終わらせるために、ありとあらゆるデータを集めて「そのプロダクトは動く。(きっと)問題ないはずだ!」を弁護する必要がありました。

DFD(data flow diagram)の生みの親であり、「ピープルウェア」「デッドライン」等の名著で有名なTom DeMarco(トム・デマルコ)というエンジニアの巨匠がいます。

Photo of Tom DeMarco by Hans-Rudolf Schulz

彼は、若かりし頃(1982年)の論文で次のような言葉を発しました。


「計測できないものは制御できない」 “You can’t control what you can’t measure.”

これは、私のような古いエンジニアにとっては、しばらくのあいだ呪言となりました。

しかし、2009年7月、IEEE Software誌7月8月合併号*1に、Tom DeMarco は衝撃的な記事を寄稿します。

タイトルは ”Software Engineering: An Idea Whose Time Has Come and Gone?(ソフトウェアエンジニアリング:その考えは、もう終わったことなのか?)”

“You can’t control what you can’t measure.”(計測できないものは制御できない) このセリフには本当の真実が含まれているのですが、私はこのセリフを使うことに違和感を覚えるようになりました。 (中略)例えば、過去40年間、私たちはソフトウェアプロジェクトを時間通り、予算通りに終わらせることができないことで自らを苦しめてきました。

この文章は当時、Tom DeMarco 自身が「測定できないものは制御できない」は誤りだったと認めた!ということで業界に衝撃が走りました。しかし、Tom DeMarco が本当に言いたかったことは、次の文章に含まれます。

しかし、先ほども申し上げたように、これは決して至上命題ではありませんでした。 もっと重要なのは、世界を変えるような、あるいは企業やビジネスのあり方を変えるようなソフトウェアを作るという「変革」です。

Tom DeMarcoが指摘したように、時代はさきほどのような「裁判ごっこ」よりも、もっと顧客との関係性を重視する方向に確実に変化してきました。それが、リーンであり、アジャイルでありDevOpsなのだと思います。

そんな中で登場してきたFour Keysですが、出会ったときの衝撃は今でも鮮明に覚えています。その指標があまりにもシンプルで、なおかつ説得力に満ちていたからです。

DORAの研究成果は決して一時的なトレンドではありません。Four Keysを単なる「流行り」と捉えるのは誤りです。しかし、その一方で、すべてを鵜呑みにする必要もありません。

2024年で10周年を迎えたDORA Reportには、この取り組みが成熟してきたことを示す重要なメッセージが随所に見られます。その代表例が"Applying insights from DORA"(DORAの知見を実践に活かすために)という章です。

一部を訳します。

私たちの調査結果は、皆様が独自の実験や仮説を立てる際の参考にしていただけます。チームや組織に最適なアプローチを見出すために、変更による影響を測定しながら実験を重ねることが重要です。それにより、私たちの調査結果の検証にもつながります。結果は組織によって異なることが想定されますので、ぜひ皆様の取り組みを共有していただき、その経験から互いに学び合えればと思います。

これは、DORAの取り組みが科学的だからこそ言えることであり、数々の困難があったなかで10年間継続してこれた理由でもあると思うのです。

科学とは何か?

DORA Reportから少し脱線するのですが、とても大事なことなので説明させてください。

書籍「LeanとDevOpsの科学[Accelerate] テクノロジーの戦略的活用が組織変革を加速する」(原題"Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations") が、DORAの研究ベースに書かれていることは広く知られているところです。

book.impress.co.jp

ところで、このタイトルの「科学」は何を指しているのでしょうか。

科学とは「なぜだろう?」という疑問に対して、実験と観察を重ねながら、誰でも同じ結果を得られる答えを見つけ出す取り組み、と言えます。天文学者であり小説家でもあった、20世紀屈指の科学者カール・セーガンは科学について次のような表現をしていました。

"Science is a way of thinking much more than it is a body of knowledge." Carl Sagan

科学は、思考の方法であり、知識の集積ではない。 カール・セーガン

例えば、あるカレー店の人気の秘密を科学的に考えてみましょう。

「このカレーが美味しいのはなぜか?」という問いに対して、「シェフの腕が良いから」という個人的な感想や、「創業100年の伝統の味だから」という言い伝えだけでは、科学的な説明とはいえません。

科学的なアプローチでは、次のような調査と実験を行います。

  • 100人のお客さんに同じ条件で食べてもらい、評価を集める
  • スパイスの配合を少しずつ変えて、味の変化を測定する
  • 調理時間や火加減を細かく記録し、最適な条件を探る
  • 異なる調理人が同じレシピで作っても、同じ味になるか確認する

このような過程を経て、「このスパイスの配合で、この温度で30分煮込むと、8割以上のお客さんが美味しいと感じるカレーができる」と言った、誰でも確認できる答えにたどり着けるのです。

開発の現場でも同じです。

「このやり方は効果的だ」という個人の経験や、「有名な企業がやっているから」という理由だけでは、本当にそれが正しいのかわかりません。

DORAの研究では、これらを科学的アプローチによってDevOpsの成功要因を明らかにしました。個人の経験や直感を超えて、数多くの組織のデータを分析し、客観的な証拠に基づいて「何が効果的なのか」を示したのです。これは単なる成功事例の集積ではなく、科学的リサーチによって検証された信頼性の高い知見なのです。

「科学的リサーチ」方法とは

科学的リサーチとは、現象を体系的に調査し、新しい発見や理論を導き出す手法です。観察から始まり、概念化、測定可能な変数への置き換え、モデル化という段階を経て、客観的なデータに基づいた結論を導きます。

向後, 千春. (2016). 18歳からの「大人の学び」基礎講座: 学ぶ, 書く, リサーチする, 生きる. 北大路書房. 図3-3に筆者が加筆

研究の流れは次の通りです。研究プロセスは次の4段階で進みます:

  1. 現象の観察
    例えば、「なぜある開発チームは他のチームより高い成果を上げているのか」という問いからDORA(DevOps Research and Assessment)チームの研究が始まりました。
  2. 概念の特定
    DORAの研究では、継続的デリバリー、継続的インテグレーション、自動化テストの実施などを成功要因として特定しました。
  3. 変数への変換
    概念を測定可能な形に変換します。例えば、デプロイ頻度、変更リードタイム、障害復旧時間、変更失敗率などの具体的指標として定義します。
  4. モデルの構築
    収集したデータを統計的に分析し、因果関係を明らかにします。DORAの研究では、自動化テストの実施が変更リードタイムを短縮することや、チームの独立性がサービスの信頼性向上につながることを実証しました。

この一連のプロセスを通じて、科学的リサーチは客観的な検証可能性、再現性、そしてデータに基づく実証性という特徴を持ちます。これらの特徴により、DORA Reportは他の研究者が検証・発展させられる形でDevOpsの成功要因を明らかにできました。

定量的データと統計分析がもたらす信頼性

DORA Reportで用いられる科学的アプローチに対して、「都合の良いデータ解釈をしているのではないか」という批判を目にすることがあります。

しかし、この批判は科学的リサーチの本質を十分に理解していないことから生じている可能性があります。科学的リサーチとは、単にデータを収集し結果を得ることではなく、現象を客観的に理解し、実証可能な方法で結果を導き出す「方法論」そのものです。

この考え方は、DORAの研究において次のように具体化されています:

  1. 思考の方法の適用
    DORAの研究では、特定の仮説(例えば「継続的デリバリーはパフォーマンス向上に寄与する」)を立て、それを証明または反証するために厳密な手法でデータを収集・分析しています。これは、科学的な「疑う」姿勢と「検証する」姿勢の両立です。
  2. 透明性と再現性
    測定方法や分析手順を厳密に文書化し、他の研究者が追試可能な形で公開しています。これは、科学が単なる知識の蓄積ではなく、「共有されるべきプロセス」であることを象徴しています。
  3. 実践への応用
    科学の思考方法をもとに導き出された成果が、現場での実践を通じて再び検証されています。たとえば、継続的デリバリーやテストの自動化が現実の組織で具体的な効果をもたらすことが示されています。

DORAの研究は10年にわたり、理論と実践の両面で成果を示してきました。カール・セーガンが述べたように、科学とは「知識の集積」ではなく「より良い問いを立て、より深く理解するための思考の道具」です。

DORAはこの科学的アプローチを用いて、LeanやDevOpsの成功要因を信頼性の高い方法論として確立し、現場の改善や組織変革に直接応用できる形で提供しています。毎年のクラスター分析結果の微細な変化も、このような継続的なデータ分析の重要性を示しているのです。

DORA Report 10年の変遷

今回の2024 DORA Reportでは、 "A decade with DORA"(DORAと共に過ごした10年)という章があります。DevOpsの起源から、State of DevOps Report誕生の背景、今日に至るまでの歴史が書かれています。

私は、その説明内容からこれまでの変遷を1枚の画にまとめてみました。

State of DevOps Reportの歴史

この画から分かるように「DORA Report 10年」とは 2014年版State of DevOps Reportから2024年版State of DevOps Reportまでを指します。2020年版はCOVID-19の影響で発行されていませんので、時間の経過を指す10年ではなく、この10冊が10年というわけです。

ですが、2013年版のState of DevOps Reportは10年の10冊には含まれていません。それはなぜなのか? 順を追って解説します。

State of DevOps Report のはじまり

2011年、Puppet Labsで働いていたAlanna BrownはDevOpsについてより深く理解するための調査を開始しました。この調査は、「'DevOps'的な働き方がITにおける新しいビジネスの方法として台頭している」ということを裏付ける助けとなりました。

itrevolution.com

この調査の成功をベースに、2012年に新たな調査を開始、2013年 Puppet LabsとGene Kim氏が率いるIT Revolution Pressが共同で、最初のState of DevOps Reportを発行しました。

書籍「LeanとDevOpsの科学[Accelerate] *2」には、次のような記載があります。

「State of DevOps Report』の初回は2014年版だが、研究自体はそれ以前に始まっていたという点に留意されたい。Puppet社のチームは、DevOpsという(まだそれほど知られていなかった)概念をより良く理解し、それが現場でどう採用されつつあるか、組織パフォーマンスの改善を組織がどう実感しているかを把握するための研究を始めており、2012年にこの研究への参画をGene Kimに求めた。

Gene KimはTripwire, Inc.の創業者兼CTOとして13年間務めたあと、IT Revolutionを創業し出版活動、DevOpsコミュニティへの貢献に尽力する人物でした。

itrevolution.com

さらに、優秀なキーマンが巻き込まれることになります。

そしてGene Kimがその後、Jez Humbleにも応援を仰ぎ、ともに調査に加わって全世界の組織から4,000件の回答を集め、分析した。この種の調査では最大規模である。

Jez Humble は、カルフォルニア大学バークレー校で教鞭も執っているDevOpsの研究者でした。

github.com

このような経緯を経て、2013 State of DevOps Reportはリリースされたのです。

なぜ2013年版はカウントされないのか?

一言で言えば「調査方法が違う」からです。

現在の統計的アプローチが取られたのは2014年以降となります。2013年当時のPuppet社は、ITインフラストラクチャの自動化を支援するソフトウェアを開発・提供している会社でした。

その中心的なプロダクトは Puppet Enterpriseであり、これはサーバーやクラウド環境、ネットワークデバイスなどの管理を効率化し、DevOpsやインフラ管理の自動化を推進するツールでした。

2013 State of Devops Report は、当時あまり知られていなかったDevOpsという概念を広め、Puppet Enterpriseの市場を開拓することが主な目的だったと思われます。実際、2013年版の調査による【主要な発見】は、やや意図的な印象を受けます。

【主要な発見】

  • DevOps導入状況
    • 回答者の63%がDevOpsを導入(2011年から26%増加)
    • 導入期間が長いほど、高パフォーマンス達成の可能性が5倍に上昇
  • 高パフォーマンスを実現する共通実践
    • バージョン管理システムの使用(89%)
    • コードデプロイの自動化(82%)
  • DevOpsスキルの需要
    • コーディング/スクリプティング(84%)
    • コミュニケーションスキル(60%)
    • プロセス再構築スキル(56%)

また、このレポートでは、後のDORAメトリクスの基礎となる4つの主要指標、デプロイ頻度・変更のリードタイム・変更の失敗率・復旧までの平均時間、いわゆるFour Keysが登場します。

しかし、現在の観点とは異なる分析結果でした。下記に、2013 State of DevOps Reportのパフォーマンス指標の解説と、グラフを転載します。

DevOpsの成熟度(未導入から12ヶ月以上前に導入)に基づいて、デプロイ頻度、変更のリードタイム、変更の失敗率、復旧までの平均時間という4つの主要なDevOpsパフォーマンス指標を分析しました。DevOpsの導入が成熟している組織は、まだDevOpsを導入していない組織と比較して、すべての指標において著しく高いパフォーマンスを示しました。

2013 State of DevOps Report, p5

ご覧の通り、4つの指標はそれぞれ、「DevOpsを導入しているか否か」で期間分類されているのです。

  1. Not Implemented(未導入)
  2. Currently Implementing(導入中)
  3. Implemented <12 Months(導入後12ヶ月未満)
  4. Implemented >12 Months(導入後12ヶ月以上)

そして、このレポートは回答者の多くがDevOpsに関心の高い層に偏っている可能性を検証しておらず、バイアスの制御が十分でないという問題も抱えていました。

Dr. Nicole Forsgren の参画

2014年、State of Devops Report に大きな転機が訪れます。

それが、当時ユタ州立大学ハンツマンビジネススクールの教授であったDr. Nicole Forsgren(ニコール・フォースグレン博士)の参画です。

彼女は、ITインパクト、ナレッジマネジメント、ユーザー体験の専門家でした。

itrevolution.com

「LeanとDevOpsの科学[Accelerate] 」に書かれた「謝辞」には、Nicole Forsgrenの次のようなコメントがあります。

私が初めて皆さんのところへお邪魔して、「ここは違っています」などと指摘させていただいたとき(そのときの私の口調、失礼じゃなかったですよね、ハンブルさん?)、皆さんは私を部屋から蹴り出したりしなかった。 おかげで私はその後、忍耐力と共感力を養い、冷めかけていたテクノロジーへの愛を再燃させることができた。また、「あともう1回だけ、分析やってみて!」が口癖であるキム氏の無尽蔵の熱意と気合いは、我々の仕事を堅牢で大変興味深いものにしてくれている。

この謝辞から読み取れるように、Dr. Nicole Forsgren は2014年以降のState of Devops Reportに科学的厳密性をもたらした重要な人物です。それまでの調査手法に対して建設的なフィードバックをし、その結果、2014年のレポートから、より体系的で科学的なリサーチがもたらされます。

そして、2014年から2017年までの間、State of Devops Report は次のメンバーによって調査・研究がなされました。

  1. Nicole Forsgren Velasquez(ニコール・フォースグレン・ベラスケス)
  2. Gene Kim(ジーン・キム)
  3. Jez Humble(ジェズ・ハンブル)
  4. Nigel Kersten(ナイジェル・カーステン)
    • Puppet LabsのCIO
  5. Alanna Brown(アラナ・ブラウン)
    • 2012年からState of Devops Report を担当する発案者

最初はFour Keysじゃなかったし、Eliteも居なかった

Dr. Nicole Forsgrenの参加は、リサーチプログラムに科学的な厳密さをもたらしました。

このことで、最初のFour Keysは統計学的有意(=確率的に偶然とは考えにくく、意味があると考えられる)が検証されるようになります。

このことで、Change Failure Rate(変更失敗率)は、他の3つの指標とは有意な相関が見られなかったため、ITパフォーマンスの定義から除外されています。

【2014 パフォーマンス指標】

指標 High Medium Low
Deployment Frequency 1日に複数回 週1回〜月1回 月1回〜6ヶ月に1回
Lead Time for Changes 数分単位 1日〜1週間 1週間〜6ヶ月
MTTR 分単位 時間単位 日単位

とはいえ、2014 State of DevOpsレポートは、ソフトウェアデリバリーのパフォーマンスと組織のパフォーマンスの関連性を明らかにし、


「高パフォーマンスのITチームを持つ上場企業は、低パフォーマンスのIT組織を持つ企業と比較して、3年間で市場価値の成長率が50%高かった」

ということが発見されています。(2014 Accelerate State of Devops Report)

2014 State of Devops Reportは、Nicole Forsgrenの「科学的リサーチ」によって次第にデータは説得力のあるものに変わっていきました。 ところが、ここにきて大きな壁が立ちふさがります。

それが、ソフトウェア界の巨匠Martin Fowler(マーティン・ファウラー)でした。

次回に続きます。

tech.findy.co.jp


ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers

*1:https://www.cs.uni.edu/~wallingf/teaching/172/resources/demarco-on-se.pdf

*2:Forsgren, N., Humble, J., & Kim, G. (2018). LeanとDevOpsの科学 [Accelerate]: テクノロジーの戦略的活用が組織変革を加速する. インプレス.

GitHubからエンジニアスキルを可視化する「スキル偏差値」を大幅リニューアルした話

こんにちは。

FindyでMLエンジニアをしているyusukeshimpo(@WebY76755963)です。

今回は直近で公開した「スキル偏差値ver.3」機能について、その内容や具体的な機械学習モデルの作成方法について紹介します。

Findyのスキル偏差値とは?

スキル偏差値の概要

まずはプロダクトの概要を説明いたします。 「スキル偏差値」は、Findyに登録されているユーザーのGitHubリポジトリ(※Open-access repositoryのみ)を解析し、 コミット量、OSSプロジェクトへの貢献度、他者からのコードの支持などに基づいて技術力をスコアリングする機能です。

GitHubの解析は機械学習技術を用いて実施しており、これまでに何度かアルゴリズムや学習データのアップデートを行ってきましたが、 今回は2017年のリリース以降で最も大幅なリニューアルを行なっており、大元のアルゴリズムや学習データの作り方自体をガラッと変更しています。

アップデートすることになった背景

アップデートの背景には、大きく分けて2つの理由が存在します。

  • リリースから数年が経過する中でユーザーの方からの要望が増えてきたから。
    • 特に「言語別」のスコアに対する要望が強く、言語別のスキル判定の精度改善を求めるニーズが強かったため。
  • 2023年以降、LLMやAI Agentが登場し、エンジニアリング(特にコーディング)の領域でも、それが当たり前に使われるようになってきたから。
    • 今後エンジニアに求められる「スキル」も環境に合わせて変わっていくことが予想される中で、よりサービス開発や運用に直結する能力を評価できるモデルに進化させていく必要があったため。

上記「ユーザーボイス」と「開発を取り巻く環境変化」を受けて、このタイミングで新たな「スキル偏差値」を作ることを決めました。

スキルの見える化する、スキル偏差値ver.3の詳細

今回のスキル偏差値の開発では次の3つの手順でスキルの見える化をしています。

  1. 学習データの作成
  2. ランキング学習
  3. 学習モデルによるスコアリング

この3つの手順について詳しく解説します。

1.学習データの作成

ランキング学習を支えるのは「質の高いデータ」です。

GitHubリポジトリから収集したデータを加工し、言語ごとに特徴量を整備することで、正確なモデル構築を目指しました。

1-1.使用言語ごとのデータ準備

GitHubリポジトリを次の主要言語ごとに分類しました。

  • Python
  • JavaScript
  • TypeScript
  • Go
  • Ruby
  • PHP

各言語ごとで重視される特徴量に違いがあり、言語ごとにデータを分ける方針にしました。

1-2.ペアワイズ形式のデータ構築

ランキング学習するために、今回は「ペアワイズ形式」のデータを用意しました。これは、2つのエンティティ間でどちらが優れているかを比較する形式です。

今回のケースではGitHubユーザー間のリポジトリ情報を比較し、相対的なスキル勝敗データを生成しました。また、ペアはバイアスが生まれないようにランダムでペアを作るようにしています。

1-3.勝敗アノテーションの付与

生成したペアデータに対して、上の図のようにスキルの「勝敗」をラベリングしました。

特にラベル基準をある程度明確化しておくと担当者間でのクロスレビューの際の議論がまとまりやすかったです。

2.ランキング学習

次に、上で用意した学習データに対して、次の2つのステップを経て、「ランキング学習」の手法を用いて特徴量間の比較します。

「ランキング学習」(Learning to Rank)は、複数ある事象の「順位」を目的変数とした場合に用いられる機械学習の手法で、身近な例としては「検索エンジン」などで使われています。

機械学習を用いた「ランキング学習」の実装にはいくつか方法がありますが、今回は上述したペアワイズなデータ間の「勝敗」を学習し、

2-1.ペアワイズデータの特徴量生成

2-2.機械学習モデルによる勝敗予測

以下、それぞれのステップを解説していきます。

2-1.ペアワイズデータの特徴量生成

ランキング学習するために、まずは各々のデータを機械が認識できる形にする必要があります。

具体的には、各データを「特徴化」し、ベクトル化した数値で比較できるようにしています。

当初はこの「特徴」抽出のプロセスについては、言語を問わずある程度一元化できると考えていましたが、 実験過程で、言語間の「特徴」に大きな違いが見られました。

詳細は社外秘情報にあたるため公開できませんが、一例として、ある言語では「Readmeなどのテキストの長さ」が重要な意味を持ちます。 別の言語だとボイラープレート内にReadme用のテキストが充実しているため「Readmeの長さ」を特徴として重視しない方が好ましい、という傾向が出ていました。

上記のように、言語ごとに利用者を取り巻く環境が大きく違う点も考慮し、現段階では「言語ごとに異なる特徴を採用する」という意思決定をしています。

*1

2-2.機械学習モデルによる勝敗予測

データを特徴化したら、機械学習モデルによるランキング学習をし、当該データ間の順位を予測します。

検討初期にはより複雑なモデルを利用することも検討しましたが、「言語ごとに個別のモデルを動かす」という仕様上の制約や、特徴抽出の工夫によりシンプルなモデルでも十分な精度が実現できたため採用しています。

上記のアルゴリズムにペアワイズの学習データとその勝敗を学習させ、勝敗の判定を行なっています。

モデルの精度についてはこちらも非公開ですが、検証方法としてはこちらも「複数人が判断した際の判断と同様の出力(=勝敗)を出せるか」を基準に評価しています。

リリース時点で、対象としている6言語はいずれも人間の判断を8割以上の精度で模倣できており、一定正確なジャッジができていると判断しています。

*2

3.モデルによるスコアリング

学習モデルを作成したら、その推論結果を元にユーザーのスキルをスコアリングしていきます。

学習したのは、ペアワイズデータの勝敗ですので、計算したいユーザーと他のユーザーとの勝敗をシミュレーションして、それを元にユーザーのスキルスコアを算出します。

このスコアを一般的に偏差値計算をする数式に当てはめ、最後に調整(言語ごとに異なる尺度を正規化するなど)をしたものを「スキル偏差値」としています。

実装で苦労した点と解決策

上記を実装する上で困難な点が多々ありました。

今回は次の苦労した点と解決としてどんなことをしたかも説明していきます。

  1. モデル精度の担保
  2. 言語ごとの特徴量

1.モデル精度の担保

アノテーションしたデータを使用しているので、モデルはアノテータに影響を受けます。

アノテータのバイアスを極力抑えて客観的に良いモデルを作成するための工夫が必要だったため、次の工夫をすることで精度改善を試みました。

アノテーションの工夫

データ作成時のアノテーションには次のような手順を導入しました。

  1. 言語経験者による判断: 経験者がアノテーションをすることで正確性を担保
  2. ラベル基準の明確化: ラベル基準を言語化してアノテータに共有
  3. クロスレビュー: 複数人がレビューすることでバイアスを極力抑える

例として、社内でRubyを日常的に利用する有識者へアノテーションの基準づくりを依頼しました。筆者は普段の業務でRubyを使用しないため、このように経験者から合意を得ることで、より良質な正解データを作成できました。

評価設計

アノテーションという定性的な評価を学習データにしているため、ただ単に評価関数の良し悪しで判断できません。

そんな中どのようにモデルの精度を評価したのかも非常に重要かつ難しいポイントでした。

そこで、アノテーションとモデルの精度を担保するために定性と定量の両方で確認しました。

  • 定量評価: 正解率や偏差値の分布を測定
  • 定性評価: 偏差値の妥当性を人間が確認

勝敗の正解率を確認したのち、Ratingや偏差値を実際に算出しますが、正規分布に基づいているか、ユーザーはこの数値で妥当かを定性的に評価しました。

これによるアノテーション結果の正当性を確保しつつ、モデルの方向性も正しいと確認できました。実際には次のサイクルでモデル評価とアウトプット評価をして都度アノテーションから見直すこともしました。

2.言語固有の特徴量設計

また、言語ごとに特徴量設計をするのも苦労したポイントです。同じ特徴量では表現できなかったため、言語ごとに最適な特徴量設計を見つける必要がありました。

言語特徴の言語化をしてデータ化

言語ごとにどんな特徴を持っているのかを言語化し異なる特徴を設計しました。

例えば、あるプログラミング言語ではボイラーテンプレートが充実しているため独自ロジックにスキルが出やすい傾向にあったり、ライブラリの利活用が盛んなプログラミング言語など特徴量が変わってくることがわかりました。

この特徴量の言語化のフローは大きく4つのポイントに分かれます。 まず、仮説の立案し、各言語の特性を調査し、スキルの違いを反映するための特徴を言語ごとに仮説立てを行います。その後実験を重ねて、仮説に基づいて特徴量を作成し、推論結果や評価指標を確認します。 確認した結果に違和感があれば、仮説を見直して新たな特徴量を追加・修正します。これらを繰り返し、最後に評価基準と実際の結果にズレがあれば、特徴量の設計を見直し、適正化という流れを繰り返しました。

最後に

以上がスキル偏差値ver.3の開発についてでした。参考にしていただければ幸いです。

また、弊社では機械学習エンジニア・データエンジニアなど一緒に働いてくれるメンバーを募集しております。

興味がある方は↓からご応募していただければと思います。

herp.careers

*1: 今回のリニューアルが6言語限定になったのも、上記の理由からモデルを言語別に作る必要があったためです

*2: 余談ですが、人間が判断した際も"意見が分かれる"ペアは10~20%程度存在するため、8割の精度というのはかなり妥当な水準だと考えています

Findy Team+のデータインポートのアプリケーションアーキテクチャを大公開!

こんにちは、ファインディでFindy Team+(以下Team+)を開発しているEND(@aiandrox)です。この記事はFindy Advent Calendar 2024 10日目の記事です。

adventar.org

Team+ではコード管理ツール・イシュー管理ツール・カレンダーなど、様々な性質の外部サービスと連携して、エンジニア組織における開発生産性の可視化・分析を行うためのデータを取得しています。

この分析を行うためには、外部サービスごとに異なるデータ構造やAPI仕様の差を吸収した統一的なデータ管理を行う必要があります。この課題を解決するため、異なるサービスのデータを統合し、単一のUIで一貫性を持って表示する仕組みを整えています。

この記事では、コード管理ツールのデータインポートをどのようなアーキテクチャで実現しているかを紹介します。

Team+と外部サービス差分の例

前提として、Team+では現在GitHub, GitLab, Bitbucket, Backlogから取得したコード管理系のデータを以下のように表示しています。

この画面の分析のために取得しているデータは、プルリクのステータス、コミット日、オープン日、最初のレビュー日、レビューステータス、マージ日です。

外部サービスごとの小さな差分の例として、プルリクのステータスがあります。Team+では「対応中」「クローズ」「マージ」の3種類がありますが、各サービスのAPIレスポンスの値は以下を返すようになっています。

GitHub GitLab Bitbucket Backlog
open, closed opened, closed, locked, merged OPEN, MERGED, DECLINED, SUPERSEDED Open, Closed, Merged

GitHubの場合、ステータスの値だけを参照してもクローズとマージの区別がつかないため、merged_atに値が入っているかどうかで判定しています。また、GitLabのlockedやBitbucketのSUPERSEDEDのようなイレギュラーなステータスは、他の値に丸めるようにしています。

全体のアプリケーションアーキテクチャ

全体の流れは、以下のようになっています。これらのインポート処理は、各サービスごとに分割して全8インスタンスで行い、それぞれのインスタンス内で組織ごとに20プロセスで並列して処理しています(2024年12月時点)。

主な役割
Client層 APIの仕様差を吸収し、レスポンスをRepresentationインスタンスとして返す
Importer層 エラーハンドリングをし、サービス独自テーブルに保存する
Transformer層 各サービスごとのデータ形式差分を吸収し、共通テーブルに保存する
API層 表示データのフロントエンド提供

GitHub, GitLabはClient層とImporter層の設計が少し違うため、この図では省略しています

このように4つの層と2種類のテーブルを使うことで、以下のようなメリットとデメリットがあります。

メリット

責務が明確になりコードの見通しがよい

この設計では、新しい外部サービスを追加する際はClient層とTransformer層、それぞれ単独で実装することができます。また、コードレビュー時も変更範囲が限定されるため、確認すべき箇所を絞りやすく、レビューの効率がよいです。

不具合が起きたときの原因がわかりやすくなる

例えば、外部サービスのAPIのエラーによってデータが取得できなかった場合、Importer層でエラーがキャッチされます。また、値がTeam+で扱うものとして想定外だった場合はTransformer層でエラーになります。

外部サービスのAPIを使っているため、こちらで対応できないエラーが起きることや想定されないデータが返ってくることは避けられませんが、それによる影響が最小限になるようにしています。

2種類のデータを扱うことで、柔軟性と速度を担保する

Team+には、サービス独自テーブルと共通テーブルの2種類があります。前者はAPIレスポンスの形に近い形で保存することを目的とし、後者はTeam+で扱いやすい形式で保存することを目的としています。これにより、フロントエンドからのリクエストに対しては、外部APIと独立して安定したデータソースとして迅速に提供することができます。

他にも、サービス独自テーブルに保存されたデータはTransformer層でエラーが発生しても再利用可能なため、外部APIを再度叩く必要がありません。

層ごとにスケールすることができる

現時点では行っていませんが、Import処理とTransform処理が独立しているため、必要に応じて片方のみスケールするという選択肢を持つことができます。

デメリット

層ごとの独立性が高いがゆえの複雑さがある

層ごとに責務が分離しているため、全体の流れを把握するのが大変です。特に新しいメンバーが参加した場合、どの処理がどの層で行われているかを理解するまでに学習コストがかかります。また、デバッグ時には層の間をまたぐデータフローを追う必要があり、状況によっては負担になることもあります。

リアルタイム性に欠ける

このアーキテクチャは、データの取得から変換、提供までを複数の層に分割しているため、一連の処理がリアルタイムで完結する用途には適していません。例えば、ユーザーがリアルタイムにデータを参照したい場合、現行のバッチ処理的なインポートでは対応が難しいです。

Team+でも、初回連携の際の一時的なデータ取得ではClient層しか利用していません。

一部のリソースのみインポートするといった処理が難しい

このアーキテクチャでは、すべてのデータのImport処理が完了した後にTransform処理を行っています。これが完了するまで画面上にデータを表示できないため、Import処理に時間がかからないリソースから逐次的に表示できるようにするなどの柔軟性は持たせづらいです。

各層について

Client層

この層では、外部サービスのAPIレスポンスや仕様がサービスごとに違うため、その差分を吸収することが大きな目的としています。

libディレクトリ配下に置いてGemのように独立して作用できるようにしつつ、アプリケーションで統一して扱えるようにしています。主に、以下のような処理を行っています。

  • 外部サービスのAPIにリクエストを行い、そのレスポンスをアプリケーションで扱いやすい形に成形加工するRepresentationクラスに格納する
  • リトライ処理を行う
  • 例外を発生させる

エラーに対しては、Rate limitのみリトライ処理を行っています。その他のエラーと、最大回数を上回ったエラーは例外を投げるようにしています。ここでは、主にレスポンスステータスを参照しています。

これらの処理には、外部サービス独自のGemは使わずスクラッチで実装しています。また、1ページごとに遅延実行をするようにしてメモリを圧迫しないように対策しています。

Importer層

Client層から取得したRepresentationインスタンスを使い、レコードごとのアソシエーションに応じて関連レコードと一緒に独自テーブルに保存します。

また、Clientインスタンスのような依存性を注入することで、Importerの単体テストを容易にしています。

class PullsImporter
  def self.call(client, repo_id:, repo_full_name:)
    new(client, repo_id:, repo_full_name:).call
  end

  def initialize(client, repo_id:, repo_full_name:)
    @client = client
    @repo_id = repo_id
    @repo_full_name = repo_full_name
    @user_finder = UserFinder.new
  end

  def call
    client.pulls(repo_full_name).each do |response|
      attributes = response.map do |representation|
        PullApiMapper.call(representation, repo_id:, user_finder:)
      end
      Source::Pull.import!(attributes)
    end
  end
end

Importer内では、FinderやApiMapperなどを定義し、それを用いてインポート処理を行っています。

関連レコードの取得はFinderクラスを作成し、これを介するようにしています。これにより、関連レコードを読み込むためのN+1を最小限に抑えることができるようにしています。

class UserFinder
  def find_by(uuid:)
    users_index_by_uuid[uuid]
  end

  private

  def users_index_by_uuid
    @users_index_by_uuid ||= Source::User.reload.index_by(&:uuid)
  end
end

ApiMapperは、Representationからサービス独自テーブルへの変換のためのattributes作成を行っています。関連レコード(この例ではuser)の外部キーを取得するためにFinderを渡しておき、レコード取得の処理はApiMapper内で行っています。

class PullApiMapper
  def call
    {
      repo_id:,
      user_id: user.id,
      title: representation.title,
      state: representation.state
    }
  end

  private

  attr_reader :representation, :repo_id, :user_finder

  def user
    @user ||= user_finder.find_by(uuid: representation.user.uuid)
  end
end

Transformer層

サービス独自のテーブルからレコードを取得し、各サービスのデータ構造の差分を吸収し、表示データ用のテーブルに保存します。

class PullConverter
  def self.call(duration)
    sources = Source::Pull.where(updated_at: duration)
    attributes = sources.map { |source| PullMapper.new.call(source) }
    View::Pull.import!(attributes)
  end
end

ここでもPullMapperが使われていますが、これはImporterで使われているApiMapperとは似ているようで少し違います。前者は、Representationクラスのインスタンスをテーブルに保存するためのもので、後者はモデルのレコードを共通テーブルに保存するためのものです。外部サービスのデータ形式の差分はここで吸収されることがほとんどです。

class PullMapper
  def call(source)
    {
      source_type: source.class.name,
      source_id: source.id,
      repo_id: source.repo.view_repo.id,
      user_id: source.user.view_user.id,
      title: source.title,
      status: to_view_status(source)
    }
  end

  private
  
  def to_view_status(source)
    return :merged if source.merged?
    return :closed if source.closed?

    :created
  end
end

API層

バックエンドから表示データ用のテーブルの値をフロントエンドに返します。ここでは、サービス独自テーブルにはアクセスせず、共通テーブルの値のみを使用します。

リアルタイムのデータ集計には独自のアーキテクチャを利用していますが、こちらに関してはまた別の機会にご紹介します。

おわりに

今回、Team+のデータインポート周りのアプリケーションアーキテクチャについて紹介しました。

このアーキテクチャは、最初からこの形だったわけではありません。複数の外部サービスと連携する中で、API仕様やデータ形式の違いに直面し、それらを克服するために少しずつ設計を見直してきました。また、例えば、初期にはエラー処理がClient層やImporter層に散在していたり、それぞれの層が密結合になっていたため、層ごとの役割分担を明確化しました。こうした試行錯誤を繰り返しながら、現在の仕組みに至っています。

今も、旧アーキテクチャの名残が残っている箇所があったり、データインポートの時間が長い組織があるといった伸びしろがあります。これからも、改善を重ねて、みなさんにとって価値のあるサービスを提供していきます!


ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味のある方は、ぜひカジュアル面談で話を聞きに来てください!

herp.careers

新しい技術領域へのチャレンジを促進!フロントエンドエンジニアのためのバックエンド勉強会を開催

こんにちは!ファインディでFindy Team+開発チームのEMをしている浜田です。 この記事はFindy Advent Calendar 2024 6日目の記事です。

adventar.org

今年の上旬、フロントエンジニア向けにバックエンド勉強会を開催しました。この記事ではバックエンド勉強会を開催した目的や内容、効果について紹介します。

バックエンド勉強会を開催した背景

私のチームが開発しているFindy Team+はWebアプリケーションとして提供しており、フロントエンドはReact/TypeScript、バックエンドはRuby on Railsを使用しています。

ファインディでは、エンジニア個人の志向に合わせて、特定の技術領域を深く理解することでバリューを発揮している方や複数の技術領域を幅広く扱ってバリューを発揮している方など様々な方がいます。 そのため、自分の得意領域以外への挑戦を推奨しています。

ただ、現在では1チームに6〜8名ほどのエンジニアで構成され、各自が得意領域を担当することで効率よく開発が進む状況です。 このような状況では、得意領域以外にチャレンジしたい気持ちがあったとしても、最初の一歩が踏み出しづらいものです。

そこで、今回はフロントエンドを主軸としているエンジニアがバックエンドに挑戦する一歩を後押しするためにバックエンド勉強会を実施しました。

バックエンド勉強会の概要

  • 期間: 2024年2月〜8月
  • 頻度: 毎週30分
  • 回数: 23回
  • 講師: 私
    • RubyもReact/TypeScriptも業務で使った経験あり
  • 参加者: 3名
    • バックエンドほぼ未経験、または数年触っていない。React/TypeScriptはバリバリ書いている
  • カリキュラム
    • RubyやRails、DBの基礎
    • 業務で使うコードを使ったライブコーディング

バックエンド勉強会の内容

バックエンド勉強会では、前半はRubyやRails、後半はDBについて学習をしました。 業務で使っているコードを使ってAPI開発の基礎知識を学ぶことで、簡単なAPIの作成や修正ができることを目指しました。

RubyやRailsの学習

VS Codeのプラグイン設定

まずはエディタを整備しました。 参加者は全員VS Codeを使っていたので次のプラグインをインストールしました。

Rails console / dbconsoleを使ってみる

開発環境には全員バックエンド環境も構築しているので環境構築は不要です。 RubyやSQLをサクッと試すことができるように、Rails console / dbconsoleの使い方を紹介しました。

railsguides.jp

ruby-lang.orgを読む

ruby-lang.orgの一部を読み合わせしました。 読み合わせした箇所をいくつか紹介します。

他言語からのRuby入門

普遍の真理

Rubyでは、nilとfalseを除くすべてのものは真と評価されます。 CやPythonを始めとする多くの言語では、0あるいはその他の値、空のリストなどは 偽と評価されます

JavaScriptを書いている人にとって0はFalsyですが、Rubyだと0はTruthyでありハマりやすいので強調しておきました。

不思議なメソッド名

Rubyでは、メソッド名の最後に疑問符(?)や感嘆符(!)が使われることがあります。

珍しいルールなのでこちらも詳しく説明しました。

?はbooleanを返すメソッドにつけることが多く理解しやすいです。

# 偶数かどうかを判定するメソッド
def odd?(n)
  n % 2 == 1
end

!は破壊的メソッドに付けることが多く、破壊的メソッドと同じ役割だが破壊的ではないメソッドがある場合には破壊的メソッド側に!が付けます。ただし、破壊的メソッドしかない場合は!をつけません。

# !あり・なしメソッドがあるパターン
str = "string"
str.upcase!
str # => "STRING", 元の値がstrが上書きされている

str = "string"
upcased_str = str.upcase
str # => "string", 元の値が上書きされていない
upcased_str # => "STRING", upcaseの結果がupcased_strに代入されている

# 同名の非破壊メソッドがないため、破壊的メソッドだけど!がつかない
str = "string"
str.replace "STRING"
str # => "STRING", 元の値が上書きされている

また、Railsだと例外を発生させるかどうかで!を付けたり付けなかったりすることがあります。

hoge.save # 保存に失敗した場合falseを返す
hoge.save! # 保存に失敗した場合例外を発生させる

「存在しなかった」メソッド

Rubyはメッセージに対応するメソッドを見つけられなかったとしても諦めません。 その場合は、見つけられなかったメソッド名と引数と共に、 method_missingメソッドを呼び出します。 method_missingメソッドはデフォルトではNameError例外を投げますが、 アプリケーションに合うように再定義することもできます。

Rubyではメソッドが見つからなかった場合の処理を簡単に上書きできます。 アプリケーションコードで多用することはないですが、ライブラリではよく使われている仕組みなので知っておくと良いです。

class Hoge; end
hoge = Hoge.new
hoge.fuga # undefined method `fuga' for an instance of Hoge (NoMethodError)

# method_missingを上書き
def hoge.method_missing(name, *args)
  puts "メソッドが見つかりませんでした: #{name}"
end
hoge.fuga # メソッドが見つかりませんでした: fuga

演算子は糖衣構文(シンタックスシュガー)

Rubyにおけるほとんどの演算子は糖衣構文です。 いくつかの優先順位規則にもとづいて、メソッド呼び出しを単に書き換えているだけです。 たとえば、Integerクラスの+メソッドを次のようにオーバーライドすることもできます。

class Integer
  # できるけれど、しないほうがいいでしょう
  def +(other)
    self - other
  end
end

Rubyでは"+"もメソッドです。面白い仕組みなので紹介しました。

メソッドなので次のような呼び方ができます。

1 + 2 # => 3
# 1(Integerのインスタンス)の+メソッドの引数に2を渡している
1.+(2) # => 3

Ruby 3.2 リファレンスマニュアル > オブジェクト

Ruby で扱える全ての値はオブジェクトです。

「全ての値はオブジェクトです」はRubyの特徴だと思うので説明しました。

クラスをインスタンス化したものがオブジェクトなのは理解しやすいですが、数値や文字列などのリテラルもオブジェクトであり、クラス自体もオブジェクトです。

全てオブジェクトなのでメソッド呼び出しや既存メソッドの挙動を上書きなどを行えます。

Ruby 3.2 リファレンスマニュアル > クラス/メソッドの定義

ここでは次の項目について説明しました。

  • クラス定義
  • モジュール定義
    • クラスとの違いがわかりづらいので説明
    • インスタンス化できず、共通の振る舞いを複数のクラスで共有したい場合などに使うことが多い
  • メソッド定義
    • 引数がなければ()を省略できる
  • 特異クラス定義 / 特異メソッド定義 / クラスメソッドの定義
    • Railsのモデルで多用するので説明

Ruby 3.2 リファレンスマニュアル > 制御構造

if文など基本的な制御構造やunlessなどRubyの特徴的なところを紹介しました。 また、forは他の言語では多用するがRubyではあまり使わずeachを使うことが多いということも説明しました。

Ruby 3.2 リファレンスマニュアル > 変数と定数

よく使うローカル変数、インスタンス変数、定数を説明しました。

Ruby 3.2 リファレンスマニュアル > リテラル

さらっと全体を眺めつつ「シンボル」「ハッシュ式」「ヒアドキュメント」「%記法」は初見だと戸惑うポイントなので詳しく説明しました。

ハッシュ式

キーとバリューのペアを保持するオブジェクト。 キーには数値・文字列・シンボルが使える。キーにはシンボルを使うことが一般的。

{ 1 => 2, 2 => 4, 3 => 6}
{ "a" => "A", "b" => "B", "c" => "C" }
{ :a => "A", :b => "B", :c => "C" }
{ a: "A", b: "B", c: "C" } # シンボルの場合、このような書き方ができる。この書き方が推奨。

ヒアドキュメント

複数行の文字列を表示するときに使う。

<<
<<-: 終端子にインデントをかける
<<~: インデントをいい感じに削ってくれる

Railsガイドの紹介

Railsガイドについてはこの勉強会では紹介だけ行いました。 Railsの機能が網羅されており、最新バージョンに追従しているとてもよいドキュメントなので、困ったらまずはRailsガイドを読むことをおすすめしました。

railsguides.jp

Railsの構成を説明

実際に開発しているバックエンドのディレクトリ構成を説明しました。

  • Gemfile / Gemfile.lock
    • package.jsonみたいにライブラリを管理しているもの
  • Dockerfile / Dockerfile.dev / compose.yml
    • .devは開発環境用
    • compose.ymlはdocker composeの設定ファイル
  • config
    • 各種設定ファイル
  • db
    • データベーススキーマやマイグレーションファイル
  • log
    • ログファイル
    • dockerで実行している場合、ここに出力されないのでdocker compose logs -f webで確認
  • lib
    • 本体とは切り離して使えるライブラリが格納されている
    • taskにコマンド実行するタスク(ジョブ)が格納されている
  • app
    • admin
      • 管理画面用のgem activeadminで使うクラス
    • controllers
      • MVCのC
    • enums
      • localeやメトリクスのenum情報
    • graphql
      • GraphQLのgem graphql-rubyのquery/mutation/typeなど
    • interactors
      • interactor gemを使ってCUDのビジネスロジックを実装
      • Controller -> Interactor -> Modelの依存関係
    • jobs
      • 非同期処理
    • mailers
      • メール送信処理
    • models
      • MVCのM
      • DBアクセスクラスが一般的だが、分析クラスや外部接続クラスなども入っている
    • views
      • MVCのV

バックエンドのライブコーディング

RubyやRailsの基礎的な部分は抑えたので、ここからは実際にバックエンドのコードを使ってライブコーディングを行いました。

既存のGraphQLのQueryにfieldを追加

フロントエンドを開発しているときにバックエンドを触りたくなる最も多いケースは、APIのインターフェースの変更だと思います。 そこで、既存のGraphQLのQueryにfieldを追加するというテーマでライブコーディングを行いました。

GraphQLの新規作成

次はよりがっつりバックエンドを触るケースとして、次の仕様を満たすGraphQLをライブコーディングしました。 このテーマでAPIを作成するときに必要となるDBのテーブル作成・Model作成・GraphQL作成の一連の流れを学びました。

  • userに紐づくメモを保存する機能
    • ユーザーごとにメモは複数作成可能
    • 1つのメモは100文字まで
    • 空のメモはNG
    • メモは1ユーザー10件まで
  • ユーザーIDに紐づくメモを取得できる
    • テキスト検索できる
  • メモIDに紐づくメモを取得できる
  • メモを新規登録できる
  • メモを更新できる
    • 自分のメモしか更新できない

DBの基礎

バックエンドの学習を進めるうち、データベースの知識を底上げする必要があると感じたため、ここからはデータベースについて学びました。

SQL実習

既存のテーブルを使って、お題を取得するSQLを書いてもらいました。

  • 単一テーブルのSELECT
  • JOINを使ったSELECT
  • 複数のテーブルをJOINするSELECT
  • INNER JOIN、LEFT JOINの使い分け
  • GROUP BY、HAVINGを使った集計

SLQ実習で書いたSQLをActiveRecordで書く

Railsで開発する場合、生のSQLを書かずActiveRecord経由でDBにアクセスするので、SQL実習で書いたSQLをActiveRecordで書いてもらいました。

N+1問題

N+1問題について説明し、RailsでN+1を回避するプラクティスを紹介しました。

  • preload / eager_load / includes
    • 結合が不要な場合はpreload、joinするときはeager_load(left outer join)
    • includesはpreload or eager_loadを自動判断する。
      • 意図せずクエリーが変わってしまうことがあるので使わない方が無難
      • 経験上、includesで発行するSQLが変わって困ったことはあってもincludes禁止で困ったことはない
    • Rails始めた人が絶対辿り着く記事
  • GraphQLではpreloadやeager_loadを使った先読みが適さないこともあるので注意

正規形

リレーショナルデータベースを使う場合、第3正規形までは必修だと思うので非正規形と第1〜3正規形を説明しました。

インデックス、実行計画

テーブル設計をするようになったらインデックスの検討も必須なので説明しました。

インデックスはとても良いスライドがあるのでこちらを共有しました。

speakerdeck.com

また、発行したSQLがどのインデックスを使っているのか判断する手法としてEXPLAINを使う方法を紹介しました。

トランザクションとロック

トランザクションは更新系の処理を実装する時に必須なのでRailsでトランザクションを使う方法を説明しました。

transaction do
  # この中の更新処理は1つでも失敗したら全部ロールバックされる
  # データの整合性を保つために更新系の実装では考慮が必要
end

Railsを使ってWebアプリケーションを作っている場合、明示的にロックを意識することは少ないのですが、概念として楽観的ロックと悲観的ロックについて軽く説明しました。

外部キー

外部キーについてもMySQLのドキュメントをなぞりながら軽く触れました。 特にON DELETEは便利なので活用していきたい機能の1つです。

dev.mysql.com

まとめ

バックエンド勉強会で実施した内容をざっくりではありますが紹介しました。

勉強会に参加したメンバーから「勉強会後からバックエンドのコードがスムーズに読めるようになった」などの感想をいただくことができました。さらに次の機能開発でバックエンドに挑戦するメンバーもいたので開催して本当に良かったと感じています。

次の画像は参加したメンバーのバックエンドのプルリクをFindy Team+で表示したものです。GraphQLのQueryやMutationの追加や、フィールドの追加・修正などを行なったことがわかります。

参加者のプルリク抜粋


ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers

Nx活用術!モノレポ内のStorybookのパス設定自動化

ファインディ株式会社でフロントエンドのリードをしている新福(@puku0x)です。

この記事はFindy Advent Calendar 2024 4日目の記事です。

adventar.org

Nxはモノレポ管理の便利なユーティリティとして @nx/devkit を提供しています。

今回は @nx/devkit を利用したStorybookの設定の自動化についてご紹介します。

Nxについては以前の記事で紹介しておりますので、気になる方は是非ご覧ください。

tech.findy.co.jp

モノレポでStorybookをどのように管理するか?

皆さんはモノレポでStorybookを運用されたことはありますでしょうか?

概ねどちらかの方法を採用することになるかと思います。

前者はStorybookのデプロイ設定をシンプルにできますが、全プロジェクトのStorybookへのパスを記述する必要があります。後者は設定がやや複雑であり、プロジェクト毎にStorybookのデプロイ設定も必要です。

Findyのフロントエンドはモノレポで管理されており、フィーチャ単位に細分化された多数のプロジェクトを持つという性質と運用の難易度を考慮し、前者の手法を選びました。

どうやってパス設定を自動化するか?

単一のStorybookで集中管理する際に課題となるのは↓の部分でしょう。

// .storybook/main.js
const config = {
  addons: ['@storybook/addon-essentials'],
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
  stories:[ // 👈 ここ!👇
    '../apps/app1/src',
    '../libs/components/src',
    '../libs/app1/feature-a/src',
    '../libs/app1/feature-b/src',
    '../libs/app1/feature-c/src',
    // 以下、全プロジェクトのパスが続きます
    // :
  ]
};

export default config;

数個程度であれば十分に運用できそうですが、モノレポ上となると話が違ってきます。

ちなみに、Findyのフロントエンドではプロジェクト数は 70 個でした。
※他プロダクトでは100個近くになる場合もあります

「え?これ全部手動で管理するんですか!?」

さすがに手動での管理には限界がありますので、ここはNxの力を借りましょう。

使用するのは createProjectGraphAsync というユーティリティです。

createProjectGraphAsync | Nx

Nxは各プロジェクトの依存関係を保持しており、そこからStorybookのパスを算出できます。

最終的に次のようなものを目指します。

// .storybook/main.js
const config = {
  addons: [...],
  framework: {...},
  stories: getStories(),  // https://storybook.js.org/docs/configure#with-a-custom-implementation
  // 👇 こんな感じの配列を返して欲しい
  // [
  //   { titlePrefix: 'app1',  directory: '../apps/app1/feature-a/src' },
  //   { titlePrefix: 'app1-feature-a',  directory: '../libs/app1/feature-a/src' },
  //   { titlePrefix: 'app1-feature-b',  directory: '../libs/app1/feature-b/src' },
  //   { titlePrefix: 'app1-feature-c',  directory: '../libs/app1/feature-c/src' },
  //   ...
  // ]
};

export default config;

実装してみよう!

方針が決まったところで実装していきましょう。

事前の準備として、Storybookのパスを取得したいプロジェクトに tags を設定しておきましょう。Nxの @nx/enforce-module-boundaries ルールによる依存の制御を導入している場合は自然と設定されてあると思います。

// apps/app1/project.json
{
  "name": "app1",
  "tags": ["scope:app1"],
  ...
}
// libs/app1/feature-a/project.json
{
  "name": "app1-feature-a",
  "tags": ["scope:app1", "type:feature"],
  ...
}

nx.dev

@nx/devkitcreateProjectGraphAsync を利用して、各プロジェクトの name および sourceRoot を取得します。

// apps/app1/.storybook/main.js
import { createProjectGraphAsync } from '@nx/devkit';

const getStories = async () => {
  const graph = await createProjectGraphAsync();

  // Storybookが不要なプロジェクトは無視
  const ignoredProjects = ['app1-e2e', 'app1-utils'];

  return Object.keys(graph.nodes)
    .filter((key) => graph.nodes[key].data.tags?.includes('scope:app1'))  // 関連するStorybookの絞り込み
    .filter((key) => !ignoredProjects.includes(key))
    .map((key) => {
      const project = graph.nodes[key].data;
      return {
        titlePrefix: project.name,
        directory: `../../../${project.sourceRoot}`,
      };
    });
};

あとはこれを stories に渡せば完成です。

// apps/app1/.storybook/main.js
const config = {
  addons: ['@storybook/addon-essentials'],
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
  stories: getStories(),  // 👈 プロジェクトの追加・削除に応じて自動的に設定されます
};

export default config;

Storybookが表示されました!

Nxによる自動化のもう1つのメリットは、↓のように titlePrefix にプロジェクト名を関連付けることで、フィーチャ毎の分類がより明確になる点だと思います。

const project = graph.nodes[key].data;
return {
  titlePrefix: project.name,
  directory: `../../../${project.sourceRoot}`,
};

開発メンバーからは、

「モノレポ構造とStoryの構造がリンクすることで画面の使い勝手が非常に良い」
「検索したときにStory名が同じでもどの階層にいるか判断して目的にたどり着ける」

といったフィードバックを受けることができました👏 検索性の向上に一役買えましたね!

今回のサンプルは次のリポジトリから動作を確認できます。是非お試しください。

github.com

まとめ

この記事では @nx/devkit を利用したStorybookの設定の自動化についてご紹介しました。

Nxの機能を活用すれば「モノレポにプロジェクトを追加した後のStorybookの設定が漏れていた!」といった事とは無縁になるでしょう。

検証した時点では、@storybook/test-runnerstories をAsync Functionで渡すパターンとの相性がまだ悪いようでした。今後の更新に期待したいですね。

今回はStorybookとの組み合わせでしたが、同じ仕組みを使ってGraphQL Codegenの設定自動化も可能であると確認しています。また別の機会にご紹介できればと思います。

それではまた次回!


ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers