エンジニアは「開発」から「AIのマネージャー」へ。プロセスを白紙に戻し「10倍」の進化を生み出す組織づくり

こんにちは、エムスリーキャリア株式会社 CTO 兼 CAIOの寺田です。

先日、エンジニアリンググループ全体に向けて「今後のAI活用方針と方向性」について説明しました。今回はその内容をベースに、エムスリーキャリアのエンジニアリンググループが目指している未来についてお話ししたいと思います。

チャットAIによる「1.1倍の改善」からの脱却

現在、多くの開発現場でチャットベースの生成AIツールが導入されています。弊社でも日々の業務で活用が進む一方で、既存の業務フローの延長線上での活用にとどまっており、「圧倒的な生産性向上」に向けては、まだまだ改善の余地があると感じていました。

細かくAIに指示を出したり、AIとやり取りをしてしまうと、結果的に人間がAIをマイクロマネジメントしてしまっています。これでは既存の業務フローの延長線上にある「1.1倍」の改善にしかなりません。

私たちが目指すべきは、既存のプロセスを白紙に戻し、「AIありき」でプロセスを根本から再定義することによる「10倍」の非連続な進化です。

目指すのはAIエージェントによる「自律的な改善サイクル」

最近のAIによる開発では、人間が何度もフィードバックを回す従来の手法から一歩進み、AIエージェント自身が自律的にタスクを解決していくループエンジニアリング的なアプローチがトレンドになっています。

私たちもAIの最適な活用方針を模索する中で、まさにそのアプローチに行き着きました。すでにこれらと同等の仕組みを自社の開発フローに組み込み、実運用に向けて作り込んでいる最中です。

しかし、私たちが目指しているのは、それを単なる「個人の開発環境のアップデート」や「コーディングの効率化」にとどめることではありません。その概念をさらに拡張し、事業KPIの分析から企画立案、そして検証に至る「事業全体の改善サイクル」にまで広げ、組織の仕組みとして定着させることです。

ゴールは、AIエージェントが自律的に状況を判断し、プロダクトの改善ループ(分析 → 企画立案 → 開発 → 効果検証)を自動で回し続ける環境を構築することです。

AIエージェントによる改善サイクル

この「自律的な改善サイクル」は、大きく分けて以下のステップで回っていく想定で、すでに各フェーズにおいて仕組みの構築と実運用が進んでいます。

  1. 分析

    事業KPIを自動で収集・分析し、AIがデータの中からボトルネックや異常値をリアルタイムで抽出します。現在すでに、社内のKPIや営業データをAIに読み込ませ、人間がダッシュボードを見て課題を探すのではなく、AIが自律的に課題を抽出する仕組みが稼働しています。

  2. 企画立案

    過去の事例や市場データなど多角的な情報と、抽出された課題を掛け合わせ、AIが最適な改善策(企画)を立案します。

  3. 開発

    企画を形にするフェーズにおいて、私たちは「ドラフトファースト」で推進しています。従来のように、人間が数時間かけて調査・設計し実装するのではなく、AIエージェントに数行の指示を投げます。AIが数分から十数分で設計・実装・Pull Request作成までを行い、まずは「60%の完成度のドラフト(初稿)」を作り上げます。人間がやるべきことは、そのドラフトを動かして完成度を確認し、要件をブラッシュアップしていくことです。これを仕組み化し全エンジニアが一定水準以上の成果物を担保できるようにしています。

  4. 効果検証

    リリースした機能が事業KPIに与えた影響(A/Bテストの結果など)を自動で集計・評価し、手動でのデータ抽出やレポーティング作業をなくす仕組みにします。

点と点を繋ぎ、シームレスなサイクルへ

現在、これらの仕組みは各フェーズ単体で稼働し、精度を高めているところです。この次は、これらの「点」の仕組みをシームレスに結合させることです。この一連のサイクルが完全に繋がれば、人間の主業務は「作業」から「AIのレビューと最終的な意思決定」へと完全にシフトします。

サイクルを現実のものにするための独自の工夫

この4つのステップからなるサイクルですが、机上の空論ではなく現実の業務で機能させるために、裏側では様々な工夫を仕込んでいます。

例えばその一つが、「組織の暗黙知」の扱いです。社内に蓄積されたドメイン知識や暗黙のコンテキストを、どうAIエージェントに適切に理解させるかなども重要になります。こうした「裏側の仕組み作り」も数多く走っており、それらは今までや1年前のエンジニアの働き方さえも変えていこうとしています。

エンジニアの役割は「開発」から「AIのマネージャー」へ

これまで、エンジニアの役割は4つのステップのうち「開発」フェーズでした。しかし、この自律的な改善サイクルが回り始めると、その常識は大きく変わります。エンジニアは単なる実装作業をAIに任せることで、自ら「分析・企画立案・開発・効果検証」のすべてのプロセスに横断的に入り込み、サイクル全体をドライブしていくことになります。

エンジニアは自らのAIエージェントを「部下」として持ち、タスクを割り振り、レビューする「マネージャー」や「リーダー」としての役割が求められるようになります。また、人間は細かい手順の指定をやめ、「Why / What(目的と枠組み)」の提示と、踏み越えてはいけないガードレールの設計やレビューに徹します。そして、これまで全体のわずかしか割けていなかった、事業成長に直結する「企画」や「意思決定」の時間を増やしていきます。

おわりに

「AIを正しく使えば、開発工数は大幅に削減できる」というのが私の考えです。開発や運用の大部分をAIエージェントによる自律的なサイクルに任せ、人間はビジネスのより深い部分に挑戦していく。そんな全く新しいエンジニア組織を作っています。

 

私たちのチームやエンジニアリンググループに少しでも興味を持っていただけましたら、ぜひ採用サイトをご覧ください!

https://career.m3career.com/midcareer/engineer

Cloudflare Workers + D1で社内ツールをデプロイした話

 

こんにちは!エンジニアリングGの松本です。

今回は、Cloudflare Workers + D1を使って社内のJira工数管理ツールを無料でデプロイした話をします。

 

きっかけ

エムスリーキャリアでは、どの施策にどれほどの工数が掛かったかを算出し、今後の工数改善に繋げる為、Jiraの作業時間ログで管理しています。

しかし、日々の入力を管理するのは少し手間がかかります。

会社のルールでは一日8時間の工数入力が求められるのですが、Jiraにはタイムシート的な横断表示画面がなく、その日どのタスクに何分入力したかが一目でわかりません。

