Timee Product Team Blog

タイミー開発者ブログ

GitHubマージキューTIPS:CIの実行を最適化し、障害対応を10分高速化する

こんにちは、タイミーでバックエンドのテックリードをしている新谷 (@euglena1215) です。

このシリーズでは「モノリスRailsにマージキューを導入してデプロイフローを安定させる」の続編として、導入時に工夫した点や直面した課題をTIPS形式で紹介しています。前回の記事では「GitHubマージキューの制約:マージメソッドが1つに強制される」について解説しました。

今回は、マージキューの特性を活かしてCIの実行方法を最適化し、特に緊急時のデプロイを高速化した話を紹介したいと思います。

Pull RequestにおけるCIは本当に必須か?

マージキューの最も基本的な機能は、エンキューされたPull Request(PR)を常に最新の master ブランチに取り込んだ状態でCIを実行することです。これによりmaster ブランチのCI成功が保証されるため、私たちはまず「master ブランチへのマージ後のCI」を不要と判断しました。

さらに、私たちはもう一歩踏み込んで考えました。

「そもそも、PR上でのCI実行も必須ではなくなったのでは?」

仮にPR単体でCIが失敗したとしても、その変更はマージキューのCIで検知され、キューから自動的に取り除かれます。master ブランチが壊れることはないため、PR作成時のCIはあくまで開発者のための任意実行とし、マージをブロックする「必須チェック」から外せるのではないか、という発想です。

CIを「必須」と「任意」に分ける方法

この「PRでのCIを必須としない」運用を実現するには、一つ技術的な壁がありました。

GitHubの仕様上の壁

GitHubの仕様では、PRの必須チェック(Required Check)とマージキューの必須チェックを分けることができません。マージキューでマージ前にチェックしたいCIはPRでも常に実行する必要があります。
こちらはマージキューへのフィードバックとして挙がっていますが、2025年7月時点では対応されていませんでした。

ref. https://github.com/orgs/community/discussions/46757#discussioncomment-4912738

PRでの必須チェックを外すためにチェックを外すと、マージキューでも必須ではなくなってしまい、CIが実行されないままマージされる問題が生じます。

ワークフロー分割による解決策

そこで我々は、CIのコアロジックを再利用可能なワークフロー(_ci.yml)として切り出し、それを呼び出す2つのワークフローに分割することで、この問題を解決しました。

1. ci.yml(マージキューでの必須CI)

これをブランチ保護ルールの必須チェックに設定します。このワークフローはpushイベントとmerge_groupイベントの両方でトリガーされますが、github.event_nameを判定し、merge_groupイベントでない場合はskip: trueを渡すことで、CIの実行をスキップします。

これにより、「必須チェックとしては存在するが、PR上では即座に完了(スキップ)し、マージキューでのみ実行される」状態を作り出せます。

# .github/workflows/ci.yml
name: ci

on:
  push:
    branches:
      - '**'
      - '!master'
  merge_group:

jobs:
  ci:
    # ...
    uses: ./.github/workflows/_ci.yml
    with:
      # merge_group イベントでない場合は true を渡し、ワークフローをスキップさせる
      skip: ${{ github.event_name != 'merge_group' }}
    secrets: inherit

2. ci_branch.yml(PRでの任意CI)

こちらは必須チェックには設定しません。pushイベントでのみトリガーされ、常にskip: falseを渡してCIを実行します。開発者はこのCIの結果を参考にしますが、完了を待たずにエンキューできます。

# .github/workflows/ci_branch.yml
name: ci branch

on:
  push:
    branches:
      - '**'
      - '!master'

jobs:
  ci:
    uses: ./.github/workflows/_ci.yml
    with:
      # こちらは常に false を渡し、CIを実行させる
      skip: false
    secrets: inherit

この構成により、「PR上ではCI実行は必須ではないが、マージキューではCI実行が必須」という状態を実現しました。

実行結果を確認する

挙動を確認するために、実行結果を確認してみましょう。

PR上では、ci.yml と ci_branch.yml が実行されます。必須であるci.ymlはほとんどのステップがskipされているので全体が数秒で確認できます。対して、ci_branch.ymlでは数分かかっていますが、必須ではないので実行途中でもマージキューにエンキューできます。

PR上でのci.ymlの実行結果

PR上でのci_branch.ymlの実行結果

対して、マージキュー上ではci.ymlのみが実行されます。マージキューで実行した場合、ci.ymlはskipされずに実行されるので、CIが失敗しているのにmasterブランチにマージされてしまうといったリスクはありません。

マージキュー上でのci.ymlの実行結果

障害対応のデプロイを10分短縮

この最適化は、特に緊急の修正(Revertなど)をデプロイする際に大きな効果を発揮します。

  • 変更前
    • featureブランチのCI(10分) + masterマージ後のCI(10分) + デプロイ(5分) = 合計25分
  • 変更後
    • マージキューのCI(10分) + デプロイ(5分) = 合計15分

結果として、障害発生から復旧までの時間を10分短縮できました。

まとめ

マージキューはmaster ブランチを安定させるための機能ですが、その特性をうまく利用し、CIの実行方法を工夫することで、デプロイの安定化とデプロイの高速化を両立させることができました。まさに一石二鳥の改善だったと感じています。

次回は、マージキュー導入時に発覚した、GitHubのブランチ保護機能(Ruleset)との思わぬ非互換性について紹介します。

GitHubマージキューの制約:マージメソッドが1つに強制される

こんにちは、タイミーでバックエンドのテックリードをしている新谷 (@euglena1215) です。

先日公開した記事 「モノリスRailsにマージキューを導入してデプロイフローを安定させる」 では、多くの開発者が関わるモノリスリポジトリのデプロイフローを安定させるために、GitHubのマージキューを導入した事例を紹介しました。

今回からは、導入にあたって直面した課題や、我々の開発プロセスにうまく組み込むために工夫した点などを、TIPSとして何回かに分けて紹介していきます。