そこで、業務の傍で一目で工数入力や入力状況の確認ができるツール開発を行ってみました。

作りました

見ての通りですが、Jiraに入力した工数をタイムシート形式で表示・入力できるアプリです。バックエンドではJira APIを叩いて情報を取得しています。

技術スタック

アプリケーション周り

- Next.js

- Tanstack Query

- Tailwind CSS

- Effect-ts

- Drizzle ORM

インフラ周り

- Cloudflare Workers

- Cloudflare D1

- Cloudflare Access

デプロイ

このアプリは最初は個人で使えるようにローカルでサーバーを立てるWebアプリとして開発していたため、比較的手軽にリッチな画面が作れてメジャーなNext.jsを使用していました。

後に、「社内で使えるようにデプロイしてほしい」という要望が出たとき、素直にサーバーを立てようとすると保守やインフラ設定、費用が気になります。

そこでCloudflare WorkersとD1を使えば複雑なインフラ設定なしにサーバレスで保守や費用の負担を最小限にした上で十分運用できると気づき、その方針に切り替えました。

Next.jsはVercelじゃないと難しいという話をよく聞きますが、最近はOpenNextVinext等、Vercel以外の環境へのデプロイ手段が整ってきています。今回はOpenNextを使用してCloudflare Workersにデプロイしましたが、大きな変更なく移行できました。

 

動作に不安があるかもという声も聞きますが、実際に社内利用する上では十分に実用的であり、無料運用の手軽さを優先しています。

(今回はOpenNextを使用してCloudflare Workersにデプロイ。無料プランのビルドサイズ制限は3MiB(圧縮後)までなので割とギリギリの結果になりました)

(最近Next.js公式がAdapter APIをサポートし始めたおかげでOpenNextも安定版がリリースされました!これからも注目ですね!)

意識したこと

今回のような「Jiraとアプリ内DBの両方にデータを持つ」同期ツールは、実装を誤ると次のような不整合が発生しがちです。

  • Jira側にあるデータがアプリ側に反映されていない(逆のケースも含む)
  • データの同期状態がユーザーから確認できない
  • Jira側に既にデータが入っているのに気づかず上書きしてしまう

 

これに対して、DBのレコードには以下の2種類の値を持たせる設計にしました。

 

フィールド

意味

hours

ユーザーがアプリ上で入力した時間

jira_hours

Jira上の実データのコピー(同期後に更新)

フローはシンプルです。

  1. 取得: 画面表示時にJira APIをフェッチし、jira_hours(Jira上の実データ)を最新化。
  2. 編集: ユーザーが値を変更する際は、ローカル側の hours のみを更新。
  3. 同期: 「送信」ボタン押下時にJira側への書き込みを実行し、成功を確認後に jira_hours を更新。

要するに、UIの操作フロー上で不整合な状態が生まれないよう制御しているということです。

反響

サポートツールとして実装し、自由に利用を促した結果、自主的に13名もの方に利用いただけている状況です!嬉しいですね!

まとめ

 Cloudflare Workers+D1で社内アプリをリリースした記事でした。

 やはりサーバーレスは「小さく作って公開する」ためのハードルをぐっと下げてくれますね。

最後に

今回作成したものは画面構成がリッチでかつ外部連携先とのデータの整合性も考慮しなくてはならないため社内ツールとしてはかなり複雑な内部仕様になっていると思いますが、AIによる強力な設計サポートもあり本業務の傍で完成させることができました。

社内ツールの展開事例、Cloudflare Workers, D1, OpenNext等の活用事例として参考になれば幸いです。

 

私たちエムスリーキャリアのチームやエンジニアリンググループに少しでも興味を持っていただけましたら、ぜひ下記の採用ページをご覧ください!

 

https://career.m3career.com/midcareer/engineer

 

CSチームの近くで働いて気づいた現場にある改善のヒント

はじめに

あるプロダクトの管理画面の改修で、カスタマーサクセスチーム(以下CSチーム)のメンバーにモックの操作イメージを見てもらっていたときのことです。

実際の業務に沿ってこちらで操作しながら見てもらっていると、ふとこんな一言が出てきました。

「この画面、毎回前の画面で確認した薬局の所在地を頭に入れた上で作業しないといけないんですよね...」

ドキュメントを読んでいても、コードを読んでいても、直接話を聞かなければ気づけなかった一言でした。

今回は、入社して間もない時期にCSチームの近くで働いてみたことで得られた気づきと、自分の開発スタイルの変化についての記事になります。

 

自己紹介

こんにちは。エムスリーキャリアの薬剤師採用支援チームでエンジニアをしている石川です!

昨年12月にエムスリーキャリアに入社し、現在は薬局向けプロダクトの開発を担当しています。

最近は食べたことのない海外料理を食べることにハマっています。少し前に食べたエジプト料理のコシャリがとても美味しかったです!

 

なぜCSチームの近くで働こうと思ったか

きっかけは大きく2つあります。

1つ目はCSチームからの問い合わせ対応を通じて生まれた疑問です。

問い合わせに対応していると、以下の2点が気になる場面がありました。

・なぜこの事象が起きているのか

・そもそも改善できる余地はないのか

ただ問い合わせを受けて回答するだけでは、その背景や根本原因までは見えないことも多くありました。

2つ目は入社したばかりで、プロダクトが実際にどう使われているのかを知りたかったことです。

コードを読むことはもちろん大事ですが、それだけでは「誰が、どんな流れで、どこに困りながら使っているのか」まではなかなか見えてきません。

それなら、実際に運用されている場に行くのが一番だと思いました。

 

やったこと

最初から大きなことをしたわけではありません。

まずは次の2つから始めました。

- 週1回、CSチームの席のそばで作業する

- CSチームの定例MTGに参加する

 

弊社では、フリーアドレス制を導入しており、席はある程度自由に決めることが可能です。

普段はCSチームの方とは業務を行う階が違うため、まずは物理的に距離を近づけるところから始めました。

席にいる間は、自分の作業を進めつつ、CSメンバーが対応している業務を実際に見せてもらったり、雑談の中で困りごとを聞いたりしていました。

「この対応って、実際にはどういうことをしているんですか?」と声をかけて、作業中の画面を見せてもらうこともありました。

 

CSチーム側の定例MTGにも継続的に参加しました。

そこではプロダクトの利用状況や、導入後のフォロー、リリース前後の周知・問い合わせ対応など、日々の運用に関わる話題が多く扱われていました。

参加を重ねるうちに現在のプロダクトがどのように使われているのか、課題に対してどのような施策が検討されているのかを少しずつキャッチアップできるようになりました。

また、普段の問い合わせを深掘りする中で、実際にどのような事象が発生しているのかを把握できる場面も増え、プロダクト利用の解像度が上がっていきました。

 

近くで見て気づいたこと

CSチームは、ユーザーからの問い合わせ対応、社内からの依頼対応、データの確認・修正など、幅広い業務を担っています。

近くで見て初めてわかったのは、1件の対応をするだけでも、管理画面や複数のツールを何度も行き来しながら、その都度内容を確認しているということです。

依頼を受けてやり取りしているだけでは、その複雑さまではなかなか見えてきませんでした。

たとえば開発側から見ると「この項目を確認して、この設定を変更する」という単純な作業に見えるものでも、実際には前後の文脈を確認したり別の画面と見比べたりしながら進めていることがあります。

画面上の1クリックの裏に思っていた以上の確認作業がありました。

 

一番印象に残った出来事

CSチームとの距離が少しずつ近くなってきた頃、管理画面の改修を進めていました。

特定の設定を、これまでより簡単にできるようにする改修です。

せっかくなら実際に使う人に確認してもらおうと思い、CSチームのメンバーにモックの操作イメージを見てもらうことにしました。

実際の業務に沿ってこちらで操作しながら見てもらっていると、冒頭で紹介した一言が出てきました。

 

「この画面、毎回前の画面の薬局の所在地を頭に入れた上で作業しないといけないんですよね...」

 

今回の改修とは直接関係のない、何気ない一言でした。

しかしそれはドキュメントにもコードにも書かれていない、実際に作業している人だからこそ気づける困りごとでした。

それまでは、画面遷移後に必要な情報が表示されていなかったため、CSメンバーは前の画面で見た薬局の所在地を覚えたまま作業する必要がありました。

この設定は都道府県によって値が変わるため、薬局の所在地を把握していることは、正確に作業を進めるうえで欠かせません。

そこで、元々の改修に加えて次の画面にも薬局の所在地を表示するようにしました。

 

小さな変更ですが、実際の業務では「覚えておく」という負荷を減らせる改善です。

後日、CSチームの隣で作業しているときに、直接感謝の言葉をもらえたことも印象に残っています。

話を聞いてみると、一連の改修で月間約10時間の対応工数が削減できたとのことでした。小さな一言から生まれた変更でも、積み重なれば現場の負荷を着実に減らせると実感した出来事でした。

もしCSチームの方に話を聞いていなければ、この改善は生まれなかったと思います。

 

CSチームの近くで働いて変わったこと

CSチームの近くで働いてみて、実際に運用している人の目線でしか見えないことが多いと実感しました。

CSチームはユーザーと日々接しているため、「実はこういう使い方をしている人が多い」「この操作を想定外にやってしまうケースがある」といった、ユーザーに近い場所でしか聞けない話を持っています。こうした情報は、開発側だけで仕様を考えていてもなかなか出てきません。

継続的に関わる中で、CSチームとの距離も少しずつ縮まりました。以前は問い合わせ対応や依頼のやり取りが中心でしたが、困りごとを相談してもらえる場面が増え、こちらからも気軽に仕様や実際の使われ方を確認しやすくなりました。冒頭のエピソードも、そうした関係性があったからこそ出てきた一言だったと思います。

この体験以降、仕様を検討するときには一度立ち止まってCSチームに確認するようになりました。後から「実際には使いにくかった」と気づくよりも、検討段階で聞いてしまう方がずっと早いと感じています。

 

今後やっていきたいこと

CSチームの近くで働いたことで、日々の業務の流れが具体的に見えるようになり開発の進め方も少し変わりました。

一方でCSチームの隣で見えたのは、あくまで運用者視点での一側面にとどまります。

次のステップとして実際のプロダクト利用者にユーザーインタビューをさせていただき、ドキュメントにもコードにも現れない困りごとや使われ方を把握していきたいと考えています。

CSチームが捉えている困りごとと、利用者本人が感じている困りごとの両方を知ることで、よりよい改善につなげていきたいと思っています。

 

おわりに

CSチームの近くで働いてみることは、小さな取り組みだったかもしれません。

それでも実際の業務の流れや、普段の会話の中にある困りごとに触れることで、得られるものは想像以上に大きいと感じました。

今後はCSチームから見える景色だけでなく、実際にプロダクトを利用してくださっている方の声も聞きながら、より良い改善につなげていきたいと考えています!

 

エムスリーキャリアでは、技術を武器にビジネスやユーザーへの理解を深めながら、より良いサービスづくりに挑戦したいエンジニアを募集しています。

私たちのチームやエンジニアリンググループに少しでも興味を持っていただけましたら、ぜひ採用サイトをご覧ください!

career.m3career.com



RDSのIO天井張り付き問題と次の一手

こんにちはエンジニアチームの山本です。

最近はコテンラジオの龍源さん回を聴いています。抜苦与楽、離苦得楽が耳に残りました。

年始早々にDB障害に遭遇した話をご紹介します。

3行まとめ

  • RDS for PostgreSQLで大量のディスク読み込み(Read IOPS高負荷)が発生した
  • 論理削除レコードが検索対象に含まれ大量の行アクセスが発生していたのが原因で部分インデックスの導入で解消
  • 発生から解決までのLTを短縮するためAIの活用を検討する

問題の発生と解決まで

突然のIO悪化と枯渇するBurstBalance

1月6日。気持ちよく新年の仕事初めを迎えようとしていた矢先のことでした。

  • 事業側から「特定の画面が表示できず業務に支障が出ている」と連絡
  • DBのRead IOPSが異常に跳ね上がっている
  • DBの BurstBalance が枯渇
  • ディスクタイプは gp2

といった事象が確認されました。

高負荷で張り付くRead IOPS
枯渇したバーストバランス
正月気分が吹き飛びました。

ディスクタイプをgp3にしても解決しなかった

ディスクタイプが gp2 であることを確認した私は、「完全に理解した。gp3 に変更してIOPSの上限を上げれば解決だろう」と早合点しました。

しかし、gp3 へ変更してもIOPSの最大値が上昇し、結局その上限に張り付くだけで問題解決には至りませんでした。