シリーズ最初のトピックは、マージキューを導入すると直面するマージメソッドの制約についてです。

開発者の自由がなくなる?マージフローの変化

マージキューを導入すると、開発者のマージ作業のフローが大きく変わります。最も大きな変化は、開発者が直接「Merge」ボタンを押すのではなく、「Merge when ready」ボタンでエンキュー(マージキューに追加)する点です。

エンキューされたPull Requestのマージ作業そのものは、開発者ではなくbotが内部的に自動的に行うようになります。

マージメソッドを指定できない仕様

2025年7月時点でのGitHubマージキューの仕様では、各開発者がエンキュー後のマージメソッドを選ぶことはできません。代わりに、リポジトリの設定として、どのマージメソッド(Merge commit, Squash and merge, Rebase and merge)を使うかを指定しておく必要があります。

結果として、これまで開発者が個人の好みでマージメソッドを選んでいた運用はできなくなり、全員が同じメソッドを使うことを強制されます。

我々が行った選択とそのプロセス

マージキューを導入するにあたり、我々は Merge commitSquash and merge のどちらに統一するか、という選択を迫られました。

各選択肢の課題の比較

それぞれの設定をデフォルトにした場合に、開発者にどのような影響が出るかを比較検討しました。

  • Squash and merge を選択した場合の課題:
    意図的に複数のコミットに分けているプルリクエストが、マージ時に強制的に1つにまとめられてしまいます。さらに、GitHubのUI上で自動生成されるコミットメッセージは、個々のコミットメッセージを単純に連結したものになり、マージ時にメッセージをきれいに整形することができません。
  • Merge commit を選択した場合の課題:
    これまで feature branch 上で作業途中のコミットを細かく積み、マージ時にUI上で1つにまとめていた開発者にとっては、整形前のコミットがそのまま master ブランチの履歴に残ってしまうことになります。

我々の決定:「Merge commit」の採用と運用によるカバー

両者を比較した結果、我々はリポジトリのデフォルト設定として Merge commit の採用を決定しました。
決め手は、Squash and merge を選択した際の「コミットメッセージが適切に整形できない」という制約の影響が大きいと判断したためです。

その上で、Merge commit を選択した場合の課題(整形前のコミットが残ってしまう)については、運用でカバーする方針としました。
具体的には、コミットを1つにまとめたい開発者に対し、「マージキューに入れる前に、feature branch 上で git rebase -i などを使って事前にコミットを整理してもらう」という協力をお願いすることにしました。

この運用により、意図したコミット履歴をそのまま残したいケースと、複数のコミットを1つにまとめてマージしたいケース、その両方に対応できると考えました。
この決定の背景と具体的な運用ルールをドキュメントにまとめ、開発者全員に協力を依頼することで、スムーズな移行を目指しました。

まとめ

GitHubのマージキューはデプロイフローを安定させる有用なツールですが、一方で、チームの開発スタイルに影響を与える制約も存在します。特に、マージメソッドが1種類に固定される点は、導入前にチーム内でコンセンサスを取っておくべき重要なポイントです。

この「マージメソッドが強制的に固定される」という仕様は、見落とされがちなポイントだと思いましたので、最初のTIPSとして紹介しました。

次回は、マージキューの特性を活かして、障害対応時のCI待ち時間を短縮した工夫についてご紹介します。

マージキュー対応の概要

GitHubマージキュー導入時のGitHub Actions CI設定変更

こんにちは、タイミーでPlatform Engineerをしている近藤です。
今回は、GitHubのマージキュー(Merge Queue)に対応するために、GitHub ActionsのCI設定を修正した話を紹介します。

TL;DR