あくまでもIOパフォーマンスの上限が上がっただけで「IOが高くなっている原因を取り除いていない」のでそれはその通りです。

1000万行のテーブルと997万行の論理削除レコード

色々なメトリクスやログに翻弄されながらも、たどり着いたのは「1000万行ある特定のテーブルへのSELECTクエリが異常に遅い」ということでした。

遅いクエリの explain analyze 結果としては

...
Recheck Cond: {条件}
Rows Removed by Index Recheck: 1447586
Filter: (条件)
Rows Removed by Filter: 290967
Heap Blocks: exact=44364 lossy=292651
-> BitmapAnd (cost=239013.33..239013.33 rows=55419 width=0) (actual time=3599.459..3599.461 rows=0 loops=1)
-> Bitmap Index Scan on カラム (cost=0.00..11743.88 rows=468726 width=0) (actual time=72.315..72.315 rows=521373 loops=1)
Index Cond: (条件)
-> Bitmap Index Scan on カラム (cost=0.00..46656.66 rows=1994164 width=0) (actual time=2879.922..2879.922 rows=2044585 loops=1)
Index Cond: (条件)
-> Bitmap Index Scan on カラム (cost=0.00..180612.16 rows=5888480 width=0) (actual time=629.062..629.062 rows=6108671 loops=1)
Index Cond: (条件)
Planning Time: 6.000 ms
Execution Time: 5065.175 ms
...

のような表示が確認されました。

また

show work_mem
4MB

からBitmap Index Scan で作成されるビットマップは最大4MBまでであることもわかりました。

以上より - 1000万行のうち論理削除されていないレコードは3万行 - インデックスの中で作成されたビットマップは約870万行分 - 問題のクエリ実行計画では約200万行程がFilter処理の中で削除されていた - つまり約200万行程のディスク読み込みが発生していた

ことまで辿り着けました。

何が起きたか詳しく

1.ビットマップがメモリに乗り切らなくなる

まずはインデックスから取得するレコードを絞り込んでいきます。 その過程で3つのビットマップ(計870万行分ほど)を work_mem 領域に載せようとします。

-> BitmapAnd (cost=239013.33..239013.33 rows=55419 width=0) (actual time=3599.459..3599.461 rows=0 loops=1)
-> Bitmap Index Scan on カラム (cost=0.00..11743.88 rows=468726 width=0) (actual time=72.315..72.315 rows=521373 loops=1)
Index Cond: (条件)
-> Bitmap Index Scan on カラム (cost=0.00..46656.66 rows=1994164 width=0) (actual time=2879.922..2879.922 rows=2044585 loops=1)
Index Cond: (条件)
-> Bitmap Index Scan on カラム (cost=0.00..180612.16 rows=5888480 width=0) (actual time=629.062..629.062 rows=6108671 loops=1)

ビットマップは「条件に一致する行はディスクのここにあるよ」の集合です。

これが800万行分あるとなるとwork_memの4MBを超えることが容易に想像できます。

2. 曖昧な検索によりディスク読み込みが発生

ビットマップがメモリに乗り切らないと「このディスクブロックのどこかに欲しい行があるよ」。 という曖昧な検索戦略に切り替わります。

Heap Blocks: exact=44364 lossy=292651

より「候補に上がったブロックへディスク読み取り(少なくとも150万行以上は)」が発生したことが推測されます。

部分インデックスの導入で解決

以上より「論理削除されたレコードはビットマップに含めない」を達成するためにインデックスの改善を実施しました。

CREATE INDEX CONCURRENTLY IF NOT EXISTS {インデックス名} 
ON {問題のテーブル} (重いクエリで利用しているカラム DESC)
WHERE delete_timestamp IS NULL;

そもそも「使われるインデックスに論理削除レコードは含めない」という戦略でインデックスを追加したところタイムアウトエラーになっていた画面が表示されるようになり、問題のクエリにかかっていた時間も 5〜6秒ほどから数十msほどまで短縮されました。

そもそもなぜ年始に問題が発生したのか

よりによってなぜ年始になってから極端にディスク読み込みが高負荷で張り付いてしまったのでしょうか。

エビデンスはなく憶測でしかないのですが以下のことが言えると思っています。

work_memに乗り切るかが分水嶺だった

ビットマップがwork_memに乗り切ってくれていればディスクへ「曖昧な走査」を行うことはなかったと思います。

レコードが増えてくる中でメモリから溢れてしまった途端に - 処理に時間がかかり始める - 並列でリクエスト処理が走る分SQLも並列で実行されていく ことで一気にIO悪化が引き起こされたのだと考えています。

反省と学び

「IO上昇の原因調査」よりも「事業影響の解決」を優先するべきだった

初動として「取り急ぎ gp2 から gp3 へ変更する」という暫定対応自体は悪くなかったと思っています。 しかし、その後のアクションとして「なぜIOが高くなっているのか(インフラ寄りの原因)」の調査に固執してしまいました。

一番優先すべきだったのは、「なぜ特定の画面が表示できないのか(アプリケーション・クエリ寄りの原因)」の調査でした。 最終的な原因には同じように辿り着いたかもしれませんが、「高負荷なクエリを1つずつ原因を調べる」のと「問題が顕在化している画面で実行される重いクエリを特定する」のとでは、目の前の問題解決(=ユーザーへの価値提供の復旧)に至る時間は大きく変わっていたはずです。

行指向データベースを理解していなかった

恥ずかしながら行指向データベースがどういうことかをきちんと理解できていませんでした。

今回遅かったクエリについては認識していたはずなのに「絞り込まれたレコードは大した行数じゃないので読み込みも少ない」ような思い込みが、判断を鈍らせていました。

実際には「(ブロックで走査された行数 + インデックスで絞り込まれた行数) * 全カラム分のデータサイズをディスクから読み込む」ということが起きていました。

問題が起きていたクエリが参照しているテーブルにはフリーテキストで比較的大きめなデータ量のカラムも含んでいます。

1行あたり大体 2.5KB で少なくとも150万行のレコードが参照されたと仮定すると少なくみても3GBほどはデータの読み込みが発生しており、数GBのディスク読み込みが発生する画面へのアクセスが複数並列でリクエストが来ることを考えるとReadIOPSが跳ねていた上限まで張り付いていたのも納得できます。

行指向だと「絞り込みの段階で全カラム分のデータ量を読み込んでいる」という意識が足りていませんでした。

タダでは転ばない次の一手

今回の件を経て「問題の発見から解決まで時間がかかった」ことが大きな伸び代に感じています。

時間がかかってしまったのは

  • マイクロサービスな構成故の実態把握難易度の高さ
    • オンプレ、CloudWatch Logs等各所にログが散らかっている
    • メトリクスダッシュボードのようなものもなかった
  • メトリクスに対する理解の浅さ
    • RDS Performance Insights でIO負荷の高いクエリの特定ができた可能性がありましたがAASなどの指標への理解が浅く特定に至れなかった

だと考えています。そこに対して今までだと

  • CloudWatch ダッシュボードの整備
  • 障害から学んだ知見をアウトプットしポストモーテム資料として記録

程度が月並みなアイデアだとは思うのですが、これからは「AIと一緒に仕事できないか」という視点であらゆる業務を見つめ直す必要があると思います。

メンバーからAWS DevOpsを提案いただき、検証してみようという動きになりました。

おわりに

年始早々にシビれる経験をすることができました。

「間違えなければ、成長も創造もない」と藤田あき美さんも話されています。転んだことをプラスに捉えこれからも日々価値を生んでいきます。

エムスリーキャリアでは、技術を武器にビジネスの深掘りに挑戦したいエンジニアを募集中です。興味を持ってくださった方は、ぜひ採用サイトをご覧ください!

career.m3career.com

PlaywrightとGitHub Actionsを用いたE2Eテスト自動実行環境の構築と、リリース頻度向上の取り組み

はじめに

エムスリーキャリアでQAエンジニアをしている宮浦です。
医師採用支援サービスのチームにて、要件定義のレビューからテスト実行まで、プロダクトの品質保証業務全般を担っています。

先日、同じチームのQAエンジニアである山地が、自ら事業課題の解決を起案しリリースまで完遂した記事を公開しましたが、今回は私からもう一つの挑戦についてお話ししたいと思います。

m3career-eng.hatenablog.com

 

本記事では、手動で行っていたリグレッションテストの負荷を軽減するため、Playwrightを用いたE2Eテストを導入し、さらにGitHub ActionsとSelf-hosted Runnerを組み合わせてCI/CDに組み込んだ事例をご紹介します。この取り組みにより、リリース前確認の工数を削減しつつ、リリース頻度の向上を実現することができました。


背景と課題

私たちのチームでは、機能追加やシステム改善を迅速にユーザーへ届けるため、リリース頻度を高めたいという目標がありました。しかし、そのボトルネックとなっていたのが、リリース前のコンバージョン(CV)確認などに代表されるリグレッションテストです。
毎回手動で画面操作して確認する作業は非常に労力がかかり、ヒューマンエラーのリスクも伴っていました。
そこでまずは、PlaywrightによるE2Eテストの導入を進めることにしました。

 

第1フェーズ:PlaywrightによるE2Eテストのコード化

まずは手動実行の手間を減らすため、Playwrightを導入し、E2Eテストのコード化に着手しました。


※弊社におけるPlaywright活用の基礎的な取り組みについては、同じくQAエンジニアである川浪が過去に執筆した以下の記事等でもご紹介しています。

m3career-eng.hatenablog.com


テストスクリプトを順次作成していくことで、画面操作による確認作業が大きく減り、確実な品質担保が可能になりました。

 

しかし、ローカル環境でテストが実行できるようになったとはいえ、「QAエンジニアが手動でテストを起動する」という運用上の手間は残っていました。真の効率向上を目指すためには、パイプラインへの組み込み(自動実行化)が不可欠でした。

 

第2フェーズ:Self-hosted Runnerの導入

私たちの目標は、「リリース前の検証環境へアプリケーションがデプロイされたタイミングで、自動的にE2Eテストが実行される仕組み」を構築することでした。

ここでインフラ上の課題に直面しました。テスト対象となる社内の検証環境や、外部システムと連携するテスト環境にはIP制限等のアクセス制御がかけられており、GitHub Actionsの標準ランナー(ホステッドランナー)から直接アクセスさせることができませんでした。

この課題を解決するため、セキュアなネットワーク環境内にE2Eテスト専用の実行基盤を構築し、自己ホスト型ランナー(Self-hosted Runner)として運用するアプローチを採用しました。Self-hosted Runnerは、ランナー側からGitHubへジョブを取得しにいく仕組みのため、外部からのアクセス(インバウンド通信)用のポートを開放せずに構築することが可能です。

この構成であれば、社内の厳格なアクセス制御要件を満たしつつ、ランナーが外部からの踏み台にされてしまうようなリスクを抑えた状態で、安全に各検証環境へ接続することが可能です。自前での運用・保守コストは発生しますが、セキュリティ面での統制が効きやすく、総合的な安定性と拡張性に優れていると判断しました。


生成AIとエンジニアの助言を活用したインフラ構築の効率化

専用サーバの構築にあたっては、クラウド上のネットワーク設定やアクセス制御、実行環境の常駐化など、インフラからミドルウェア層の作業が発生します。私の普段の業務領域(QA)からは少し外れる領域もあったため、生成AIを技術的なサポート役として活用しました。
具体的には、以下のような構築プロセスでAIと協力しています。

 

  • サーバースペックの最適化

AIとの対話を通じて「テスト実行ツールが消費するリソース量」を算出し、同時実行数に応じた必要メモリ要件を導き出すことで、コストとパフォーマンスのバランスが取れたスペックを選定しました。

  • コンテナ環境の権限設定

コンテナ環境を導入する上で必要となる、適切なユーザー権限の付与やセキュリティ設定を行いました。

  • 実行環境の常駐化

自己ホスト型ランナーをバックグラウンドサービスとして、インスタンス上で常時安定稼働させるための手順を構築しました。

 

一方で、AIの提案をすべてそのまま適用するのではなく、重要な判断についてはチームのエンジニアに相談しています。
社内ネットワークとのセキュアな接続方針や全体のアーキテクチャといった点について的確な助言をもらいながら実装を進めることで、セキュリティリスクを最小限に抑えた確実なインフラ構築を実現しました。
生成AIによる推進力と、チームメンバーの知見を掛け合わせることで、自身の技術スタックを超える環境構築をスムーズに完遂させることができました。

 

自動実行ワークフローの設計と実装