GitHubのマージキュー(Merge Queue)を導入する場合、以下の設定が必要です。

  1. Rule Sets(Branch protection rule)で「Require status checks to pass」を有効化
  2. 「Merge Queue」の有効化
  3. CIワークフロー(ci.yml)の変更
    • merge_group イベントの追加
    • gh-readonly-queue/** ブランチをpushイベントから除外

GitHub Actionsの従来のCIワークフロー

まず、従来のCIワークフロー(ci.yml)の設定を振り返ります。元々の設定は以下のようになっていました。

name: ci

on:
  push:
    branches:
      - '**'
      - '!master'
    tags-ignore:
      - '*'

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  id-token: write
  contents: read
  pull-requests: write

jobs:
  ci:
    uses: ./.github/workflows/_ci.yml
    secrets: inherit

この設定では、masterブランチ以外のすべてのpushイベントでCIが実行されます。

デプロイまでの従来の流れ

タイミーのdeployワークフローはGitHub Flowを採用していたため、以下のような流れになっていました。

  1. PRを作成し、CIが通ることを確認。
  2. PRをmasterブランチにマージ。
  3. マージによってmasterにpushが発生し、デプロイ用ワークフロー(deploy_prod.yml)が実行される。

deploy_prod.ymlは次のようになっています。

name: deploy_prod

on:
  push:
    branches:
      - master

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      # デプロイ処理...

GitHubでの設定変更(Rule Sets)

マージキューを使うには、GitHubの設定画面から「Rule Sets」(従来はブランチプロテクションルール)を設定します。 多くのケースではすでにステータスチェック必須(Require status checks to pass)の設定がされていると思いますので、その場合は「Merge Queue」を追加でONにするだけです。

GitHub ActionsのCIワークフロー変更

PRからマージキューに積まれた際、gh-readonly-queue/xxxxというブランチが作成されるため、CIワークフロー(ci.yml)に以下の変更が必要になりました。

name: ci
 
 on:
   push:
     branches:
       - '**'
       - '!master'
+      - '!gh-readonly-queue/**'
     tags-ignore:
       - '*'
+  merge_group:
 
 concurrency:
   group: ${{ github.workflow }}-${{ github.ref }}
   cancel-in-progress: true
 
 permissions:
   id-token: write
   contents: read
   pull-requests: write
 
 jobs:
   ci:
     uses: ./.github/workflows/_ci.yml
     secrets: inherit

ポイント

  • merge_groupイベントを追加することで、マージキューにエンキューされたときにCIが実行されます。
  • gh-readonly-queue/**を除外することで、不要な二重実行を防ぎます。

注意点

merge_groupイベントは単独で定義できず、必ず他のイベントと一緒に定義する必要があります。

GitHub公式ドキュメント - merge_group

The merge_group event cannot be the only event defined in a workflow.

変更が不要なワークフロー

デプロイ系ワークフロー(例:deploy_prod.yml)など、masterブランチへのpushのみで動作するワークフローは、マージキュー対応後も変更不要です。

おわりに

GitHubのマージキューは導入が比較的簡単ですが、既存のGitHub Actionsにも影響を与えます。本記事を参考に、スムーズにマージキューを導入していただければ幸いです。 最後までお読みいただき、ありがとうございました!

モノリスRailsにマージキューを導入してデプロイフローを安定させる

こんにちは、タイミーでバックエンドのテックリードをしている新谷 (@euglena1215) です。

タイミーのバックエンドは、多くの開発者が関わるモノリスなRailsアプリケーションを中心に構成されています。本記事では、このモノリスリポジトリのデプロイフローを安定させるためにGitHubのマージキューを導入した事例を紹介します。

本記事が、masterブランチにマージしたコードでCIが頻繁に失敗し、開発プロセスに影響が出ているチームの参考になれば幸いです。

masterブランチのCIが頻繁に失敗していた

タイミーのモノリスRailsリポジトリでは、1日に20件以上のPull Requestがマージされることもあります。多くの開発者が同時に開発を進める中で、ある問題が頻発していました。

それは「他の人の変更がmasterブランチに取り込まれたことに気付かず、古いmasterを元にしたPull Requestをマージしてしまい、結果的にmasterブランチでCIが失敗する」という事象です。

masterブランチでCIが失敗する図

この事象により、開発チームに次のような影響が出ていました。

  • リリース遅延
    • masterが一度CIに失敗すると、原因の特定と修正(Revertなど)が終わるまで、誰もデプロイできなくなります。これにより、ユーザーに届けたい価値をタイムリーに届けられない事態が発生していました。
  • コンテキストスイッチとコミュニケーションコストの増大
    • masterのCI失敗は、Slackでの全体周知、原因調査の依頼、マージの一時停止呼びかけなど、多くの開発者を巻き込むコミュニケーションを発生させ、チーム全体の生産性を低下させていました。

実際に、2025年のある2ヶ月間を調査したところ、masterブランチでのCI失敗のうち、最新のmasterを取り込んでいれば防げた可能性が高いものは6件ありました。この課題を解決するため、我々は解決策の検討を開始しました。

なぜ「マージキュー」だったのか?

この問題を解決するにあたり、我々はGitHubマージキューを選択しました。その背景をご説明します。

まず、解決策として考えられる「すべてのPull Requestは、マージ直前に必ず手動で最新のmasterを取り込む」というルール運用は、1日に20件以上もマージされるリポジトリでは現実的ではありませんでした。CIの待ち時間や再レビューのコストを考えると、開発速度の低下は避けられないと判断しました。

一方で、タイミーでは既にGitHub Enterprise Cloudを導入済みでした。そのため、マージキューは追加コストや新たなセキュリティレビューなしに利用できる機能でした。

既存のプラットフォームの機能を活用することは、我々にとって合理的な判断でした。そのため、他のサードパーティツールを比較検討することはせず、マージキューの導入にフォーカスして調査を進めることにしました。

解決策:GitHubマージキューとは?

GitHubのマージキューを改めて説明すると「masterブランチにマージされる前に、最新のmasterと合体させた状態でCIを実行し、成功したものだけを自動でマージする仕組み」です。

開発者がPull Requestをマージキューに追加すると、GitHubは内部的に一時的なブランチを作成し、その時点での最新のmasterとPRの変更をマージしてCIを実行します。CIが成功すれば、その変更がmasterにマージされる、という仕組みです。

【重要】GitHub Enterprise Cloudが前提
繰り返しになりますが、GitHubのマージキューはprivateリポジトリで利用する場合、GitHub TeamsプランではなくGitHub Enterprise Cloudプランの契約が必要となります。

詳しくは公式ドキュメントをご覧ください。

docs.github.com

まとめ

GitHubマージキューを導入したことで、課題であった「古いmasterとの競合によるCI失敗」はなくなり、masterブランチがCIの成功した状態を維持できるようになりました。

この結果は、ただ機能を有効化しただけで得られたものではありません。私たちは、本格導入に先立ち、マージキューをテスト環境で検証しました。その結果、ただ有効化するだけでは我々の開発フローとの互換性を保つのが難しく、いくつかのチューニングが必要なポイントが明らかになりました。

これらのポイントに事前に対処したことが、スムーズな導入の鍵となりました。

次回の記事では、この導入を成功に導いた、具体的な検証内容と、それに基づいて我々が実装したチューニング方法(GitHub Actionsのワークフロー設定など)について、詳しく解説していきたいと思います。

tech.timee.co.jp

tech.timee.co.jp

tech.timee.co.jp

tech.timee.co.jp

tech.timee.co.jp

tech.timee.co.jp

さよなら Flaky Test!Devinと共に実現する、CI安定化への道

  • タイミーでは、Flaky Test がデプロイの妨げになることで開発効率が悪化していました
  • この問題を解決するため、AI エージェント「Devin」を活用し、Flaky Test の検出から修正プルリクエストの作成までを完全に自動化しました
  • 結果、CIは安定し、開発者は本来の業務に集中できるようになったことで、開発体験が向上しました

こんにちは!タイミーでバックエンドエンジニアとして働いている 福井 (bary822) です。

皆さんは Flaky Test に悩まされた経験はないでしょうか?

タイミーでも、Flaky Test によって CI の信頼性が低下し、開発者の貴重な時間を奪ってしまうという課題を抱えていました。

ある期間においては master ブランチにおけるテスト実行の 4.5% が Flaky Test によって失敗しており、20+回/日 の頻度でデプロイされていることを考えると1日に1名は CI を re-run せざるを得ない状態に陥っていました。

この記事では、根深い Flaky Test 問題を解決するために、Devin を活用して修正プルリクエストの作成を自動化し、CI の安定化と開発体験の向上を実現した取り組みについてご紹介します。

私たちが抱えていた課題

Flaky Test を放置すると開発チーム全体に様々な悪影響を及ぼします。私たちが直面していた主な課題は以下の通りです。

  • 開発効率の悪化: 問題ないはずの Pull Request の CI が Flaky Test によって失敗すると、開発者は本来不要な原因調査や CI の再実行に時間を費やすことになります。多くの人にとって一定の緊張が発生するデプロイ作業の一環として実行される CI が失敗するというのは精神的にも大きな負担となります。
  • デプロイの遅延: master ブランチの CI が失敗すると、デプロイ担当者はそれが Flaky Test によるものかどうかを切り分ける調査を強いられていました。 Flaky Test だった場合は再実行によってその場を凌ぐことが常態化しており、迅速な価値提供の妨げとなっていました。これは、ビジネスの機会損失にも繋がりかねません。
  • CI における品質保証の機能不全: テストの成否が不安定なため、master ブランチで既存機能が正しく動作することが保証されているかどうかを正確に判断できなくなります。私たちの場合はその多くが「テストは正しく書かれているが何らかの不安定な要因によって失敗することがある」ケースだったため、実質的な品質の低下はほとんど発生していませんでした。しかし、その反対(本来失敗すべきなのにたまたまパスしてしまう)が発生していたとしても「また Flaky か…」と見逃されてしまいコードの修正が遅れる可能性がありました。

これらの課題を解決し、開発者全員が安心して高速に開発を進められる環境を作るため、私たちは Flaky Test の早期発見と修正を自動化する仕組みの構築に乗り出しました。

解決策:Devin による Flaky Test 修正の完全自動化

私たちは、以下のステップで Flaky Test の特定から修正までを自動化する仕組みを考案しました。

  1. Flaky Test の自動検出: CI 環境で実行されるテストの実行データを Datadog (Test Optimization)に送信し、Flaky Test を自動で検出する
  2. Devin による修正PRの作成: 新しい Flaky Test が検出されると、それをトリガーに GitHub Actions のワークフローを起動する。ワークフローは、テストファイル名やエラーメッセージなどの情報をプロンプトに含めて API 経由で Devin に渡し、修正プルリクエストの作成を依頼する。
  3. レビューとマージ: Devin によって作成されたプルリクエストは、 CODEOWNERS の設定に基づいて適切なチームに自動でレビューが割り当てられます。チームは、Devin が提案した修正内容を確認し、必要に応じて変更を加え、マージします。

この仕組みの全体像は以下のようになっています。

Flaky Test の発見から修正の「叩き台」作成までを自動化する

このフローにより、Flaky Test の発生から修正提案までが完全に自動化され、開発者は Devin が作成した Pull Request をレビューするだけでよくなりました。

もちろん Flaky Test の発見自体が誤検知である可能性もあるため、その場合はチームの判断でクローズします。

また、Devin が誤った原因仮説に基づいた望ましくない修正を提案する場合もあるため、必要なら追加の変更を行ったり、新しい Pull Request を作成したりして柔軟に対応します。

いずれにせよ Flaky な状態が疑われるテストが発見されると、ほとんど同じタイミングで担当するチームがそれを認識し、修正の「叩き台」がすでに作成されている状態を実現することができました。

Devin はどのようにテストを修正するのか?

「Devin が本当に Flaky Test を修正できるのか?」と疑問に思う方もいるかもしれません。

私たちも最初は半信半疑でしたが、Devin は当初の期待を上回るほど的確な修正を提案してくれています。

これは、Devin に渡しているプロンプトの一部です。再現手順の特定や、 Pull Request の体裁、修正の勘所などを細かく指示しています。

レビュアーやラベルなど、Pull Request 上で設定する静的な項目に関しては期待値を宣言的に定義し、CLIコマンドの返り値によってその検証を行うように指示しているのが重要なポイントです。

こうすることで、Devin 自身がセルフチェックを行いやすくして求める出力を安定的に得られるようにしています。

以下の Flaky Test の修正を行い、${レポジトリ名} に Pull Request を作成してください。

# テスト情報
## テストファイル
${Flaky Test が発生したテストファイル名}

## テストケース
${Flaky Test が発生したテストケース名}

## エラーメッセージ(先頭20行)
${テスト失敗時のエラーメッセージとスタックトレース}

## 実行コマンド
${テストの実行コマンド} (CI環境ではテストファイルを分割して並列実行しているため、どのテストファイルをどの順番、Seed値で実行しているかの情報が必要)

## 問題発生時のコミット
${直前の Commit の SHA}

# 作業内容
1. テストコードの修正
2. 修正後、当該テストをローカル環境で5回実行し、全て成功することを確認
  - seed値を変えながらテストを実行する時は、1つ以上の他のテストファイルも同時に実行
  - 複数のテストファイルに渡ってランダムな順番でテストが実行されるようにしたい
3. 以下の要件を満たす Pull Request をDraftで作成
  - タイトル: flaky: (原因と修正内容を簡潔に記載)
  - コードオーナーの設定に従ってレビューリクエストを送信。コードオーナー不在の場合は ${コードオーナー不在の場合に対応する GitHub Team 名} に送信
4. CIが成功したら、Pull Request を Open に変更

# Pull Request の description に含める情報
  - 発生していた問題の概要
  - 修正内容とそれによって Flaky が解消されると判断した理由
  - Datadog 上のテスト結果ページのURL

# 重要な注意事項
## 制約
  - 修正内容は最小限に抑え、テストの意図を変更しないようにしてください。
  - 他の spec ファイルで使用されている既存のパターンに合わせた実装を行ってください
  - 可能な限り修正前に失敗するまで条件を変えながらテストを実行し、失敗したときの実行コマンドなどの再現手順を明記してください。
  - テストの失敗を再現するときには並列実行環境下で自然に発生し得る条件を設定してください。特定のデータを手動で作成するなどすることは禁止します。
  - テストの修正によって Flaky が解消されると判断した理由を明記してください。

## 検証コマンド
以下のコマンドで各項目が正しく設定されていることを確認してください:

### PRステータスが Open であることの確認
\`\`\`bash
gh api repos/${レポジトリ名}/pulls/{pr_number} --jq '.state, .draft'
# 期待値: "open" と false が表示される
\`\`\`

### レビュアーが追加されていることの確認
\`\`\`bash
# 変更したファイルにコードオーナーが設定されている場合
gh api repos/${レポジトリ名}/pulls/{pr_number}/requested_reviewers
# 期待値: teams配列に コードオーナーのTeam名 が含まれる

# 変更したファイルにコードオーナーが設定されていない場合
gh api repos/${レポジトリ名}/pulls/{pr_number}/requested_reviewers
# 期待値: teams配列に "${コードオーナー不在の場合に対応する GitHub Team 名}" が含まれる
\`\`\`

### devin-fix-flaky-test ラベルが設定されていることの確認
\`\`\`bash
gh api repos/${レポジトリ名}/issues/{pr_number}/labels
# 期待値: name が "devin-fix-flaky-test" のオブジェクトが含まれる
\`\`\`

# 参考情報
- テストはファイルごとに一定のまとまりで分割されて並列実行されており、ランダムに設定されたSeed値を使って実行順序を制御しています
- それぞれの実行環境では独立したデータベースが用意されており、実行環境間でデータが共有されないようにしています

このプロンプトに基づき、Devin は様々なパターンの Flaky Test を修正してくれました。

ここでは、その中から代表的な2つの例をご紹介します。

修正例1: バリデーションエラーによる不安定性の解消

開始、終了時間を定義する2つの DateTime 型のカラムによって計算される値に依存するバリデーションのテストにおいて、処理時間とタイミングによっては失敗するケースがありました。

Before:

context '...' do
  before do
    create(:offering, :just_working)
  end
end

offering はいわゆる「求人」を表現するモデルです。

just_working trait では稼働時間がちょうど6時間になるように設定されています。

trait :just_working do
  start_at { Time.current.ago(5.hours) }
  end_at { Time.current.since(1.hour) }
end

労働基準法では6時間を超えると45分以上の休憩が義務付けられます。この場合はギリギリ6時間なので休憩時間が0分でも offering レコードの保存に成功します。

しかし、開始・終了時間(start_atend_at )はそれが設定されるときの現在時刻に依存しているため、 start_at が設定された後のタイミングでちょうど1秒をまたいでしまうと、 end_at = start_at + 6時間 + 1秒 となってしまうため、休憩時間が設定されていないとバリデーションエラーが発生してしまいます。

ActiveRecord::RecordInvalid: 労働時間が6時間を超えています。法定休憩時間を満たすように休憩時間を設定してください。

After (Devinによる修正):

context '...' do
  before do
    create(:offering, :just_working, :with_rests) # :with_rests trait を追加
  end
end

Devinは、 with_rests trait を追加することで、6時間超の労働時間に対して法定休憩時間(45分以上)を自動的に設定するように修正しました。

これにより、バリデーションエラーを回避し、テストが安定して成功するようになりました。

修正例2:FactoryBot のランダムデータ生成による不安定性

FactoryBot で生成されるランダムなデータが、テスト対象のメソッドの条件分岐に影響を与えて Flaky になっていたケースがありました。

Before:

describe '...' do
  let(:client) { create(:client) }
  
  context '...' do
    let(:serializer) { BrandUserSerializer.new(brand_user, client_id: client.id) }
    let(:brand_user) { build_stubbed(:brand_user) } # brand_user に client が紐づく

    it { expect(serializer.kind).to eq :other }
  end
end

このときBrandUserSerializer#kind メソッドは次のように実装されていました。

def kind
  if @brand_user.client_id == @client_id
    :manual 
  else
    :other
  end
end

BrandUserSerializer のインスタンス生成時の第一引数として渡された brand_user に紐づく cliend_id と、第二引数として直接渡された client_id の値を比較し、同一であれば :manual を、そうでなければ :other を返します。

このテストでは異なる値が設定される :other を期待値としていました。しかし、build_stubbed では ID がランダムに設定されるため、ごく僅かな確率で第二引数として渡された client.id の値と一致することがあります。

この場合 .kind:manual を返すためテストが失敗してしまいます。

After (Devinによる修正):

describe '...' do
  let(:client) { create(:client) }
  
  context '...' do
    let(:serializer) { BrandUserSerializer.new(brand_user, client_id: client.id) }
    
    # 明示的に別の client を作成して確実に異なる client_id が返るようにする
    let(:other_client) { create(:client) }
    let(:brand_user) { build_stubbed(:brand_user, client: other_client) }

    it { expect(serializer.kind).to eq :other }
  end
end

Devinは、明示的に other_client を作成し、 brand_user に設定することで、確実に異なる client_id を持つようにしました。

この変更により、serializerの条件分岐で正しく :other が返されるようになり、テストが安定して成功するようになりました。

導入後の成果と今後の展望

これを書いている時点で 3件 の Flaky Test を修正する Pull Request が Devin によって作成されましたが、そのうち 2件 は開発者によって変更が加えられることがなくマージされました。

結果として、CI の成功率は安定し、開発者は Flaky Test による手戻りや不要な調査から解放され、より価値のある機能開発に集中できるようになりました。

Devin が作成した Pull Request を「叩き台」として、チーム内で修正方針を議論したり、追加の変更を加えたりといった、より建設的な活動も生まれていることも重要なポイントです。

今後は、さらにプロンプトを洗練させ、より複雑な Flaky Test にも対応できるように改善を続けていく予定です。また、今回の成功を足がかりに、Flaky Test 修正以外の開発プロセスにも積極的に AI を活用していくことを検討しています。

おわりに

今回は、 Devin を活用して Flaky Test の修正を自動化し、CI の安定化と開発体験の向上を実現した事例をご紹介しました。AI を開発ワークフローに組み込むことで、これまで人間が時間をかけて対応していた退屈な作業をなくし、より創造的な仕事に集中できる環境を作ることができます。

この記事が、同じように Flaky Test に悩む開発者の皆さんにとって、少しでも参考になれば幸いです。

最後までお読みいただき、ありがとうございました!

droidcon NYC 2025参加レポート🟢Hunachi編

はじめに

こんにちは。タイミーでAndroidエンジニアをしているHunachi(ふなち)です。

2025年6月25日から26日に開催されたdroidcon NYC 2025に参加してきました。このブログでは、droidcon NYCの雰囲気と、特に印象に残ったセッションについてご紹介します!

droidcon NYC 2025の雰囲気

参加者は700名以上、セッションは4つのステージで80以上と、DroidKaigiとほぼ同規模か少し小さいくらいのイベントでした。

主にアメリカで働くAndroidエンジニアが集まっており、私が話した方々はニューヨーク近郊だけでなく、アメリカ各地から数時間かけて来ている方がほとんどでした。弊社社員を除く日本からの参加者には出会いませんでした。

また、同じ会場でFlutterconも開催されており、Flutterエンジニアとの交流も楽しめました。

興味のあるセッションを多数聞けたのはもちろんのこと、海外のエンジニアの方々と交流できたのは貴重な経験でした。

英語をスラスラと喋れる訳ではないのですが、他の参加者の方々がとても優しく接してくださり、楽しい時間を過ごせました。Googleの社員の方やスピーカーの方に直接質問できる機会もあり、非常に有意義でした。

一緒にセッションを聞きいたりお話ししたりしてました!

ランチの時間でも他の参加者と交流することができました!

Day2には、前日に仲良くなった人たちと一緒にセッションを聞いたり、おしゃべりしたりできました。ランチの時間も他の参加者と交流することができ、自分から喋りかけに行けば他の参加者と楽しくコミュニケーションが取れる雰囲気でした 🤝

聞いてきたセッションの一部を紹介します!

Panel: The Future of Dependency Injection in Modern Android

このパネルディスカッションでは、現代のAndroid開発におけるDI(依存性注入)の未来について議論されていました。

  • KMP(Kotlin Multiplatform)では「Metro」というDIフレームワークがよく使われる。
  • DIの原則自体は複雑ではない。我々は問題解決に集中すべきで、DIのことを気にするのは良くない。
  • サービスロケーターは避けるべきで、依存関係はコンパイル時に解決すべき。
  • DIフレームワークは早めに導入を決定することが推奨で、移行にはコストがかかるため注意が必要。
  • CompositionLocalはグローバル変数のように使われることがあり、コンパイル時にエラーにならないためリスクがある。

など、AndroidやKMPの最前線を走っている方々からDIに関するお話を聞くことができて、とても勉強になりました。

Tackling Memory Issues on the Instagram Android App at Scale

InstagramのAndroidアプリにおける大規模なメモリ問題への対処法についてのセッションでした。

アプリでユーザーがコンテンツを編集中に落とさないための工夫や、メモリが足りずにアプリがクラッシュする時の調査方法(HPROFファイルを使った調査)の説明がありました。

特に印象的だったのは、メモリについて詳しくなりたいなら「Runtime.getRuntime()」や「/proc/meminfo」から取得できるデータなど見ると良いという細かなアドバイスがあったことです。大半の普通のAndroidエンジニアは一生触れないだろう内容ですが、前職でメモリと向き合ったことのある私にとってはとても興味が湧く内容でした。

The Future of Android...And How to Prepare For It

Androidの未来と、それに向けてどのように準備すべきかについてのパネルディスカッションでした。

  • AIの進化により、スクリプトだけで開発できる時代が来ており、将来的にはコードの70%がAIによって生成されるだろう。
  • エンジニアはより複雑な問題を解決するために働くようになってきている。
  • AIによるポジションの削減などもありますが、逆にAIのおかげで私たちもあらゆる分野で働けるようになるため、ポジティブな側面も多くある。
  • このようなカンファレンスでいろんな人と問題など色々なことについて話す等、エンジニア同士の交流も重要になってくる。
  • AIにより学ぶことも前より容易になっている。
  • 一般的な知識(いろんな言語や分野)を身につけることが大切。

といった白熱したトークが行われていました。

弊社でもAIの活用はとても推奨されているのでAIを使った開発が日常になりつつはあるものの、このパネルディスカッションを聞いて、私ももっとAIを活用して今までよりも多くの分野で、より多くの作業量をこなすことができる人材になりたいなと思いました。

Career Office Hours with Stacy Devino

Stacy Devino氏によるキャリアに関するオフィスアワーにも行きました。

キャリアを考える上で大切だと言われていたことのうち印象に残ったものは、

  • 経済的に安心できる状況を作る
  • AIでカバーできない範囲などユニークなスキルを持つ
  • 自分が何を楽しんで、何が得意かを認識する
  • ビジネスプロセスや効率化について学習する
  • レイオフの危機を感じたら、事前に準備する

の5つです。

レイオフが日本よりも身近なアメリカだからこその話も聞けてとても為になりました。

Server Driven UIs: the future of cross-platform rendering architectures

サーバー駆動型UI(SDUI)についてのセッションでした。

SDUIはUIのデータをサーバー側に持たせて、動的なUI変更を可能にする仕組みのことなのですが、個人的にとても親しみのある内容でした。

タイミーでも取り入れられると嬉しいなと思いました。

Google社員の方や、スピーカーの方に質問してきた

出発前に社内のAndroidメンバーに聞いてきて欲しいことがあるか意見を聞きました。

そこで出てきた意見や個人的に気になっていたことをGoogle社員の方や、登壇者の方に質問してきました。

社内で質問したいことや気になるセッションを聞いた時のmiroのボード

Googleの方に、"Ask Android" Office Hours の時間を使って質問をしました。

「Google的に、Adaptive layoutsを推してると思うけど、時間がないデザイナーが対応したいと思える情報ある?」

「Android用のMCPってどんなのがある?」

等の質問をしました。

Googleの方と2対1(Google社員の方2人、私1人)という贅沢な環境でたっぷり時間を使って質問することができました。また、Google I/Oの動画に出ていた方も数人おり、直接お話しできてとてもワクワクしました。Google社員の方がたくさんいるニューヨークだからこんな状況で質問できたと思うので、droidcon NYCに来た甲斐があったと思える瞬間の一つでした。

また、フリーの時間で、キャリアのセッションをしていたStacyさんという女性の方にキャリアと育児についての質問もさせていただきました。

自分にはまだ子供がいませんが、将来的に必要になるかもしれないアドバイスを多くもらえました。特に「子供を産んだり育てることは想像以上に大変だから金銭面でも時間の面でも余裕を持っておくことがオススメだよ。」「子供との時間、仕事両方大切だけど、その時々で優先順位を決めて行動しなさい。同時に両方を全力でするのは厳しいよ。」という言葉がとても心に響きました。

感想

初めての海外カンファレンスですごく緊張していたのですが、とっても楽しかったです!

私はまだ英語に苦手意識があったのですが、各自見たいセッションも異なるし、弊社メンバーで固まらずに別行動をしよう!となり、Day1の最初以外の大半の時間を別行動していました。このチャレンジのおかげで自分の力だけでも海外の方と仲良くなれるという自信をつけることができてとても嬉しかったです。

また、今回のdroidconはAIの話が多く、AIの影響による良い面や悪い面について考える良い機会にもなりました。上記で紹介していないのですが、Stacyさんのセッションで「AIの出現はComposeの出現と似たようなもので、怖がる必要はない。我々は変化に対応できる。」という話を聞き、ポジティブな意味でもっとAIと仲良くなれるように頑張ろうと思えました。

今回のカンファレンスで学んだことをこれからのキャリアや社内での振る舞いに活かしていきたいです。

関連記事

productpr.timee.co.jp

tech.timee.co.jp

おまけ

行くまでの準備

英語の勉強をしました。Native Campで会話をする練習をしたり、Duolingoで文法や単語を勉強しました。また、英語のドラマを毎日見て、英語に親しみを覚えるようにしました。

今年もGoogle I/Oの動画でモバイルに関連するものはほぼ全部見ました。去年より英語の勉強をした甲斐があり英語での視聴でもほぼ問題がなかったのでスラスラ見ることができました。

droidcon NYC 2025の自分用のアプリも作りました。作成時にまだ公式アプリがなかったためです。後から出てきたので用無しになりましたが、AIがどれくらい使いやすいのか知れる良い機会になりました。細かいエラーなどは手動で修正しつつ、大部分のコードをAIに書かせるというスタイルだと爆速でアプリが作れることがわかりました。この経験のおかげでdroidconの中で行われていたAIの議論にもよりついて行くことができた気がします!

観光

カンファレンス以外では、ニューヨーク市内で観光も楽しみました。

サブウェイなどのローカルな飲食店や、ローカルなスーパーも色々と行きました。

チャンククッキーが好きなので、色々なお店でクッキーを買って食べたりもしました。

どのお店の店員さんもわからないことも教えてくれたりととても優しく過ごしやすかったです。乗る電車がわからなくなった時も、駅員さんと他の乗客の方々がめちゃくちゃ丁寧に道を教えてくれて感動しました😭 

また機会があればニューヨークにもdroidconにも行きたいです!

“You’re Not an Android Developer Anymore” ── droidcon NYC 2025現地レポ

こんにちは!タイミーでAndroidを主軸にしながらプロダクトエンジニアをしている中川です。

6月25, 26日の2日間、ニューヨークのブルックリンで開催された droidcon 2025 NYCに参加してきました。

今回の参加には、個人的に強い動機がありました。

30歳になる節目に、日本だけでなく世界で自分がエンジニアとしてどの位置にいるのかを確かめたかったのです。

今回このような貴重な機会をいただけたのは、日頃から支えてくださっている社内の関係者の皆さまのご理解とご支援、そして不在の間、現場を支えてくれたチームメンバーのおかげです。心より感謝しています。

タイミーには、世界中で開催されているすべての技術カンファレンスに無制限で参加できる「Kaigi Pass」という制度があり、今回はこの制度を利用してカンファレンスに参加しました。詳しくは以下をご覧ください。

productpr.timee.co.jp

私自身も、入社する前にタイミーのエンジニア組織について調べており、2023年にも現在の同僚がdroidcon San Franciscoに参加した記事を読み、これらのエンジニアの成長を強く後押ししてくれる制度に魅力を感じ、実際に入社を決める一つの大きな要因となりました。

productpr.timee.co.jp


今回のdroidcon 2025 NYCはニューヨークのBrooklyn Storehouseで開催されました。会場はStorehouseという名の通り倉庫の形をしており、およそ10,000㎡の広大なスペースでセッションごとに仕切りを設けずに開催されました。

カンファレンス会場には高さ5メートルほどの巨大なドロイドくんが設置されており、初めての人でも「ここが droidcon だ」と感じさせてくれる象徴的な存在でした。

しかし、それ以上に存在感を放っていた“見えない”がいました──それは AIとレイオフ です。

You’re Not an Android Developer Anymore (Keynote)

  • Stacy Devino

全米のみならず世界中からAndroidエンジニアが集まるカンファレンスの初っ端から、いきなり核心を突くタイトルが登場しました。"こういうの待ってたぜ…USA…" とタイトルを見た時から感じていました。

今、アメリカには大きなレイオフの波が来ており、実際にレイオフをされて仕事を探している人が周りにいるかという質問に対し約7割の人が挙手しており、アメリカのIT業界の現状を肌で感じる機会になりました。

セッションではStacyが長年にわたり、さまざまな企業で Android エンジニアとして活躍してきた経験をもとに、これからの時代に生き残るための戦略を語りました。

AIの進化によって、プロダクトマネージャーやデザイナーなどのエンジニア以外の職種でもある程度の機能を作れる環境になってきています。

そして今後、モデルが進化したら誰でもソフトウェアを作れる時代が来るかもしれない──その中で、Androidエンジニア、ソフトウェアエンジニアはどうあるべきか?を問いかけるセッションでした。

セッションの中で語られていた

  • 問題解決にフォーカスすること
  • 自分の専門領域にとらわれず越境をすること
  • 小さな職能横断チームでゴールに向かって動くこと

これらはまさに、タイミーにおけるSAチームのプロダクトエンジニアのスタイルと強く重なる部分で、聴いていて非常に共感しました。

docs.google.com

"Ask Android" Office Hours

  • Google's Android DevRel team

さまざまなセッションの裏で自由にGoogleのAndroid DevRel teamのメンバーになんでも質問をすることができる"Ask Android"にも参加をしました。この会は特にファシリテーターやルールなどはなく、その場にいるGoogleのAndroid DevRel teamのメンバーを捕まえていろんな質問をぶつけることができました。

実際にComposeに注力をしているAlexに話を聞く機会があり、Edge to edge対応やLarge Screen Device, Material Designについてさまざまな話(ここには書けない裏話も含めて)を聞くことができ、貴重な時間になりました。

彼らのようなライブラリを提供する側からすると、逆にアプリケーションを開発するエンジニアがどんなことを考えながらアプリを作っているかに興味があり、お互いに情報交換をする機会にもなりました。

AlexはHandling configuration changes in Composeというタイトルで2日目のセッションにも登壇をしており、この"Ask Android" Office Hoursをきっかけに彼のセッションを聴講したのですが、なんとComposeの動作に関する例で実際にUIがスライド内で動作しており、これはもしや…と思いながら最後まで聞いていると、やはりスライド自体もComposeでできており、Composeを提供している側ならではの、圧倒的な熱量に驚かされました。

https://alex.vanyo.dev/talks/configurationchanges/

Career Office Hours with Stacy Devino

先述したStacyと参加者が円になって座り、キャリアの相談をする会でした。このセッションにはスライドやアジェンダもなく録画もされません。純粋にそれぞれのキャリアの相談をStacyと一緒に考える会でした。

相談の内容はレイオフの波もあり、かなり深刻なものが多かったです。個々人の話であるのであまり詳細には触れないのですが

  • 実際にレイオフにあった
  • マネージャーからICに戻ることに対する葛藤
  • Native/Flutter/React Nativeなどさまざまなモバイル開発手法に手を出したがどれも深掘りできておらず自信を持てずに悩んでいる
  • 大学でCSを学んだが入学時と卒業時でマーケットが激変していて就職に苦しんでいる

など聞いていて胸が苦しくなるような話題が多くありました。そのような困難な相談に対してもStacyの持ち前のポジティブさと、これまでの経験に基づく的確なアドバイスによって40分間のセッションでしたが、参加者はセッション後には前よりは明るい顔になっていくのをその場で感じました。

なぜ「現地に行く」ことが大きな意味を持つのか?

この記事の執筆時点では公開されていませんが、今回のdroidconの動画は例年通りであればインターネット上に公開されます。しかしながら実際に足を運ばなければ得られない体験をたくさん経験することができました。

カンファレンスはセッションを聞くだけの場所ではなくコミュニティが存在する場所である

動画が後からアップロードされるカンファレンスでも現地でしか得られない体験が多く存在し、例えば以下のようなものが挙げられます。

  • 現地での出会いをきっかけに、実際に仕事が決まることもある
  • セッション外でスピーカーにさまざまな質問をぶつけることができる
  • 朝早く会場に入ってStacyのような有名なエンジニアを見つけて会場で朝食を食べながら話をすることができる
    • 有名な人を捕まえると自然と人が集まってきて、さまざまな議論が始まったりする
  • セッション後にスピーカーにセッションに関連することも、それ以外の気になることも質問することができる
  • Androidという共通の話題をベースにキャリアだけでなくプライベートについても話をすることができる
  • Jake Whartonと話をして記念写真を撮ることができる

世界中のエンジニアが集まる場で感じたこと

  • 世界と比べても、タイミーのエンジニアたちは十分に戦えていると感じました
  • 有名OSSやJetpackライブラリの開発者など、世界的に活躍しているエンジニアたちは、やはり圧倒的でした jakewharton.com
  • すべてのセッションが上記のJakeのセッションのようなAdvancedな内容ではなく、自分がある程度知っている内容のセッションも一定数あり、自分もいつか、この舞台に立てるかもしれないという希望を持てました

雇用について改めて考え直すきっかけに

今回のカンファレンスを通じて、自分自身のキャリアだけでなく、「雇用」そのものについても考え直す機会になりました。

アメリカの大規模なレイオフの波を目の当たりにし、これまで安定だと思っていた“正社員”という立場も、企業の方針や市況の影響を受けて簡単に変わりうる現実を実感しました。一方で、個人として武器を持ち、どこにいても価値を発揮できる状態でいることの重要性も強く感じました。

タイミーという「働き方」に向き合っている会社で働く自分が、“雇用とは何か”をアメリカの現地で問い直すというのは、とても象徴的な体験だったように思います。

おまけ

時差ボケを考慮してスケジュールを組ませていただき、カンファレンスのない日は、せっかくニューヨークまで行ったので、観光を楽しむこともできました(もちろん仕事もちゃんとしていました)。

ナイトミュージアムの舞台になったアメリカ自然史博物館

ブロードウェイにてマイケルジャクソンの半生をミュージカルにしたMJ the Musical