最終的に完成したGitHub Actionsのワークフローは、以下のような仕様で運用しています。

  1. デプロイ連動の自動実行トリガー
    検証環境へのデプロイ完了をトリガーとして、対象の全E2Eテストが自動的に開始されるように設定しました。これにより、エンジニアがテストの実行操作を意識することなく、シームレスな品質確認が可能なフローを実現しました。

  2. 並列実行と順次実行のハイブリッド設計
    テスト全体の実行時間を短縮するため、基本的には独立したテストのJobを並列で実行させています。一方で、外部システムとの連携バッチ実行が絡むテストなど、データの競合や順序性が重要なものについては、適切な待機時間を設定し順次実行するよう、Job間の依存関係を細かく定義しました。

    並列実行と順次実行(バッチ待ち等)を組み合わせたGitHub Actionsのワークフロー


  3. 結果通知とArtifactsを用いたトレース
    テストが完了すると、実行時間やステータスがSlackへ自動通知されます。万が一テストが失敗した場合は、Playwrightのレポートファイルや実行時の動画、スクリーンショットがGitHubの「Artifacts」として保存される仕組みにしました。これにより、エラー原因の特定が非常に容易になっています。

    テスト完了時のSlack通知。エラー時は該当レポートも即座に確認可能


成果と今後の展望

Playwrightによるテストコードの実装から始まり、CI/CD環境の構築までを完了させた結果、開発サイクルのスピードは劇的に向上しました。

最大の成果は、「リリース前確認作業の自動化」です。E2Eテスト導入前は月3〜4回程度だったリリース頻度が、CI/CD環境構築後には月7〜8回程度まで増加しました。
これだけリリース頻度が増加したにも関わらず、テストが自動かつ安定して実行されるようになったため、手動でのリグレッションテスト工数をゼロに抑えた状態でのリリースフローを実現できています。

今後は、この実行環境のさらなる安定化とパフォーマンス改善を図るとともに、チーム内でのE2Eテスト適用範囲の拡大、さらには他チームのプロダクトへの横展開を進めていきたいと考えています。

 

おわりに

プロダクトの価値を素早くユーザーに届けるためには、今回のようなテスト自動化やプラットフォーム整備を通じた認知負荷の低減が欠かせません。QAエンジニアという役割に留まらず、インフラ領域の構築も含めて一気通貫で環境を整備できたことは、非常に良い経験となりました。

エムスリーキャリアでは、こうした自動化技術やAIを積極的に活用し、医療業界の課題解決に共に挑戦していただけるエンジニアを募集しています。少しでも興味を持っていただけましたら、ぜひ下記の採用サイトをご覧ください。

career.m3career.com

テストだけじゃない。いちQAエンジニアが最終承認を得て、事業課題の起案からリリースまで完遂した話

はじめに

エムスリーキャリアでQAを担当している山地です。

現在、私は医療機関向けの医師採用支援サービスを担当しており、QA観点での要件定義レビューからテスト実行まで、プロダクトの品質を担保するための業務を担っています。

「QAエンジニア」と聞くと、作られた仕様書をもとにテストケースを作成し、バグを見つける仕事、というイメージを持たれる方もいらっしゃると思いますが、今回私は「ビジネス課題の発見・起案」から「最終承認の獲得」、そして「要件定義〜リリース」まで、一連の開発プロセスを主導するという初めての経験をしました。

 

本記事では、いちQAエンジニアである私が、なぜ職掌の枠を超えてビジネス起案を遂行したのか、また、その過程でぶつかった壁・得られた学びについて共有いたします。

「QA=テストする人」という枠組みを超えて働ける、弊社の環境とカルチャーを感じていただけますと幸いです😊

 

 

なぜQAがビジネス起案をしたのか?(背景と課題、その打開策)

私が担当しているプロダクトにおいて、「特定の機能を利用していない顧客層(潜在層)へどうアプローチするか」は全社的にも優先度の高い事業課題でした。

そこで、この課題を解決するため、プロダクトの保有データに基づいて顧客ごとにパーソナライズされた訴求UI(新機能)を実装する、という起案を行いました。

しかし、この起案を通常の開発承認フロー(複数の関連部署を経由するルート)に乗せると、どうしてもリードタイムが長期化してしまい、機会損失を生む懸念がありました。

 

「もっとスピーディーに価値を届けるためにはどうすべきか?」

 

悩んだ結果、スピードを最優先するため、起案の初期段階から、この施策で直接的に価値を享受する部門の事業責任者を巻き込むアプローチをとることにしました。

具体的には、構想段階で直接相談を持ちかけ、その方々のお力を借りてROI(投資対効果)を算出し、懸念していたリードタイムを大幅に短縮する形で、最終的には事業を統括する決裁者の承認まで持っていくという、QAの枠を大きく超えた動きをとりました。

 

葛藤とオーナーシップ。「問題 vs 私たち」のマインドセット

上記を言葉にしてみるとスムーズに進んだかのように見えますが、その裏ではどこか「罪悪感」に近い感情との葛藤がありました。

通常、弊社では事業課題の解決や起案はビジネスサイドの主たる職掌であり、そこへQAエンジニアである私が入り込むことに「他のプロフェッショナルの領分を侵しているのではないか」というためらいを感じていました。

 

そんな葛藤を乗り越える際、心のお守りになったのは「問題 vs 私たち」というマインドセットです。

こちらはソニックガーデンの倉貫義人さんが提唱されている 「対立」ではなく「問題 vs 私たち」の構図をつくる という考え方で、一緒に働くQAの先輩から教わり、私自身も強く影響を受けている言葉です。

※参考: 心理的安全性を生み出す「問題 vs 私たち」と「提案より相談」

 

「これは誰の仕事か」「誰の顔を立てるべきか」といった組織のサイロ化や遠慮にとらわれるのではなく、「どうすればユーザーと事業に最速で価値を届けられるか」という共通の課題にフォーカスする。そう考えることで、罪悪感を「プロダクトを良くするための推進力(オーナーシップ)」に変換することを試みました。

 

結果として、開発チーム内にとどまらず、ビジネス側のステークホルダーとのミーティング調整やスケジュール作成など、慣れないガントチャートを引きながら泥臭い調整に奔走することになりましたが、「より良いものを速く作りたい」という想いに(半ば巻き込む形になってしまいましたが🙇‍♀)快く乗ってくださった方々のご協力により、無事に承認まで辿り着くことができました。

 

QAエンジニアが直面した「ROI・KPI設定」の壁

こうしてスタートしたプロジェクトですが、すぐに未知の壁にぶつかりました。それが「ROI(投資対効果)の算出」です。

普段の業務では「ユーザーの期待を損ねないか」「仕様通りに動くか」「バグがないか」といった品質面にフォーカスしているため、マーケティング視点での「この施策でいくら売上が上がるのか」といった数値を算出する経験が圧倒的に不足していました。

 

「やりたい」という熱量だけでは、ビジネスとしての承認は下りません。

そこで、ここでも「問題 vs 私たち」のマインドで当該事業のマーケティング担当者に協力を仰ぎ、やりとりを重ねました。

 

「どの条件を表示したら、顧客は『アクションを起こしたい』と思うか?」

「UIの表示ロジックをどう組めば、クリック率を高められるか?」

 

ここで役立ったのが、QAとして培ってきた以下のスキルです。

  • 仕様理解の解像度の高さ

  • テスト設計における「因子・水準の整理スキル」

データベース内に保有する各テーブルのカラムの値を分析した上で、ターゲットを絞り込むための複雑な条件(ユーザー属性や利用状況など)を整理し、矛盾や抜け漏れのないパーソナライズロジックを策定することができました。

こうした要件をもとにマーケティング担当者にROIを算出いただいた結果、承認依頼を出すフェーズまでたどり着くことができ、その後、開発承認を獲得するに至りました。

 

自ら「要件定義」をして気づいた罠

承認を得て、開発スケジュールの目処も順調に進み、いよいよ私の本職である「テストフェーズ」に入ったときのことです。

自分で要件を書き、自分でテスト仕様書を作成してテスト実行したところ、まさかの考慮漏れによる仕様バグが見つかりました。

 

具体的には、「新設したUIの集計ロジック」と「既存ページ(一覧)の表示ロジック」の乖離です。

新設したUI側では「特定の条件(AとBが一致するなど)を満たすデータ」をシンプルにカウントする要件にしていました。しかし、サイト内の既存ページには、元々『イレギュラーなデータは除外する』といった複雑なネガティブ条件(弾く条件)が実装されていたのです。

そのため、このままでは「新設したUIでは『対象データが存在する』と表示されるのに、既存の一覧ページに遷移すると(複雑な除外条件に引っかかり)1件も表示されない」という、矛盾したユーザー体験を生み出す状態でした。

 

矛盾や抜け漏れのないパーソナライズロジックを策定することができました

一体…どの口が言っていたのでしょうか…🥺

 

そこで、「なぜ、要件定義の段階で気づけなかったのか?」について考察しました。

 

普段、他者が書いた仕様書をレビューする際は、「こういうケースはどうなりますか?(弾かれますか?)」と客観的な視点で気づきやすいものですが、いざ自分が「作りたいもの(ポジティブな要件)」を考える側になると、既存機能との整合性という観点がすっぽ抜けるほど視野が狭くなっていたと感じました。

結果、「弾きたいもの(ネガティブな要件)」の定義が不足していたことに、最終工程になってようやく気づくことになったのです。

 

「作る視点」と「テストする視点」を一人で切り替えることの難しさ、そして「要件定義の段階で、いかにQAマインド(批判的思考)を発動できるか」の重要性を、身をもって痛感する出来事でした。

 

おわりに

いくつかの壁や自身の反省点と向き合いながらも、プロジェクトは無事にリリースされ、実際にユーザーからの反応が出始めています🎉

 

今回の経験を通じて私が最もお伝えしたかったことは、エムスリーキャリアには「QAエンジニアだからここまで」という職能の壁がないということです。

「プロダクトを良くしたい」「ユーザーに価値を届けたい」という想いと、一歩踏み出すオーナーシップがあれば、承認フローを開拓し、組織を巻き込み、事業課題の解決にダイレクトに貢献することもできます。

そのためにはビジネスサイドとの連携および信頼関係の構築、顧客ニーズの理解が不可欠です。ときには修羅の道と感じることもありますが、裁量を持ってエキサイティングな道を選択できる環境はエムスリーキャリアの面白さでもあると感じます。

そして、担当サービスを横断して背中を押してくれるQAチームの存在が、私にとって何よりの心のお守りとなってくれました。

 

今後はこの経験を活かし、誰もが「問題 vs 私たち」のマインドで越境しやすくなるような、再現性のある仕組みや環境づくりにも挑戦していきたいと考えています。

 

本記事が、「職能の枠にとらわれず、プロダクトの価値向上にダイレクトに貢献したい」と考えているQAや開発者の方にとって、弊社(とくにQAチーム)のカルチャーを知る一つのきっかけになりましたら幸いです。

私たちの活動に興味を持っていただけた方は、ぜひ下記の採用ページをご覧ください😊

https://career.m3career.com/midcareer/engineer

少人数チームでマルチプロダクトを支える、"頑張りすぎない"プラットフォームエンジニアリング

こんにちは!エムスリーキャリアの薬剤師採用支援チームでチームリーダーを務めている諸岡です。

私たちのチームでは現在、少数精鋭の体制でフェーズや技術スタックの異なる複数のプロダクトを運用・開発しています。限られたリソースでこれらを安定成長させるため、私は頑張りすぎないプラットフォームエンジニアリングというアプローチを採用しています。

認知負荷を制御し、エンジニアリングの醍醐味を取り戻す

認知心理学において、認知負荷は大きく以下の3つに分類されます。

  1. 課題内在性認知負荷: ビジネスロジックやドメインの複雑さなど、解決すべき問題そのものの負荷。
  2. 課題外在性認知負荷: インフラ構成、煩雑なデプロイ手順、ライブラリ更新など、本質に関係のない環境や手順の負荷。
  3. 学習関連認知負荷: 知識を構築し、新しい技術を自分のものにするためのポジティブな認知活動。

以前の記事 公式noteから紐解く、エムスリーキャリアのエンジニア組織が目指す事業貢献の「本質」 - M3Career Techblog でも発信しましたが、弊社には「課題の本質に向き合って事業そのものを伸ばしたい」と考えるエンジニアが多く集まっています。
そして、ユーザーの課題を解き、複雑なドメインモデルを組み上げている瞬間 -つまり課題内在性認知負荷に向き合っている時こそ、エンジニアの創造性が最も発揮されると私は考えています。

私たちのチームでは、課題外在性認知負荷を極限まで減らし、本質的課題にフルコミットできる環境を創る取り組みを行っています。

頑張りすぎないプラットフォームエンジニアリングとは

少人数のチームにおいて、壮大な社内基盤の構築にリソースを割くことは現実的ではありません。本記事における「頑張りすぎない」の定義を、話さないこと・話すこととして改めて整理します。

本記事で話さないこと

  • 基盤構築の具体的な手法: Terraformの書き方や特定のクラウドサービスの構築手順といった技術的な詳細。
  • 重厚な内製ツールの開発: 社内専用ポータル(IDP)の構築や、プラットフォーム自体の巨大なプロダクト化。

本記事で話すこと(実践していること)

  • 認知資源の適正配置: 限られたチームの「脳内メモリ」を、いかにして事業価値に直結する課題へと割り振るか、という組織設計。
  • 解決すべき「外在課題」の選定: 何をプラットフォームエンジニアリングで解決し、何を各エンジニアの裁量として残すべきかという境界線の引き方。
  • チーム内のセルフサービス化: プラットフォームエンジニアリングで得た知見をチームの集合知とするためのプロセス。

私たちが実践しているのは、ツール開発そのものではなく、エンジニアの創造性を最大化するための戦略です。

「外在課題」と「内在課題」の境界線

頑張りすぎないプラットフォームエンジニアリングにおいて最も重要なのは、どこに境界線を引くかという意思決定です。私は以下の基準で、プラットフォームエンジニアリングで解決すべき「外在課題」を選定しています。

  1. 共通性と再現性: 複数のプロダクトで共通して発生し、かつ同じ解決策が適用できるか。(例:CI/CDの基本構成、セキュリティ設定)
  2. ドメイン知識不要な専門性: ビジネスロジックの理解がなくても、技術スタックの知識だけで解決可能か。(例:インフラのコード化、ライブラリのパッチ適用)

逆に、プロダクト独自の複雑なビジネス要件や、それに伴う新たな技術選定などは、プロダクト担当エンジニアが向き合うべき課題内在性認知負荷としてあえて残します。

立ち上げ期におけるチーム内プラットフォームエンジニアの役割

現在のチーム体制は以下の通りです。

現在は立ち上げフェーズであるため、チームリーダーである私がチーム内SREとして横断課題を吸い上げています。
※SRE(サイト信頼性エンジニアリング)は厳密には誤用ですが、活動内容は似ており、社内で既に浸透している役割のため、このように呼称しています。

一般的にチームリーダーが仕事を巻き取ることは、知識がブラックボックス化するサイロ化のアンチパターンとされることがあります。しかしプラットフォームエンジニアリングの立ち上げ期においては、「何が外在課題で、何が内在課題か」の境界線を見極める意思決定を最速で実行する役割が必要です。

私はこの役割を「作業の代行」ではなく、将来的な委任とセルフサービス化のための先行投資と位置づけています。

具体的な活動内容

単に保守を行うのではなく、各エンジニアが迷わず駆け抜けられる舗装された道路(Paved Road)を作るために、以下の2段階で活動しています。

1. 基盤構築:不確実性とメンテナンスコストの排除

まずは、エンジニアの足を止める「外在性負荷」を徹底的に削ぎ落とすための基盤を整えています。

  • レガシーインフラのIaC (Infrastructure as Code) 化: 秘伝のタレ化していた手動設定をTerraformでコード化し、インフラを誰もが触れるツールへ解放。
  • セキュリティの標準化: 各プロダクトで個別に検討が必要だったセキュリティ設定を共通化。プロダクト担当エンジニアが意識せずとも、デフォルトで安全な状態を実現可能に。
  • DevOpsツール設定の標準化: CI/CDパイプラインや、Sentry・Dependabot等の設定を共通化。
  • ライブラリ更新の先行事例: メンテナンスが困難になったライブラリのバージョンアップやリプレースを主導し、モデルケースを作成。

2. チームへのインストール:セルフサービス化への道筋

構築した基盤を「プラットフォームエンジニアだけの持ち物」にせず、チームの集合知に変えていくプロセスです。ここが「サイロ化」を防ぎ、ゴールデンパスを構築するための鍵となります。

  • 課題解決プロセスの相互レビュー: 私が作り上げた設定やIaCのコードは、必ずチームメンバーにレビューを依頼します。「なぜこの構成なのか」を他者に説明し、納得してもらうプロセス自体が、知見の移譲になります。
  • チーム内共有会での発信: 「次はどう触ればいいのか」というコンテキストをチーム全体にインストールします。単なるマニュアル共有ではなく、背景思想を伝えることで、学習関連認知負荷をポジティブに高めています。
  • コードそのものをドキュメントとする: 綺麗に整えられたIaCやCI/CD設定は、それ自体が最高のマニュアルになります。「これを見れば自分でもできそうだ」と思える状態を作ることで、属人化を排したセルフサービス化を促しています。

成果:ビジネスロジックへの集中と、AIと共に自走する現代の開発体験

最大の成果は、プロダクトの年次や経験に関わらず、チーム全員が課題内在性認知負荷、つまり事業を伸ばすための本質的な問いにワクワクしながら取り組めていることです。

特に象徴的だったのは、2025年4月入社の新卒エンジニアの活躍です。彼は外在性負荷に阻害されることなく、入社間もない時期からビジネス議論の最前線に参画しました。

さらに、適切にIaC化されたコードがドキュメントとして機能したことで、彼は現在のコードをコンテキストとしてAIに渡し、AIと壁打ちしながら自力でインフラ設定変更を完遂するという、現代的な自走を見せました。

彼のエントリー プログラムコードは腐るのか? - M3Career Techblog にもある通り、認知負荷がコントロールされた環境だからこそ、早い段階でエンジニアリングの本質に向き合うことが可能になります。

おわりに

プラットフォームエンジニアリングは、事業価値を最大化し、エンジニアが本来の創造性を発揮するための手段です。最初から巨大なシステムを目指すのではなく、チームのフェーズに合わせ、メンバーが最高に面白い課題に集中できる環境を泥臭く整えていくこと。そのプロセスこそが、強いチームを創ると信じています。

エムスリーキャリアでは、技術を武器にビジネスの深掘りに挑戦したいエンジニアを募集中です。興味を持ってくださった方は、ぜひ採用サイトをご覧ください!

career.m3career.com