TECH PLAY

株式会社ラクス

株式会社ラクス の技術ブログ

953

こんにちは、 id:takaram です。 ラクスでは全社で GitHub を利用しており、大半のプロジェクトが GitHub Actions を CI として利用しています。 GitHub Actions は、テストやデプロイまでを自動化する強力な仕組みである一方、正しく使わなければ セキュリティホールとなる危険性 もはらんでいます。 今回は、GitHub Actions のセキュリティについて紹介していきます。 GitHub Actions のセキュリティ ありがちな侵害のパターンと対策 1. サードパーティアクションの侵害 問題 対策 2. 過剰な権限設定 問題 対策 3. run 内での OS コマンドインジェクション 問題 脆弱な例 対策 4. curl | bash 型の危険なスクリプト実行 問題 対策 追加の対策 1. actionlint で構文・展開の安全性をチェック 導入例 2. ghalint で権限設定やタグ指定を検査 導入例 まとめ GitHub Actions のセキュリティ プロダクトコードのセキュリティには皆さん気をつけていると思いますが、CI/CD のコードに気を配る必要性はあまり認識されていないかもしれません。 しかし、2025年に入ってからも CI/CD 基盤を狙ったサプライチェーン攻撃が相次いでいます。 3月: tj-actions/changed-files が改ざんされ、これを実行したリポジトリの GitHub Actions のビルドログにシークレット情報が出力された rocket-boys.co.jp 8月:npm パッケージ Nx の GitHub Actions の設定が悪用され、不正なコードが混入したパッケージをリリース(通称 "s1ngularity" 攻撃) blog.jxck.io これらはいずれも、CI/CD がアプリケーションと同じレベルで攻撃対象になっていることを示しています。 開発者自身が GitHub Actions のセキュリティを理解し、適切に守ることが重要です。 ありがちな侵害のパターンと対策 ここでは、よくある脆弱なパターンを4つ取り上げます。 それぞれのリスクと、すぐに実践できる対策をセットで紹介します。 1. サードパーティアクションの侵害 問題 GitHub Marketplace などで配布されている外部アクションは、メンテナーが乗っ取られたり、公開リポジトリが改ざんされると、攻撃者が不正なコードを仕込むことができます。 2025年3月に発生した tj-actions/changed-files 改ざん事件では、多数のリポジトリが影響を受けました。 タグ指定(例: @v3 や @v3.6.0 )では、タグは後から付け替え可能であり、改ざんの影響を受けてしまいます。 対策 commit SHA で固定するのが有効です。 uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 ただし、この場合アクションの新バージョンがリリースされ、通常の機能追加やバグ修正が行われても自動では最新版が利用されないことになります。 Dependabot / Renovate を利用して適宜最新バージョンに更新するのがいいでしょう。 また、そもそもアクション自体が信頼できるものか、導入時点で確認することも重要です。アクションのコードを確認できればベストですが、せめてメンテナーや開発体制、利用実績などを確認するとよいでしょう。 2. 過剰な権限設定 問題 GITHUB_TOKEN に不要な権限が付与されていると、侵害時にリポジトリ改変やタグ作成・リリース改ざんなどの被害が発生する可能性があります。 また、secret に PAT (Personal Access Token) を設定している場合も、発行時に不要な権限を付与していると、漏洩時の被害が拡大します。 対策 ジョブごとに GITHUB_TOKEN の最小限の権限を明示します。 permissions : contents : read pull-requests : read # 指定していない権限は`none`になる また、上記の権限設定がされていない場合のデフォルトの権限が、リポジトリ設定から変更できます。 これはread onlyにしておきましょう。 Settings → Actions → General → Workflow permissions → 「Read repository contents and packages permissions」を選択 PAT を利用する際は、最低限の権限のみ与えます。 また、そもそも PAT を使わずに、 GITHUB_TOKEN や GitHub Apps トークンを利用できないか検討してください。 3. run 内での OS コマンドインジェクション 問題 run ステップ内で ${{ ... }} を直接展開すると、展開された値がシェルに渡されて解釈されます。 外部由来の値(PRタイトル、入力パラメータなど)に悪意のある文字列が含まれていた場合、意図しないコマンドが実行されるおそれがあります。 脆弱な例 - run : echo "${{ github.event.pull_request.title }}" PRタイトルが Hello"; rm -rf * # のように細工されていると、シェルがそれを区切って解釈し、 rm コマンドを実行してしまいます。 対策 以下のようにします。 1. 外部からの値を環境変数に設定する 2. シェルコマンド内で "" で囲って使用する - env : TITLE : ${{ github.event.pull_request.title }} run : echo "$TITLE" ${{ ... }} の直接展開は避け、必ず変数をダブルクオート付きで使いましょう。 これにより、シェルのワード分割やメタ文字解釈を防止できます。 4. curl | bash 型の危険なスクリプト実行 問題 curl https://example.com/foo.sh | bash のような形で外部スクリプトを直接実行すると、配布元サーバーや通信経路が侵害された場合に不正なスクリプトを実行させられる危険があります。 対策 一度ファイルとしてダウンロードし、チェックサム検証などを行ってから実行する。 可能な限り、公式パッケージマネージャ(apt, npm など)を利用する。 追加の対策 こうした安全な設定を、チーム内に浸透させ、全員が気をつけて実装するというのはなかなか難しいものです。 そこで、自動検出の仕組みが有効です。プロダクトコードに対して lint を実施するのと同様、ワークフローファイルも CI を利用して機械的にチェックしましょう。 ここでは、GitHub Actions の構成を lint できる代表的なツールを紹介します。 1. actionlint で構文・展開の安全性をチェック actionlint は、GitHub Actions のワークフローファイルの構文チェックをしてくれるリンターです。 基本的な構文チェックに加えて、上記で紹介した run: 内の危険な ${{ ... }} なども検出してくれます。 ShellCheck がインストールされている環境 1 であれば、 run: 内のシェルコマンドに対してもLintを実行してくれます。 変数をダブルクオートで囲んでいないなどの問題を検知可能です。 導入例 actionlint はビルド済みのバイナリが配布されているので、ダウンロードするだけで利用可能です。 ただし、ダウンロードしたバイナリが改ざんされていないか、チェックサムで確認しておきましょう。 env : ACTIONLINT_VERSION : "v1.7.8" ACTIONLINT_SHA256 : "be92c2652ab7b6d08425428797ceabeb16e31a781c07bc388456b4e592f3e36a" steps : - uses : actions/checkout@v5 - run : | gh release download "${ACTIONLINT_VERSION}" -R rhysd/actionlint \ -p 'actionlint_*_linux_amd64.tar.gz' -O actionlint.tar.gz echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum -c - tar -xzf actionlint.tar.gz actionlint - run : ./actionlint 2. ghalint で権限設定やタグ指定を検査 ghalint は、 permissions: の指定漏れやサードパーティアクションのタグ指定( @v3 など)を検出するツールです。 actionlint に比べて、よりセキュリティにフォーカスしたチェックを行ってくれます。 導入例 ghalint もビルド済みバイナリをダウンロードして利用可能です。 ダウンロードしたバイナリの検証はチェックサムでもいいですが、 アーティファクトの構成証明 も利用できます。 env : GHALINT_VERSION : "v1.5.3" steps : - uses : actions/checkout@v5 - run : | gh release download "${GHALINT_VERSION}" -R suzuki-shunsuke/ghalint \ -p "ghalint_*_linux_amd64.tar.gz" -O ghalint.tar.gz gh attestation verify ghalint.tar.gz \ -R suzuki-shunsuke/ghalint \ --signer-workflow suzuki-shunsuke/go-release-workflow/.github/workflows/release.yaml tar -xzf ghalint.tar.gz ghalint - run : ./ghalint run まとめ GitHub Actions は開発を支える強力な仕組みであり、現代の開発にはなくてはならないものです。しかし同時に、攻撃者にとってのターゲットにもなり得ます。 設定ミスや依存先の侵害が攻撃につながる以上、CI/CD もアプリケーションと同じように守る必要があります。 今回紹介した対策を徹底し、安全に CI/CD を実行していきましょう! GitHub-hosted runner の ubuntu-24.04 などには ShellCheck がデフォルトでインストールされています。 ↩
「開発が遅い」と嘆くのは簡単です。でも本当に遅いのは“人”ではなく、“見えない仕組み”かもしれません。 ラクスの開発組織が大切にしているのは、「顧客志向」です。 そして私たちは お客様に価値を速く・確実に届けるための基盤づくり として開発生産性の向上に重きを置いています。 そのため各チームは、プロダクトの性質やお客様のニーズに合わせて、プロダクトごとに合理的な開発スタイルを選択しています。 そして、開発生産性に関わるデータを活用し、スタイルに合った施策を日々検証することで、価値提供のスピードと確実性を高めています。 今回は、そうした多様な背景を持つ開発チームが行っている リアルな「生産性向上のための取り組み」 をチラっとご紹介します。 多様な技術スタックを持つチームが、それぞれの現場でどんな工夫をしているのか。 そこには  「開発生産性を科学する文化」  があります。 目次 データが語る、チームの「今」 チームによって違う、“スピードの壁”への取組 「数字」ではなく学びの共有に注目する文化 さいごに データが語る、チームの「今」 ラクスでは開発の現在地を見える化するために、  (1) チームごとの客観データ収集  (2) Findy Team+や社内ツールで可視化  (3) リーダーによる改善 というサイクルを継続しています。 可視化結果はメンバー層まで共有され、チーム全体でボトルネックを発見・議論できるようになっています。 (※ Findy Team+については先行記事 参照) 可視化されている値の例として、 全チームのPR(Pull Request)データをFindy Team+で分析し、 「オープンからレビューまでの平均時間」や「レビューコメント数」 といった指標を毎週~毎月追跡しています。 開発に要した時間だけではなく、レビュー待ち時間やレビュアーの偏りなど、 開発サイクルの“詰まりポイント”を見つけ出し、改善の余地をチーム単位で議論しています。 プルリク数と経過時間の可視化イメージ あるチームでは、オフショア開発体制ゆえにレビュー周りの時間が長引く課題がありました。 そこで AIを活用した一次コードレビューを併用し、ヒトの負荷を減らしつつ質とスピードの両立を実現 しました。 レビュアーの偏りがボトルネックになっていたチームでは、 1PRあたりの変更量を下げてレビュー負荷を分散 しやすくしました。 同様の他チームでは、レビュアーを育てる仕組みに意図的にシフトすることで、負荷分散を目指しています。 単体テスト工数が規模に対して大きく、コストが課題になっていたチームでは、 AIを使ったテストの自動化を進め、テスト工数の半減 を実現しました。 ラクスの開発チームでは、顧客ニーズの解決に直結するPR数の増加とその消化スピードを上げるため、 開発現場をよく知る 開発リーダーが直接GitHub統計や各種社内ツールを組み合わせて分析、 原因を深堀、改善施策を打つ ことで高速な改善サイクルを実現しています。 チームによって違う、“スピードの壁”への取組 開発スタイルは、前述した通りチームによってさまざまです。 WFで設計精度を高めるチーム、Agileで小さく早く試すチーム、両者を織り交ぜるハイブリッドがあり、 いずれも担当サービスのお客様に最短で価値を届けるために戦略的に選択しています。 全チームの共通事項として、 「4Keys指標(Googleが提唱するDevOps成功の4つの指標)」を意識しつつ より自律的に改善できる指標として 自チームのスタイルに合った指標 を置いています。 前項でも挙げたように、"管理される側"というよりも、自発的に“改善を設計する側”として指標を定めて動くことで、 ラクスのユニークネス「ゴールオリエンテッド」、「着実な継続」、「不確実性の排除」(※)を地についた形で実現しています。 ※ ラクスのユニークネスについては別記事 参照 開発スタイルに即した開発生産性向上の施策として興味深いものでは、オフショア開発の事例があります。 ベトナム開発課では、現地メンバーが主導して工程ごとの指摘数や本番バグ発生率を分析しています。 レビューコメント数の集計、内容確認を振り返り会に組み込み、テスト工程へのフィードバックに活かす など、 開発拠点間の距離を“データ”で埋めることでスピードUPに繋げています。 オフショア開発という開発スタイルならではの取組と思います。 なおラクスの海外拠点では、 “本社の指示を待つ”のではなく、“自分たちで課題を見つけ、仕組みを考える”思想が根付いています (もちろんコミュニケーションも密に行っています)。 そこに Findyなどの数値で見られる各種ツールや、翻訳が容易になるAIが加わることで、私たちは言語的、地理的な壁を越えた認識の共有を しています。 「数字」ではなく学びの共有に注目する文化 開発本部では、結果として起きた数字の変化に対して、 「数字」を共有して優劣を競わせるのではなく、 変化させるために行った施策で得た「ナレッジの共有」を促進しています。 これは、手探りになりがちな施策の検討にあたり、リーダー陣が闇雲に施策を試すのではなく、 数値をベースに過去事例の根拠を以て小さく試し、大きく育てることにより、 高効率で生産性の向上をするためです。 この分析サイクルとナレッジ共有が習慣化すれば、結果としてスピードも品質も向上します。 全チームの施策は経過に伴い当然アップデートされます。 各施策の進行途上で得た ナレッジは、3か月ごとに開発本部全体で共有され、次の一歩のヒントになります 。 あるチームの課題が、別のチームのアイデアで解決される、 「学びの連鎖」が生まれる仕組み となっています。 この仕組みの中で、チームリーダーたちは“数値を見る目”を磨いていきます。 単なるチームマネジメントではなく、「開発者として顧客へどうスピーディに価値を届けるか」を リーダークラスのメンバーが主体的に考え、実践しています。 ラクスの開発現場は、 コードを書く「だけじゃない」、言われたことをやる「だけじゃない」 データをもとに開発文化を進化させる、「だけじゃない」高次のスキルを磨ける環境です。 この環境で働くエンジニアは自然と、今後更に進化するAI駆動開発にも柔軟に対応していくものと思います。 さいごに 生産性向上の目的は、「顧客に価値を速く・確実に届ける」ための基盤づくりです。 今回は、顧客へ価値を届けるために、データを使って生産性向上に挑む私たちの取組を紹介させていただきました。 開発をもっと速く、もっと楽しく! ラクスは、開発の未来を作りたい仲間をお待ちしています!
はじめに こんにちは、楽楽精算開発チームの坂田です。 私は今年、楽楽精算における新機能追加プロジェクトにアサインされ、その中で新規アプリケーションの開発を担当する機会をいただきました。 アプリケーションの新規開発は様々な要件を考慮しながら進める必要があり、検討事項は多岐に渡ります。 今回は私自身の振り返りも兼ねて、アプリケーションを設計する際の流れや考え方を共有させていただきたいと思います。 内容はどちらかというと初歩的な物が多く、これから設計にチャレンジしようという方に向けたものとなっています。 この記事が皆さんのアプリケーション開発の一助となれば幸いです。 はじめに 今回の要件 システム構成を検討する リアルタイムデータ受信 DBは既存と共通 アプリのデプロイ先 アプリを設計する アプリケーションフレームワーク DBアクセスフレームワーク トランザクション制御 DBコネクション コネクションプール DB接続の上限 スレッドプール コードアーキテクチャ API I/F仕様 ビジネスロジック ログ、メトリクス、トレース まとめ 今回の要件 今回開発に取り組んだ機能の要件はおおよそ以下の通りです。 既存システムが外部から日次でファイル取り込みしているデータを、発生の都度リアルタイム取込できるようにしたい 取り扱うドメインは単一 開発中につき対象となるドメイン等の詳細は割愛 楽楽精算ではあるデータを外部から取り込むためのバッチが稼働しているのですが、日次起動という都合上、お客様の元にデータをお届けするタイミングは一日一回の決められたタイミングとなります。 このデータをよりタイムリーにお客様へお届けするため、データが発生するたびにリアルタイム取込を実現したいというのが今回の背景になります。 またデータのやりとりは B to B の限られたサーバー間でのみ行われます。 非機能要件は以下の通りです。 性能要件 想定される最大秒間処理回数:1 処理完了:3秒以内 通信は限られたサーバー間のみで発生し、頻度もそれほど多くないと言えます。 性能についてはあまり神経質になる必要はなさそうです。 (本当はセキュリティ要件とか運用・保守要件とか色々ありますが、このあたりは表に出せないので割愛) システム構成を検討する まずは要件の達成に向けてざっくりとした青写真を描いてみます。 今回のシステムの特徴は、おおよそ以下の通りまとめられそうです。 リアルタイムのデータ受信 DBは既存と共通 それぞれの特徴ごとに考えられることを列挙してみます。 リアルタイムデータ受信 どのプロトコルを採用するか 性能目標は? セキュリティは? DBは既存と共通 既存DB構成 既存DBの使用状況 順番に見ていきましょう。 リアルタイムデータ受信 今回は外部サーバーでデータが発生するたびに、こちらのサーバーでデータを受け取れるようにする必要があります。 パッと思いつくだけでも色々なやり方がありそうです。 HTTPで受信 どこかにファイルを置いてもらって、SFTPでポーリング どこかのキューに入れてもらって、それをサブスクライブ どれも一長一短あるかと思いますが、今回はHTTPを採用します。 データ送信元でもこちらの処理結果をトラッキングする必要があるため、非同期でデータのやりとりをするよりは、その場でこちら側のステータスを返却できる方が都合が良いからです。 (非同期でもできなくはないですが、ステータスをトラッキングするための仕組みを別で用意するといった一手間が必要になると思います) 今回は単純なデータ受信でセッションのような仕組みも必要ないことから、アプリ自体の構成も自然とREST-APIを採用することになりそうです。 また、性能については性能要件で記載した通りです。 DBは既存と共通 アプリ自体は新規で開発しますが、使用するDBは既存と共通です。 この場合、既存DBの使用状況、特にコネクション数には注意した方が良いと思います。 既存でコネクション上限すれすれの運用をしていた場合、追加のコネクションを貼れない可能性があります。 その際はDB側のコネクション上限を引き上げる等の対策が必要になります。 (DBが稼働しているマシンのスペックとの相談になるので、場合によっては単純に上限を引き上げるだけでは対応が難しいこともあると思います) 今回は幸い、対象DBのコネクションには余裕があるので、この点は問題にならないと考えて良さそうです。 また、DBの構成も重要です。 今回はアプリ一つに対して複数のDBが存在する構成になっており、アプリはデータの内容に応じて登録先となるDBを選択します。 このようにアプリ:DBが1:Nとなっている場合、アプリ側が保持するコネクション数にも注意を払う必要があります。 コネクションが多ければ多いほどアプリが稼働するサーバーのリソースを消費しますし、場合によってはアプリ側のコネクション上限に引っ掛かります。 そのあたりはDBと同様、サーバーのリソースと相談しつつ、コネクション上限を調節することになります。 さらに今回のシステムは少々特殊で、データを受け取った時点では、データを登録すべきDBをすぐに特定できない構成となっています。 このようなマルチテナント構成の場合、データ内部にテナント識別子のようなものがあって、それが直接DB名になっていたり、あるいはそれを元にどこかのデータストアにDB接続先を照会するといった仕組みが一般的かと思いますが、この既存システムは以下のようなアプローチが採用されています。 各DB内に保持しているテナント識別子を全て取得する アプリが受信した識別子と、1で取得した全識別子を突合する 2で合致した識別子を持つDBにデータを登録する 図で表すと概ね以下のようになります。 この構成は、「データ登録先を特定するために、一度すべてのDBに接続する必要がある」という点に特徴があります。 みなさんご存知かと思いますが、DB接続はコストの高い処理です。 これが一つや二つなら無視できる程度のオーバーヘッドかもしれませんが、数が増えていくにつれて無視できるものではなくなっていきます。 このシステムに含まれるDBは、現時点で100を超えています。 これはすなわち、100回を超えるDB接続処理がデータ取り込みのたびに実行されているということです。 これだけの回数になると、非常に大きなオーバーヘッドとなります。 既存システムはバッチ処理であり、多少時間がかかっても処理が完了すれば大きな問題にはなりません。 そのため、性能上の懸念はありつつもこの構成で運用されてきました。 しかし、今回作成するアプリはリアルタイムデータ受信用のAPIです。 データ受信頻度は既存システムと比較になりません。 データを受信するたびに100回以上のDB接続処理を繰り返していては、性能要件を達成することは到底不可能です。 そこで今回はDB側にも以下のような構成変更を行います。 新しく「テナント情報DB」を作成し、そこに各DBに散らばっているテナント識別子を全てコピーして集約します。 (テナント識別子を登録する別のアプリを改修し、各DBとテナント情報DBの情報が常に同期されるようにしています) 新しいアプリはテナント情報DBにテナント識別子を照会することで、全てのDBを参照することなく、最短ルートで適切なDBにデータを登録することができます。 少々回りくどいやり方ですが、今回は既存のデータ取り込みを変更することができないため、やむを得ずテナント識別子のコピーというやり方を選択しました。 (既存も併せて変更できる場合は、各DBのテナント識別子を廃し、テナント情報DBに一本化するのが最も適切だと思います) アプリのデプロイ先 アプリをデプロイするプラットフォームについても検討が必要です。 (オンプレ or クラウド、サーバーのOS、etc...) とはいえこのあたりはよほどド新規のサービスでない限りなんらかのプラットフォームがすでに存在していて、そこに載せるのが最善であることが少なくないと思います。 今回の場合は社内で利用可能なKubernetes(k8s)クラスタがすでに存在しているので、そちらにデプロイする方式を採用します。 アプリを設計する ここまででシステム構成を検討することができました。 あとはこれに当てはめてアプリを設計すればOKです。 これまでの情報をまとめると、アプリの特徴は概ね以下のとおりまとめることができそうです。 REST-API 複数のDBを取り扱う必要がある これを元に以下のような観点でアプリを設計していきます。 アプリケーションフレームワーク DBアクセスフレームワーク トランザクション制御 DBコネクション スレッドプール コードアーキテクチャ API I/F仕様 ビジネスロジック ログ、メトリクス、トレース アプリケーションフレームワーク アプリケーションフレームワークはアプリの基本的な構造となる重要な要素です。 最近だと Spring Boot が主流かと思いますが、Quarkus、Micronaut のようなマイクロサービス系も結構使われてたりすると思います。 弊社でも様々なプロダクトで Spring Boot が活用されています。 社内での普及状況も加味して、今回は Spring Boot を採用したいと思います。 現代のWebアプリケーションに必要な機能は大体カバーされていますし、世の中のナレッジも豊富です。 迷ったら Spring Boot でOKくらいのフレームワークと言えると思います。 REST-APIのフレームワークとしても優秀です。 一応、対抗馬として Quarkus の検討も行いました。 こちらはkube-native、コンテナファーストを謳うフレームワークで、GraalVMのネイティブイメージに早くから対応していた点にも特徴があります。 今回作成するアプリはk8sにデプロイすることになっているので、その点で親和性は高いと言えます。 またDBのコンテナなどを手軽に立ち上げることができるDev Serviceという機能が備わっており、開発環境の構築もかなり快適です。 個人的にはおすすめのフレームワークです。 Spring Boot と比較すると、それほど大きな機能差異はないと言えると思います。 Spring Boot もコンテナ運用は十分可能です。 こういった点と社内外に蓄積されたナレッジを考慮して、Spring Boot を採用することになりました。 この辺りはケースバイケースかと思いますが、今後もメンテナンスしていくことを考えると、ナレッジの有無は重要な判断要素だと思います。 DBアクセスフレームワーク Java の DBアクセスには JDBC を活用するのが一般的ですが、標準の JDBC API をそのまま業務アプリで採用するのは稀で、何らかのDBアクセスフレームワークを活用することが多いと思います。 これらを活用することで、DBのテーブル構造をJavaオブジェクトとして表現しやすくなり、より簡潔で可読性の高いDBアクセス処理の記述が可能になります。 多くの選択肢が存在しますが、Jakarta EE の仕様に組み込まれている JPA や、独自の仕様を持つ Mybatis、Doma、jOOQ といったものが選択肢としてあげられます。 JPA はDBとJavaオブジェクトの同期を可能にする仕組みで、Javaオブジェクトに対する作成、更新、削除が独自のメモリ空間に反映され、それらをDBと同期するためのSQLを自動で発行します。 (SQLを記述する仕組みもあります) SQLの記述なしでDBを変更することができるため、適切に設計すれば非常に簡潔で可読性の高いコードを記述することが可能です。 一方でオブジェクトの変更がDBに反映されるまでのメモリ空間上のライフサイクルを適切に把握する必要があり、場合によっては意図しないDB操作を発生させるリスクもあります。 この点で若干ハードルの高い選択肢と言えると思います。 JPA の実装としては、Spring Data JPA や Hibernate 等が挙げられます。 Mybatis、Doma、jOOQ 等は、JavaオブジェクトとDB間のマッピングや、SQL記述方法に独自の仕様を持っているライブラリです。 それぞれの特徴は、ざっくり以下の通りです。 Mybatis SQLをJavaコードやXMLファイルで記述し、それをJavaオブジェクトにマッピングする SQL記述の自由度が高い点が魅力で、特に複雑なSQLを必要とする場合に有用 Doma Mybatisと似たような機能に加え、SQL自動生成機能やコンパイル時のSQLチェックといった開発効率化機能を備える jOOQ SQLをJavaコード上でビルドすることにフォーカスしており、型安全を保ったままDB操作を記述することができる これらには使い勝手の違いこそありますが、SQLを何らかの方法で表現し、それをプログラム上で明示的に実行するという点で共通しており、ここがJPAとの大きな違いと言えます。 技術選定する際はJPAかどうかが一つの軸に挙げられると思います。 今回は以下のような点を加味して、Mybatisを採用します。 個人的に使い慣れている 楽楽精算開発チーム内での採用事例が多い SQLを柔軟に表現できる トランザクション制御 トランザクションの設計はDB操作に関連する重要な要素です。 ここではプログラムのどの単位をDBトランザクションの区切り(トランザクション境界)とするかを検討します。 例えばあるAPIの中に大きく分けてA、B、Cの三つの処理が含まれているとします。 これに対し、いくつかトランザクション境界のパターンを考えてみます。 すべて一つのトランザクションとする A、B、Cすべての処理が成功した場合のみコミットされる いずれか一つでも失敗すれば、すべてのDB操作がロールバックされる 「A、B」と「C」で分ける A、Bは両方成功しないとコミットされない Cが失敗しても、A、Bはロールバックされない すべて個別のトランザクションとする いずれかの処理が失敗しても、他のトランザクションはロールバックされない このように、どこにトランザクション境界を置くかによって、最終的なDB操作の結果が変わってきます。 特にいくつかの単位に分ける場合、何らかの処理が失敗しても、すでにコミットされたトランザクションはロールバックできないという点に注意が必要です。 もし全ての処理が常に同期している必要があるにも関わらず、Cの結果のみコミットされないといった場合、Cの結果が欠落した中途半端なデータが出来上がります。 またエラーが発生したトランザクションの後にも別のトランザクションを発行する処理が実装されている場合、エラーとなった時点で処理全体を止めるのか、それとも処理を継続して別のトランザクションを開始するのかといった判断も必要になります。 例えば処理の実行結果をDBに残すと言った場合、処理の成否に関わらず履歴が登録される必要があります。 この場合、先行するトランザクションが失敗した場合も、履歴登録処理は確実に実行されるよう設計されているべきです。 以上のようなことを踏まえて、今回のアプリケーションにおけるトランザクション設計を考えてみます。 システム構成でも述べた通り、一回の処理で二つのDBに接続する必要があります。 そのため、これらを一つのトランザクションとするか、それともDBごとにトランザクションを分離するかという判断が必要になってきます。 複数DBを一つのトランザクションとして扱う場合、2フェーズコミットのような仕組みが必要になります。 これは各DBに対する操作をコミット前の状態で待機させ、全てのDB操作が成功したタイミングでコミットされます。 (いずれかが失敗した場合はすべてロールバックされます) 複数DB間でデータの一貫性を保証できる一方、どこかの処理が遅延すればそれだけ多くのDBをロックすることになり、パフォーマンス上の影響が懸念されます。 こういった影響を最小限に抑えるための適切な設計が必要になるため、ハードルの高い選択肢と言えます。 まずはこれらに対するトランザクションを一つにまとめる必要があるか判断します。 今回の場合、「テナント情報DBからテナント情報を取得→取得した情報を元にいずれかのDBに登録」という流れで処理が行われます。 テナント情報DBへの接続は読み取り専用になるため、トランザクションについてはそれほど意識する必要はなさそうです。 これ以降の処理で何らかのエラーが発生したとしてもテナント情報DBの状態には一切影響がないため、パフォーマンス上の影響も加味して、2フェーズコミットを採用するメリットはあまりないと考えられます。 そのため、今回は各DBアクセス単位でトランザクション境界を作成する設計とします。 DBコネクション コネクションプール 先述の通り、DB接続は比較的コストの高い処理です。 あまりにも頻繁に行っていると、CPU使用率の増加でアプリケーション、ひいてはサーバー全体のパフォーマンスに悪影響を及ぼす恐れがあります。 それを避けるために一般的な手段がコネクションプールです。 これは一度開いたDB接続をプールしておき、次回以降の処理で使い回すというものです。 これによりDB接続の頻度を下げ、一回の処理あたりのオーバーヘッドも下げることができます。 Spring BootはデフォルトでHikariCPによるコネクションプールが備わっているので、特に意識しない限りこれを使うのが一般的かと思います。 一方、プールされたDB接続の個数が増えるにつれ、メモリ使用量も増えていきます。 アプリ:DBの数が1:1のような構成であればそれほど大きな問題になりませんが、これが1:Nとなるとどうでしょうか。 今回のシステム構成では一つのアプリケーションが100個を超えるDBにアクセスするため、コネクションプールも相当数確保する必要があります。 (一つのDBに対するコネクションは一つとは限らないので、これを二つ三つと増やすにつれて、全体のプール数も増加していきます) この場合、そもそもコネクションプールが必要かどうかという点を確認する必要があります。 その際の判断基準の一つが性能要件です。 冒頭を振り返ると、性能要件は以下のようになっていました。 性能要件 想定される最大秒間処理回数:1 処理完了:3秒以内 この程度であれば、コネクションプールがなくても性能要件は十分達成可能と言えます。 既存のように全DBにいちいち接続する必要がある場合はコネクションプールが必須と言えますが、今回はその点も改善されています。 以上のことから、今回コネクションプールは採用しない方針とします。 ここからさらに流量が増えてきた場合は、コネクションプールの導入を検討した方が良いと思います。 そのあたりはケースバイケースなので、早めにPoCを作って性能検証を行い、コネクションプールの有無でどの程度のパフォーマンスが出るか確認した上で判断するのが良いと思います。 DB接続の上限 コネクションプールを活用するかどうかに関わらず、同時に使用できるDB接続の上限を管理する必要があります。 この上限を超えるDB接続はその時点でペンディングとなり、別のDB接続が終了するまで待機することになります。 Spring Boot で Hikari CP を採用している場合、デフォルトの最大値は10です。 今回のアプリケーションでもこの値で性能検証の結果が良好だったことから、上限を10に設定しています。 もしここで性能劣化等の現象が発生した場合は、この値を調節する必要があります。 値が小さすぎるとDB接続待ちが発生しやすくなりますし、逆に大き過ぎれば、同時アクセス数が増えるにつれてメモリ使用量を圧迫すると同時に、接続の切り替えに伴うコンテキストスイッチも増えていきます。 いずれの場合も処理の遅延を招き、DB処理の待ち行列がどんどん長くなっていくことが予想されます。 値の決定には以下のようなことを考慮する必要があります。 性能要件(予想される流量) アプリケーションが稼働するサーバーのスペック DBが稼働するサーバーのスペック 明確な基準については諸説あると思いますが、サーバーのCPUコア数は代表的な判断基準と言えると思います。 一般的な同期処理アーキテクチャを採用するアプリケーションは、DB処理中にCPUコアを占有します。 そして、CPUはコア数を超える処理を同時に行うことはできません。 そのため、コネクション数を多めに設定したとしても、同時実行可能な処理数はコア数で頭打ちとなり、それを超える分はコアが解放されるまで待ち状態となります。 待ちが多くなればなるほどメモリ使用量増加と頻繁なコンテキストスイッチを誘発するので、コネクションの増やしすぎには特に注意した方が良いと思います。 実際の設定値については、CPUコア数を軸に据えて調節しながら、性能検証を重ねて適切な値を割り出すというアプローチが良いと思います。 スレッドプール コネクションプールはDB接続に対してのものですが、スレッドプールはサーブレットのスレッドをプールする仕組みです。 リアクティブなAPIを除いて、世の中で稼働するJavaベースのWebアプリケーションは、大部分がサーブレットです。 Spring Boot も例外ではありません(Web Fluxを採用した場合を除く)。 サーブレットはリクエストを受信するたびに、リクエスト専用のスレッドを確保します。 スレッド作成はDB接続と同様にコストの高い処理なので、リクエストのたびにスレッドを作成していては処理の遅延に繋がります。 それを避けるため、作成されたスレッドをプールしておき、リクエスト受信時に使い回す仕組みがスレッドプールです。 Spring Boot のデフォルト値は200です。 こちらも性能検証で問題がみられなかったことから、この値を採用しています。 もし問題が見つかった場合は、DB接続と同様、CPUコア数を軸に調整するのが良いと思います。 また Virtual Thread も選択肢の一つです。 これは Java21 で登場した新しいスレッドで、DB接続などのI/O中にCPUがブロッキングされないという点に特徴があります。 詳しい説明は省きますが、例えばDBサーバー側で処理に時間がかかっているような場合、従来のスレッドではCPUを占有し続けるため、他のスレッドはこれが解放されるまで待ち状態となります。 これが Virtual Thread に置き換わると、DB処理中にCPUの占有を解除し、他のスレッドがCPUを使用できる状態を作り出すことができます。 これにより、I/O待ちが多く発生するようなアプリケーションにおいてCPUの能力をより効率的に活用し、従来よりも多くのスレッドを同時に扱うことができるようになります。 従来のスレッドを置き換えるだけでリアクティブプログラミングのような恩恵を教授できるという点が画期的で、モダンJavaの中でも注目を集めている技術です。 (今回はパフォーマンス的にそれほどシビアなユースケースではなかったことから採用は見送りました) コードアーキテクチャ 例えばMVCという言葉はかなり一般的かと思いますが、それに類するものがコードアーキテクチャです。 ここではソースコードの構成(レイヤやクラス設計等)を検討します。 これの良し悪しがアプリケーションのメンテナンス性の良し悪しに直結すると言っても過言ではなく、開発速度の担保や安定的な運用に欠かせない重要な要素です。 最近はドメイン駆動設計(DDD)の考え方がずいぶん浸透してきたように感じます。 これはアーキテクチャの中心にドメインを据える考え方で、ドメインの振る舞いや状態に応じてユースケースを組み立てていきます。 それと同時に、DB接続のようなインフラ層の実装をドメインから分離することで、インフラ層の仕様に左右されることなく、ドメインの振る舞いに集中できるようになります。 詳細を語ると長くなるので省きますが、ドメインの振る舞いに集中する考え方は一度慣れると非常にわかりやすく、個人的にもおすすめの手法です。 アーキテクチャの決定には取り扱うドメインや業務要件が大きく関わってきますが、要件が単純な場合はMVCのような三層構造でも良いと思います。 ドメイン駆動設計を取り入れたアーキテクチャはMVCと比較すると複雑な傾向があるので、要件によっては過剰な設計となることも考えられます。 今回作成するアプリケーションは単一のドメインを扱う非常にシンプルな要件です。 この点だけ見るとMVCでも良さそうですが、既存のDB構造と今回取り扱うドメインの構造に微妙なギャップがあり、DB構造にドメインが引きずられることを避ける必要がありました。 ドメイン層とインフラ層を分離できるドメイン駆動の考え方はこの点で都合が良く、今回はこれをよく取り入れているオニオンアーキテクチャを採用しました。 (オニオンアーキテクチャはドメイン駆動設計を取り入れた設計手法の一つで、この他にクリーンアーキテクチャ等が存在します) API I/F仕様 Web-APIの場合、リクエストやレスポンスの形式を定める必要があります。 業務要件に応じて、以下のような観点で設計を行います。 リクエスト URL HTTPメソッド ヘッダ パラメータの種類や形式 レスポンス HTTPステータス ヘッダ ボディ 今回のアプリケーションでは、REST-APIを採用しています。 そのため、URLで操作対象のドメインを、HTTPメソッドで操作内容ををそれぞれ表現する必要があります。 例えば対象のドメインが「hoge」の場合、URLは http://xxx.yyy.zzz/hoge となります。 このドメインを取得する場合、HTTPメソッドは「GET」となります。 これが新規作成になるとHTTPメソッドが「POST」となり、更新では「PUT」、削除では「DELETE」となります。 URLの http://xxx.yyy.zzz/hoge は常に一定で、パスパラメータで操作対象データのユニークキーを指定したり、クエリパラメータで検索条件を設定する場合もあります。 またPOSTやPUTの場合、リクエストボディに登録または更新内容を設定することになります。 今回は要件が単一ドメインの登録のみのため、POSTメソッドに対するAPIエンドポイントを設計すればOKです。 こういったAPIではリクエストボディにJSON形式のデータを設定するのが一般的ですが、今回もそれに倣った設計とします。 Spring Boot ではJSON形式のリクエストボディをJavaオブジェクトにマッピングする仕組みがデフォルトで備わっており、その点でも親和性が高いです。 またREST-APIの仕様書についてはOpenAPIのフォーマットが一般的なので、特にこだわりがなければそちらを採用するのが良いと思います。 (共通言語なので社外とのやりとりもスムーズです) ビジネスロジック 業務要件をプログラムで直接表現するのがビジネスロジックです。 この部分は要件によってまちまちなのですが、少なくともアプリケーションフレームワークやトランザクション制御、コードアーキテクチャなどから逸脱した設計にならないよう注意する必要があります。 (今回の要件はあまり表に出せないため、詳細は割愛します。) ログ、メトリクス、トレース これらはアプリケーションの運用にあたって必要な要素です。 ログはアプリケーションに対するアクセス状況や処理結果等をテキストで出力することで、外部からこれらを確認することができます。 特にエラー発生時に出力されるスタックトレースといった情報は、本番環境のデバッグにおける重要な情報源となります。 また要件によっては機能の実行回数や利用者数を集計したいといった場合があり、それらをログで実現することもあります。 Spring Boot の場合はデフォルトで Logback が有効化されているため、特にこだわりがなければそちらを利用するのが良いと思います。 また Grafana Loki のようなログ集計システムを使用する場合、ログを JSON フォーマットで出力することでラベルごとの集計が容易になります。 メトリクスはサーバーのCPU使用率やメモリ使用率、Javaプロセスのヒープ使用率、GC発生状況等を監視するために取得する情報です。 これらを適切に取得し監視することで、サーバーそのものやプロセスの異常を検知することができます。 これを実現するツールとしては OSS の Prometheus が有名です。 Spring Boot では Micrometer の Prometheus 用エンドポイントを有効化することで、Prometheus が必要としている情報を簡単に取得することができます。 Prometheus が利用できる環境では、これを活用するのが良いと思います。 トレースは処理の流れを追跡するための仕組みです。 処理全体の開始から終了までをトレース、その中の任意のステップ(メソッド呼び出しやDBアクセス等)をスパンとして記録します。 スパンごとの処理時間を計測することでパフォーマンス上のボトルネックを特定したり、エラー発生までの処理内容を確認することで原因特定に役立てるといった活用方法が考えられます。 これはシステム間を跨ぐような処理でも一つのトレースとして扱うことができるため、複数のマイクロサービスが協調して動作する分散システムのような構成では特に有用です。 これを実現するための有名な OSS として OpenTelemetry があります。 Spring Boot には OpenTelemetry と連携する仕組みが用意されているので、これを活用するのが良いと思います。 今回作成したアプリケーションでは、以下の構成を採用します。 (収集および可視化ツールは社内で標準化されたプラットフォームが存在するので、そちらに合わせています) テレメトリー種別 出力 収集 可視化 ログ Logback (JSON形式出力には logstash-logback-encoder を使用) Grafana Loki Grafana メトリクス Micrometer Prometheus Prometheus Grafana トレース OpenTelemetry Java Agent Grafana Tempo Grafana まとめ 新規アプリケーション設計の流れと考え方を振り返ってみました。 ここに書かれているだけでも多くの検討事項がありますが、これ以外に書ききれなかった項目もありますし、書いてある内容もまだまだ掘り下げられると思います。 それだけアプリケーション設計は考えることが多く、奥が深い作業です。 今回のアプリケーションは比較的単純な構造でしたが、改めて振り返るとそれなりに時間のかかる作業だったと思います。 一方で大変なだけでなく、ゼロからモノを作り上げる楽しさもあります。 既存サービスの開発ではアプリケーションを新規で作成するような機会はそれほど多くないので、エンジニアとしては大変貴重な機会をいただけたと思います。 冒頭でも述べた通りですが、今回の投稿がこれから設計挑戦する皆さんの一助となれば幸いです。
目次 はじめに AI-Agentはインフラでどこまで使えるのか 実行環境とセットアップ 3日で進めたTerraform化のプロセス 使ってみて実感した効果 まとめ はじめに このブログの目的(と、ごあいさつ) こんにちは。SREの gumamon です! 昨今のAI Agentの進歩は目覚ましいものがありますね。 Vibe Coding という言葉も登場し、自然言語でAIに指示をすればアプリケーションが作れる時代になる──そんな話もちらほら聞きます。 しかし、Vibe(雰囲気)で作業をされると一瞬で崩壊してしまうのがインフラ領域です。 「AI Agentをどう使っていこう?」と悩まれている方も多いのではないでしょうか。 このブログでは、AI Agentの一種である ClaudeCode を使って、既存のAWS環境をTerraform化した過程をご紹介します。 この記事を読むことで、 インフラ領域におけるAI Agent活用のイメージ を掴んでいただければと思います。 対象読者と前提知識 本記事は、実務で Terraform を使用している技術者を対象としています。 また、以下については(本稿よりも圧倒的に良いブログやドキュメントがあるので)割愛します。 AI Agentについての基礎知識 ClaudeCode とは/使い方 免責 本稿の元となる作業は2025年7月に実施したものです。 執筆時点では、より良い選択肢や方法があるかもしれません (時の流れが速すぎて怖い・・・)。 AI-Agentはインフラでどこまで使えるのか 結論から言うと、現状では 「インフラ(リソース)の変更を伴わない作業」 に留めるのが良いと思います。 先日行われた PLATFORM ENGINEERING KAIGI 2025 のKeynoteで、こんな発言がありました。 AI is an angry intern(AIは怒れるインターン生) 少々言い過ぎですが、「確かにね!」と思うところがあります。 AI Agentに作業を任せる際、人間は「期待するふるまい」をコンテキストとして伝えます。 たとえば次のような指示です。 terraform plan は実行可、 terraform apply は禁止 既存のAWSリソース(ECSなど)をもとにTerraformコードを生成する 序盤はこれを忠実に守って動くのですが、作業が長引くにつれて次第に怪しい提案をしはじめ、最終的には terraform apply を実行してリソースを合わせにいく──そんなことも起こります(というか起こりました)。 現状のAI Agentは 長いコンテキストを保持するのが苦手 で、会話履歴が圧縮される過程で重要な指示が抜け落ちることがあります。 その結果、「やってはいけないこと」をやってしまう。 AIと人間は根本的に特性が異なります。 AIに任せるなら、ガードレール(制限・権限・監視)をきちんと整えることが必須 だと感じました。 実行環境とセットアップ AI Agentを逐一監視(提案されるコマンドのたびにEnter)していたのでは、人間側の作業効率が上がりません。 (諸説あると思いますが)私は AI Agentの実行環境をサンドボックス化し、インフラ構成を変更できない権限 を与えたうえで、自由に作業させることにしました。以下が実際の実行環境です。 sandbox 実行環境で工夫したポイントは以下です。 VirtualMachine(sandbox)環境では自由な振る舞いを許可 root権限を付与 必要なツールをインストール インターネット上の情報を参照可 AI Agentの社内環境アクセスを厳しく制限 Git:Featureブランチのみ操作可能 AWS:Terraform関連リソース以外はすべてReadOnly その他:不要なクレデンシャルは一切付与しない(例:AWS prod環境) AWSへのアクセス権限(IAM User/RoleにアタッチするIAM Policy)は以下の通りです。 ReadOnlyAccess (AWS管理/全リソースへのRead権限) custom-dynamodb-access-for-terraform (ユーザー管理/DynamoDB(lock)へのGet,Put,Delete権限) custom-s3-access-for-terraform (ユーザー管理/S3(tfstate)へのGet,Put,List権限) 3日で進めたTerraform化のプロセス ここからは、3日間で実際に行った作業と悩んだことを書いていきます。 Day1:ClaudeCodeのインストールとプロンプト整備 実行環境の作成 → ClaudeCodeの導入 クイックスタート を参考にインストール(Node.jsが必要) 対象リポジトリのルートで claude コマンドを起動し /init 実行 CLAUDE.md(プロジェクト用プロンプト)が生成される 内容が薄いとClaudeがうまく理解できないことに気づく Terraformを少しだけ書く サンプルとして軽くTerraformコードを追加(いわゆるワンショットプロンプト) 自分の構成が意外と曖昧なことに気づく README.mdにTerraformフォルダ構成を書く フォルダ構成と役割を整理しながらREADMEに記載 (構成ツリー) . ├── README.md # README └── terraform # Terraform Code ├── bootstrap # Bootstrap Code (初期化用コード: Terraform自体の初期設定を行う) │ ├── dev # Development Bootstrap Code (Terraformの実行ディレクトリ: dev環境用) │ │ ├── main.tf # Main Terraform file for dev bootstrap (Terraformのエントリーポイント) │ │ └── variables.tf # Variables for dev bootstrap (dev環境用変数) │ └── prod (WIP) # Production Bootstrap Code (Terraformの実行ディレクトリ: prod環境用) ├── environments # Environment Code (環境ごとのコード: dev,prodなど) │ ├── dev # Development Environment Code (Terraformの実行ディレクトリ: dev環境用) │ │ ├── main.tf # Main Terraform file for dev environment (Terraformのエントリーポイント) │ │ ├── variables_global.tf # Global variables (グローバル変数) │ │ ├── variables_platform.tf # Variables for platform module (プラットフォームモジュール用変数) │ │ ├── variables_gateway.tf # Variables for gateway module (ゲートウェイモジュール用変数) │ │ └── ... │ └── prod └── modules # Modules (モジュール: 再利用可能なコードの集まり) └── common # Common Modules (共通モジュール: 複数の環境で使用される共通のリソース) ├── platform # Platform Modules (プラットフォームモジュール: VPC, IAMなどの基盤となるリソース) │ ├── vpc.tf # VPC configuration (VPC設定) │ ├── xxx.tf # xxx configuration (任意のリソース設定: .tfファイルはリソースごとに準備する) │ ├── variables.tf # Variables for platform module (プラットフォームモジュール用変数) │ └── outputs.tf # Outputs for platform module (プラットフォームモジュールの出力) ├── gateway # Gateway Modules (ゲートウェイモジュール: API Gatewayなどの設定) │ ├── alb.tf # Application Load Balancer configuration (ALB設定) │ ├── xxx.tf # xxx configuration (任意のリソース設定) │ └── variables.tf # Variables for gateway module (ゲートウェイモジュール用変数) └── ... 一日目終了。 Day2:AWSリソースのTerraform化 ClaudeCodeのPJ初期化再実行 /init 実行 → より良いCLAUDE.mdが生成される 既存AWSのTerraform化を開始 Claudeに「既存AWS環境から1モジュールを作成」と依頼 想定外の提案も多く、しばらくペアプロ的に進行 成功したケースを要約してCLAUDE.mdに反映 試行錯誤の末、安定して出力できるようになり、2モジュールをTerraform化 (プロンプト例) # 目的 VPCのリソースをTerraformで管理したい - すでにAWS環境のリソースは存在しています (.aws/config の情報でアクセス可能) - AWSリソースを調査し、Terraformのコードを作成する必要があります - さらに、既存のリソースをTerraformで管理するために、 ` terraform import ` を使用する必要があります - 複雑なタスクです ` think ` して取り組んでください。調査ができ、**実行計画を立てることができたら一度私に確認させてください** # 追加するリソース - VPCに関するリソース # その他要件 - リソースはplatform moduleに追加してください - environments/dev から platform module を呼び出すコードを作成してください - moduleの変数は適宜設定してください - 構成や変数化の粒度については既存のコードを参照 二日目終了。 Day3:Terraformのリファクタリング リファクタリング方針を検討 Day2のコードではIAMをplatform moduleに含めていたが、 それではgateway moduleなどをスケールさせる際に柔軟性がないと気づく 各moduleで使うIAMをそれぞれに分離する方針へ変更 ClaudeCodeにリファクタを指示 想定通りに動かないため、再びペアプロモードへ 成功パターンを要約し、 additional_prompts/ 以下に保存 CLAUDE.mdからリンク参照させる方式を試す NOTE CLAUDE.mdが長くなってきたため、プロンプトを分割。 結果として「どの追加プロンプトを編集すべきか」が明確になり、体験が向上しました。 仕上げ Terraform全体をレビューさせ、 命名規則・タグのばらつき を指摘させ修正 人間による最終チェックを実施し、 #TODO コメントをClaudeに処理させて完了 使ってみて実感した効果 1. とにかく速い 序盤はClaudeCodeの動作を見守っていましたが、 プランニングもコーディングも人間の速度を遥かに超える 。 自力で1ヶ月かかる作業が、試行錯誤込みで3日で完了しました。 インフラ領域でも、今後仕事の進め方が根本的に変わると実感しました。 2. 想定以上に応用が効く 以前ならAWSリソースをTerraform化するには Terraformer が必須でした。 しかし、対応外リソースでもClaudeCodeは問題なくコードを生成。 ドキュメントを読んだり学習済み知識を活用したりしているようで、十分な精度を感じました。 3. 思い切った意思決定ができる Day3でIAM設計の問題に気づきリファクタに踏み切りましたが、手書きでTerraform化していたら費用対効果の面で諦めていたと思います。 Agentと協働することで、人間の業務をより抽象度の高い領域へシフトできると実感しました。 4. 思考整理の効果がある Day1でも触れましたが、 曖昧な要件では曖昧な出力しか得られません 。 Agentに読ませるプロンプトを練る過程で、自分の理解が不十分な部分が浮き彫りになり、思考が整理されていきました。 「自分がわからないことは指示できない」という制約が、結果的に品質を高めているように感じました。 まとめ 今回は、ClaudeCodeを使用した既存AWS環境のTerraform化の事例をご紹介させて頂きました! インフラ領域へのAI Agentの活用検討の一助となれていましたら幸いです。 以上、最後までお読み頂きありがとうございました!
202025年9月30日(火)、paiza株式会社と株式会社ラクスは、共同技術イベント「AI導入最前線!理想 vs 現実『AIドリブン開発』 〜エンジニア視点で語るリアル活用術〜」を開催しました。 「AIが自動でコードを書いてくれる」「開発効率が劇的に上がる」といった輝かしい「理想」の一方で、開発の現場では多くのエンジニアが現実的な課題に直面しています。 本イベントでは、AI開発の最前線で活躍する4名の登壇者が、実際に直面した困難や、それを乗り越えるために生み出したリアルな解決策を共有しました。 全てのセッションに共通するキーワードは 「コンテキスト(文脈)」 。AIの真価をいかに引き出すか、その実践的なヒントが満載のイベントの様子をレポートします! セッション1:属人化から「チームの力」へ。ラクスが挑んだ組織的なAI活用 セッション2:人とAIが共生する新しい開発フロー「AIエージェントを使った爆速デモアプリ作成」 セッション3:AIによる自律的並列処理でテストを効率化!「Claude Codeによる自律的並列分析の実践」 セッション4:コードを書かないマネージャーがつくる「コンテキストエンジニアリング」 セッション5:10年以上続くWebサービスのAIファースト時代への向き合い方 まとめ:AIドリブン開発は「魔法」ではなく、地に足の着いた「実践」 セッション1:属人化から「チームの力」へ。ラクスが挑んだ組織的なAI活用 登壇者:株式会社ラクス 石田 浩章 speakerdeck.com AI活用の第一歩は、個人のスキルや熱意といった「点」を、いかにして組織全体の「面」の強さに変えていくか、という課題にあります。 このセッションでは、ラクス社がAIツールの導入初期に直面した「活用レベルが個人の意欲に依存し、社員間の格差が生まれてしまう」という課題から、いかにして脱却したかが語られました。 取り組みのポイント 推進体制の構築: 各チームのリーダーによる「推進チーム」と、現場の旗振り役となる「推進役」を設置し、トップダウンとボトムアップを組み合わせた体制を構築。 「まず実践」の文化醸成: 完璧な計画を待つのではなく、「まずやってみよう」という文化を徹底し、実践から得られる学びを重視。 知見の共有サイクル: 各チームの成功・失敗事例をリーダーが集約し、汎用的な知見として全体に共有する学習サイクルを確立。 この4ヶ月間の取り組みの結果、AIツールの実務利用率は80%から100%に向上。さらに注目すべきは、 自発的な情報共有の活動量が34%も向上した 点です。これは単にツールが導入されただけでなく、AIを軸とした「学習する組織文化」が根付き始めたことを示しています。 AI活用を成功させる鍵は、ツール導入そのものよりも、チームで学び、成長し続ける文化をいかに育むかにある、という重要な示唆が得られました。 セッション2:人とAIが共生する新しい開発フロー「AIエージェントを使った爆速デモアプリ作成」 登壇者:株式会社ラクス 北嶋 初音 speakerdeck.com 「AIを開発プロセスにどう組み込むか?」この問いに対し、人とAIがそれぞれの得意な領域で力を発揮する、新しいワークフローが紹介されました。 新規プロダクトのPoC(概念実証)開発という、スピードが命のプロジェクトで、Miroのワイヤーフレームだけを元に「動くデモアプリ」を迅速に構築する必要がありました。そこで編み出されたのが、人とAIの役割を明確に分担した開発フローです。 人とAIの役割分担 【人】お手本の実装: 最初の1画面はあえて人が手動で実装。コーディング規約や設計パターンといった「お手本」をAIに示す。 【AI】土台の実装: 「お手本」コードとワイヤーフレームを元に、AIが2画面目以降の土台を高速で生成(完成度30〜60%)。 【人とAI】壁打ち修正: 人がAIに具体的な修正指示を出し、対話しながら完成度を80%まで高める。 【人】仕上げとレビュー: 細かなUI調整や動的な挙動の実装、最終的なコードレビューは人が担当。 このアプローチにより、 体感で30〜40%の工数削減 を実現しただけでなく、流動的な仕様変更にもスピーディに対応できるようになったとのこと。 結論は明快です。「0→1」の初期設計と「80→100」の最終品質担保は人が担い、その間の定型的な実装作業はAIに任せる。この賢い棲み分けこそが、AIドリブン開発を成功させる核心と言えるでしょう。 セッション3:AIによる自律的並列処理でテストを効率化!「Claude Codeによる自律的並列分析の実践」 登壇者:株式会社ラクス 大口 詩織 speakerdeck.com 大規模で長く運用されているシステムにおいて、品質を保証するためのリグレッションテストは、時に開発のボトルネックとなります。このセッションでは、膨大なテストケースの選定作業を、AIを用いて自動化した非常に高度な事例が紹介されました。 課題は、毎回手動で行っていたテストケースの選定作業の負担が大きいこと。そこで、「バグが起きやすい機能をデータから特定し、テストを優先順位付けする」という目標を立て、過去2年分・約1000件のGitコミットログの分析に挑みました。 この膨大かつ複雑な分析は、人手では現実的ではありません。そこで考案されたのが、AIによる独創的なアーキテクチャです。 "Manager-Worker"モデルによる自律処理 AIツールの使い分け: 方針決定の壁打ちにはChatGPT、具体的な分析作業にはClaude Codeと、役割に応じてAIを使い分け。 自律的な並列処理: 1体の「Manager」役AIが、複数の「Worker」役AIに分析タスクを割り振る構成を構築。これにより、AIチームが自律的に並列で作業を進める仕組みを実現。 この取り組みにより、 人間が介在せずとも処理が進む「手離れ」 を高いレベルで実現するという大きな成果を収めました。一方で、Workerが作業を停止してしまうなど、自律エージェントならではの新たな課題も見えてきたとのこと。ビジネスの根幹にある泥臭い課題に、革新的なアプローチで挑んだ好例です。 セッション4:コードを書かないマネージャーがつくる「コンテキストエンジニアリング」 登壇者:株式会社ラクス 石田 浩章 speakerdeck.com 長年開発されてきた複雑なシステムにAIを導入しようとすると、「過去の設計経緯が不明」「仕様書が整理されていない」「コード量が多すぎてAIが扱えない」といった壁に直面します 。なぜなら、既存の開発はチームに蓄積された「暗黙知(コンテキスト)」に支えられているからです。 このセッションでは、AIの真価を引き出す鍵となる 「コンテキストエンジニアリング」 というアプローチが紹介されました 。これは、AIが人間の意図を正確に理解するために必要な「情報(コンテキスト)」を、いかに設計し、構造化するかという技術です 。 マネージャーとして取り組むべき3つの解決策 ① コンテキストストリーム設計: ビジネスサイドも扱いやすいGoogleドキュメントで要求をまとめ、開発側が管理しやすいMarkdown形式でGitHubに連携させるなど、情報の流れを整えます 。 ② AIによる自動化と支援: ドキュメント形式の変換をAIで自動化したり、専門知識が必要な仕様の整合性をAIにチェックさせたりすることで、属人性を排除します。 ③ できる限りの情報を残す: なぜその技術を選んだのかという背景を「ADR(Architecture Decision Record)」として記録するなど、未来のチームとAIのために、意図的にコンテキストを残していくことが重要です。 重要なのは、より賢いモデルを待つことではなく、 「課題に最適なコンテキストを与えること」 。チーム、関係者、そしてAI自身がより良く開発していける環境を整えることこそ、マネージャーとしてのコンテキストエンジニアリングであると語られました 。 セッション5:10年以上続くWebサービスのAIファースト時代への向き合い方 登壇者:paiza株式会社 高村 宏幸 氏 speakerdeck.com 最後のセッションでは、これまでの具体例を俯瞰し、AI活用で真の競争優位性を生むための戦略的な視点が示されました。 高村氏は、AI活用には「光」と「闇」の2つの側面があると語ります。 光の側面 デモ映えする派手な活用法(例:0→1のアイデア創出) トレンドの進化が速く、追随しないと競合に劣後する 「守り」 の一手 闇の側面 地味で泥臭いが、ビジネスの根幹に関わる課題解決 自社特有の巨大なコンテキストが必要で、競合が真似しにくい 真の競争優位性を生み出す可能性を秘める 高村氏が強調するのは、 「目的はAIを使うことではなく、ビジネスの課題を解決することだ」 という原則です。たとえAIで実装が速くなっても、レビューやテストが新たなボトルネックとなり、全体のリリースサイクルが短縮されなければ意味がありません。 私たちは、流行りの「魅せ球(光)」を追いかけるだけでなく、自社の文脈に深く根ざした、競合が模倣困難な「決め球(闇)」を磨き続ける必要がある、という力強いメッセージでセッションは締めくくられました。 まとめ:AIドリブン開発は「魔法」ではなく、地に足の着いた「実践」 今回のイベントを通じて見えてきたのは、AIドリブン開発の成功は、単一のツールや魔法によってもたらされるものではない、という力強い現実でした。成功への道筋は、一つの成熟度モデルとして描くことができます。 まず、個人に依存せず、チームで学び成長する 組織的な基盤 を築きます。その上で、人とAIの得意領域を見極め、協業させる 戦術的な高速化 を実践する。さらに、既存システムの複雑さという壁を乗り越えるために、 戦略的にコンテキストを設計 し、AIに与える。そして、現場の真の痛みを解決するため、AIを 自律的なエージェント として活用し、価値の高い課題に挑む。これら全ての活動を、ビジネスの「本質的な課題解決」へと方向づける 戦略的思考 が、その羅針盤となるのです。 イベント全体を貫くテーマとして「コンテキスト」の重要性が繰り返し示されました。ラクスとpaiza社が共有したリアルな知見が、皆さまの開発現場をより良くする一助となれば幸いです。ご参加いただいた皆さま、誠にありがとうございました!
こんにちは、プロダクト部 部長の稲垣です。(自己紹介やこれまでのキャリアについて↓をご覧ください。) tech-blog.rakus.co.jp これまで「製品管理課」という名称で運営してきましたが、2025年10月より課を分割し、新しい名称と体制へと進化しました。本記事では、そのご紹介を兼ねてまとめています。 (上位組織である「プロダクト部」については先日まとめましたので、こちらもぜひご覧ください。) tech-blog.rakus.co.jp マルチプロダクトを展開し、かつ多様な製品フェーズを抱える企業において、プロダクトマネージャー組織をどのように設計・運営するかを考える上で、一つの参考になればと思います。また、ラクスのプロダクトマネージャーにご興味をお持ちいただいた方にとっても、その意義や背景を感じていただける内容にしていきたいと考えています。 第二進化の背景 当初12名で1つの組織「製品管理課」として活動していましたが、以下の理由から2つの組織に分割し、名称も「プロダクトマネジメント」へ変更しました。組織変更の背景と理由は以下の3点です スパン・オブ・コントロール(2ピザルール)の観点 リーダーPdMへの権限委譲によるプロダクト成長への寄与 名称の明確化と統一 それぞれについて解説します。 スパン・オブ・コントロール(2ピザルール)の観点 一般的に、マネージャー1人が効果的にマネジメントできる人数は 5~8名 と言われています。 また、2ピザルール(Amazon創業者のジェフ・ベゾス氏が提唱したルール)では「会議やチームは、2枚のピザでお腹いっぱいになる人数までがちょうど良い」とされています。アメリカのピザを基準にすると、2枚でおおよそ 6〜10人 が食べられる想定です。 ラクスにおいても、この考え方が推奨されており、実際の組織運営に適用されています。特にラクスでは 1on1 や 目標設定・管理 を非常に重視しているため、メンバー数が10名を超えるとチーム成果の最大化に影響が出る可能性があります。 つまり、組織が大きくなると意思決定や管理が複雑化するため、適切な規模に分割することで機動力を維持しやすくしました。 リーダーPdMへの権限委譲によるプロダクト成長への寄与 2024年度からは「楽楽精算」だけでなく、「楽楽明細」「楽楽電子保存」の2つも加わり、プロダクトマネジメントする対象が増えました。これにより、プロダクト部でマネジメントしているプロダクトの合計MRRは 250億円超 となり、相対する開発組織も 70名以上、さらにステークホルダーも 10組織以上 に一気に拡大しました。その結果、一人のマネージャーでは対応に限界が出てきました。 そのため、2024年度下期(10月)からは、リーダーPdMに「楽楽精算」のプロダクトマネジメントのリードを任せ、自分は支援に回る体制に移行しました。この結果、楽楽精算に関してはリーダーPdMが自分以上に高い製品解像度を持つようになり、同年10月にリーダーPdMからMGRへの昇格が決定しました。これを機に、組織を分割することとしました。 つまり、各プロダクトに専任のリーダーPdMを配置し、責任と裁量を持たせることで、よりスピーディーに成長へ貢献できると判断しました。 名称の明確化と統一 記載の通り、開発本部内には「開発管理課」という組織があり、「製品管理」という名称では「プロダクトマネジメント」との関連性が直感的に分かりにくい状況でした。そのため、2025年10月より名称を「プロダクトマネジメント」に変更しました。 もともとは開発本部の横断組織に所属しており、その時はすべて和名で統一されていたため違和感はありませんでした。しかし、プロダクト部に移り、さらに配下に「プロダクトデザイン課」が設置されたことで、和名と英名が混在し、違和感がより強くなっていたことも背景にあります。 第二進化の中身 分割については、製品単位で組織を分ける方針としています。また、対になる開発組織として第一開発統括部内に「楽楽精算開発部」「楽楽明細開発部」があるため、これらと連携を取りやすくする狙いもあります。 ※なお、「楽楽明細開発部」は 「楽楽明細」「楽楽電子保存」「楽楽債権管理」 のプロダクト開発を担っています。 プロダクトマネジメント1課  プロダクトマネジメント1課は 「楽楽精算」 を担当しています。役割分担は プロジェクト(PRJ/ミッション)単位 で行っており、1つのPRJは開発規模が大きく、期間も長期にわたるため、2名1チーム で担当する体制を取っています。 プロダクトマネジメント1課の特徴としては以下の4点が挙げられ、これらを踏まえたプロダクトマネジメントが求められます 「楽楽精算」は製品フェーズとして、成長期から成熟期へ移行している 「楽楽精算」は「楽楽シリーズ」の核となる製品である 「楽楽精算」はUXへの課題感があり、さらにシリーズで唯一、ネイティブアプリを有している 「楽楽精算」はシリーズの中で最も早く、AI・AIエージェントの取り組みを進めている プロダクトマネジメント2課 プロダクトマネジメント2課は 「楽楽明細」「楽楽電子保存」「楽楽債権管理」、および プロダクト部内の業務支援 を担当しています。「楽楽明細」は ARR が 100億円に迫る規模 となっており、3名(実質的には1名+0.5名+0.5名) で分担して担当しています。また、プロダクトマネジメント業務のプロセス効率化やAI活用推進 については、「業務支援」がマネージャーと共に担っています。 プロダクトマネジメント2課の特徴としては以下の4点が挙げられ、これらを踏まえたプロダクトマネジメントが求められます。 「楽楽明細」は楽楽シリーズの中でも成長著しいプロダクト(2025年3月期:対前年同期比 +45.7%) 「楽楽電子保存」は「楽楽明細」との連携が強く、製品特性上、他製品との連携ハブとなる製品である 「楽楽債権管理」は2025年7月に販売開始したばかりで、今後 PMF(プロダクト・マーケット・フィット)を目指す製品である ラクスのプロダクトマネジメント業務におけるAI活用は現状バラバラに行われており、ノウハウ共有にとどまっている 更なる進化へ 今後、更なる進化を模索していく中で、10月からのプロダクトマネジメント組織において見えている課題は以下の4点です。 プロダクトマネジメント2課は稲垣が部長と兼務でMGRを務めている デザイナー・PMMとの連携がまだ弱い 統合型ベスト・オブ・ブリード型の実現に向けたPdM体制が不十分 プロダクトマネージャーの製品貢献実感および実際の製品成長に大きな余地がある それぞれについて解説します。 プロダクトマネジメント2課は稲垣が部長と兼務でMGRを務めている プロダクトマネジメント1課では新しいMGRが誕生しましたが、2課については現在、稲垣が部長と兼務でMGRを担当しています。2課は複数のプロダクトを担当し、さらに業務支援も担うため、1課とは異なる難しさがあります。現状はリーダーPdMに一部を任せつつ伴走していますが、担う役割に対して人数が十分でない ため、新しいリーダーPdMの育成や採用が必要です。 また、今後人数を増やす場合には、スパン・オブ・コントロール(2ピザルール) の観点から、さらに組織を分割する必要が生じる可能性があります。 デザイナー・PMMとの連携がまだ弱い プロダクト部でプロダクトマネージャーが関わる場合、基本的にこのようなPdM-PMM体制、役割 としています。 両者の役割分担は明確になっていますが、今後はより高い解像度で顧客の課題に向き合い、製販一体 の動きを強化する必要があります。そのため、PdM自身がPMM領域の知識を学び、自ら情報をキャッチアップしていくことが求められます。 また、目指すべき姿をこのように定義していますが、デザイナーとの連携についても強化の余地が大きく、UXやデザインの知識に深く関与できるPdMが必要です。今後は、より多様な人材の登用 も視野に入れるべきと考えています。 統合型ベスト・オブ・ブリード型の実現に向けたPdM体制が不十分 現在、楽楽シリーズでは、これまでの 「ベスト・オブ・ブリード型」戦略 をアップデートし、「統合型ベスト・オブ・ブリード」 のプロダクトを目指しています。その実現に向けて、PMM側では 「マルチプロダクト戦略課」 を立ち上げ、戦略を前に進める取り組みを始めています。(詳細はこちら note.com へ) 一方で、PdM側ではまだ十分に呼応する体制が整っていません。今後、この戦略に対応したPdMを配置することで、より効果的に機能する可能性があると考えています。 現状では、連携の中心となるプロダクトが「楽楽精算」や「楽楽明細」であり、これらを担当するPdMがその役割を担っています。しかし、今後は 単独のドメインや個別プロダクトにとどまらず、より広い視野と先を見通す力を持つPdM が必要です。そのために、少しずつ準備を進めています。 プロダクトマネージャーの製品貢献実感および実際の製品成長に大きな余地がある 2024年度下期から、プロダクトマネージャーに対して 製品貢献実感 に関するアンケートを実施しています。※アンケート内容や詳細については「(2)製品貢献実感は、まだまだ伸びしろあり」を参照してください。 担当プロダクトは ARR規模が大きく、関わるステークホルダーも多いため、貢献実感を得にくい 側面があります。そのため、プロダクトマネージャーがしっかりと貢献実感を持てるような取り組みを進めつつ、実際の製品成長をさらに加速させること を目指していきます。 最後に 11月(大阪)、12月(東京)に開催される 「PM Conf 2025」 において、当社ラクスのPdMから2名が公募セッションに採択されました。ここでもご紹介しておきます。 大阪開催  植木遼太 (シニアPdM) 私とほぼ同期で入社し、一度退職後にラクスへ再入社していますシニアPdMとして多様な経験を積んできており、その知見を今回のセッションでお話しする予定です。 関連ブログ:ブーメラン転職ってどうなの?実体験から語る tech-blog.rakus.co.jp 東京開催  紀井 美里 (リーダーPdM) プロダクトマネジメント2課のリーダーPdMとして活躍中です新卒でラクスに入社して約10年、「楽楽精算」の開発・PdMを担当後、現在は別プロダクトを担当しています。さらにPdMの育成にも尽力しており、その取り組みを今回発表する予定です。 関連ブログ:AI時代のプロダクトマネージャー:組織と人材の変化から見る新しい価値創造 tech-blog.rakus.co.jp  / 個の限界が教えてくれたマネジメントへの道:葛藤を超えて形作る自分だけのキャリア tech-blog.rakus.co.jp 私の挑戦 私自身も昨年に続きプロポーザルを出しましたが、残念ながら今回は落選しました。まだ2回目の挑戦なので、来年もチャレンジするつもりです。ただし徐々にプロダクトマネジメントの現場から離れつつあるため、来年が最後のチャンスかもしれない と感じています。 今回応募したテーマは以下の通りで、今後どこかの機会でお話しできればと思っています。 組織戦略・キャリア・役割分担 意思決定とアライン 「2)次の進化にむけて」でも触れましたが、ラクスのプロダクト部では現在 プロダクトマネージャーを積極的に採用中 です。本noteを読んで当社プロダクト部に興味を持っていただいた デザイナーやプロダクトマネージャーの方 は、ぜひカジュアル面談からでもご応募ください。 ※プロダクトマネージャーのカジュアル面談は、基本、私(稲垣)が担当しています。 ●採用情報 プロダクトマネージャー career-recruit.rakus.co.jp デザイナー career-recruit.rakus.co.jp   └ デザインマネージャー career-recruit.rakus.co.jp /アシスタントマネージャー career-recruit.rakus.co.jp
こんにちは、 稲垣 です。 2025年4月から、プロダクトデザインの組織とプロダクトマネージャーの組織が、同じ「プロダクト部」という部門に統合されたのを受けて 「 プロダクト部はじめました 」を書きましたが、あれから半年経ちましたので、続編を書きます。 10月より副部長から部長となりました。 まずは改めて自己紹介です(キャリア変遷は コチラ へ) エンジニアを軸にこれまで20年以上製品ヅクリに携わってきました。PdMはラクスに入ってから名乗るようになり、デザイナーはファッションECサービスの時に一時的にマネージャー不在の時があり、その時に直接マネジメントをしていました。 (この時には新卒のデザイナー採用課題レビューやスカウトでデザイナーのマネージャーを採用してたりしました) 基本的なプロダクト部の紹介は「 プロダクト部はじめました 」に書きましたので、今回はスキップし、あれから6ヶ月に経って どう変化や進化をしているかについて書きます。 変化と進化 ■人が増えました ■楽楽シリーズのデザインガイドライン・システムが策定及び反映が進んでいます ■PdMとデザイナー合同でのお客様インタビュー等も順調に進められています 課題と今後取り組むべきこと ■UX改善は苦戦 ■製品貢献実感のまだまだ伸びしろあり ■デザイン・プロダクトマネジメント領域でのAI活用 最後に 変化と進化 ■人が増えました 2025年4月 25名 → 2025年10月 33名 デザイナー +6名 / PdM +2名 PdMはこれまでマネージャーは一人でしたが、新しいマネージャーが誕生し組織も2つに別れました 取り組むべきことは多いため、今後も増員はしていきます ■楽楽シリーズのデザインガイドライン・システムが策定及び反映が進んでいます 現在、楽楽シリーズの各製品においてのUIの刷新を徐々にしています( 詳細 ) 楽楽シリーズはご覧の通り、15年以上前に提供開始したプロダクトからつい、2025年7月から提供した「楽楽債権管理」まで規模含めて千差万別です。また、利用するお客様はバックオフィス部門の方が中心ものもあれば、一般の従業員の方も利用されるプロダクトもあり、これらに配慮しながらのUI刷新は非常に困難を極めます。 そのため、デザインガイドライン・システムについても既存のシステムやお客様に配慮しながらの策定で非常に難しいですが、デザイナーが中心にフロントエンドや各開発組織と連携を取り、概ね完成し、今は新しいプロダクトや既存のプロダクトのUI刷新を進めながら運用フェーズに入っています。 現在は上記のようなすみ分けをしています。 実際の改修にあたっては、一番改修規模の大きい楽楽精算を例にとると、経理の方向け、一般の申請者・承認者向け、管理者の方向けなど、利用するペルソナに応じたまとめた改修をすることでお客様に大きな不便がないような改修ステップを踏みながら進めています。 デザインガイドライン・システムによってデザインに関しての各ステークホルダーとの認識がそろいやすくなり、コミュニケーションコストが格段に減っている印象があります。また、新しいプロダクトやページ作成時のデザイン策定も効率的になっています。 デザインガイドライン・システムは完成はなく、しっかり定期的にブラッシュアップしていくことが大切だと思っています。 ●関連記事 ・ ラクスのプロダクトデザインチームとは? 〜「プロダクトデザイン3課」の役割と挑戦 ・ 【UIリニューアル】楽楽シリーズカラーパレット刷新の備忘録 ・ UIを変えたら、プロダクトもチームも、動き出した ■PdMとデザイナー合同でのお客様インタビュー等も順調に進められています 楽楽精算においてはUX改善PRJが立ち上がり、ここではほぼデザイナー主体でのお客様インタビューが進んでいます。 これまでこれもラクスのデザイナーは参画できていませんでしたが、ここは一気に2025年度から変わっています。 前回のブログでも出しましたが、中長期的には全てのプロダクトで以下のような状態をつくれるといいと思っています。 一番進んでいる「楽楽精算」でも以下のような取り組みはPdM主体で進んでいる状態なので、ここも徐々にできるようになるといいと思っています。    ・UX観点での広いディスカバリーをするためのインタビュー    ・ソリューションを検証のインタビュー(モックを作成して、しっかりあてていく) これはデザイナーのケイパビリティ的な課題もありますが、一番はリソース的な課題の原因もあるため、各プロダクトでしっかりリソース確保ができるようにする必要があると思っています。 課題と今後取り組むべきこと ここまで比較的ポジティブな振り返りをしてきましたが、ここからは進めてみたわかった課題や今後やっていくべきことについてお話します。 ■UX改善は苦戦 UIの刷新については進行していますが、UX改善については各プロダクトは時間が掛かっています。これには理由が2つあります。  1.UX課題のあるプロダクトは歴史あるプロダクトであり、この改善には多機能が故に難しさや    既存で長くご利用いただいてるお客様の体験が変わる可能性もあり慎重ならざるを得ない  2.AIを取り込んだUXとした方がよいケースが多く、ここの体験設計に時間がかかる   当社のように導入社数が多いと技術的な難易度も格段にあがる 現在、1と2を踏まえて各プロダクトでAIの搭載を見据えたロードマップを策定して、UX改善をしていく方針にしています。 AIエージェントを活用することで、現在のUXはそのままに新たな体験をツクっていくことができると考えています。 ●関連記事 ・AIエージェント開発課、始動の裏側 ・ AI開発の最前線!「RAKUS AI Meetup」開催レポート~3つの事例から見えたラクスの本気度~ ■製品貢献実感のまだまだ伸びしろあり 昨年度から「製品貢献度確認アンケート」というものをメンバーに取るようにしています。 ラクスでは事業が順調である一方で、どこまで各メンバーが事業や製品に対して貢献実感があるのかがわかりづらいため それを定点観測し、各メンバーの貢献実感を生み出したいと考えてやりだしています。 アンケート内容 はとても簡単な3つの問いです。 点数は公開しませんが、人が増えたこともあり、わずかに下がっていました。 定性情報のまとめは以下の通りです。自分の恣意的な情報が入らないようにChatGPTに定性情報の分析をしてもらった結果は以下です デザイナーとプロダクトマネージャーでは多少違うものの、こういった声が上がっていました。 今後は「製品への貢献実感をより感じること」を改善できるようにする必要があると感じています。 ■デザイン・プロダクトマネジメント領域でのAI活用 現在、ラクスでは全職種が業務において生成AIを積極的に活用しています。 プロダクト開発においてもエンジニアを中心に活用をしています。デザイナー、プロダクトマネージャーも活用していますが、まだまだ余地があると思っています。プロダクトによっては多少違いはでますが、以下ようなイメージや業務の置き換えを目標として進めていく予定です。 ここまで進めることができるとデザイナーはよりUXへ、プロダクトマネージャーはよりプロダクト戦略へ染み出すことができ、これまで以上にお客様のペインや課題理解に時間を充てることができる、開発組織全体がより『顧客志向』での開発を進められると思っています。 ●関連記事 ・ラクスの生成AI活用を加速させる、情シスAIチームの取り組み ・ ラクス、AI戦略を加速させる新ポジション「CAIO(最高AI責任者)」を新設 最後に プロダクト部は   「社内外の関係者と連携し、UX志向で製品価値を創出・提供し続けることでお客様の満足と利益を生み出す循環を担う」 を「責務」に以下の発足経緯と活動領域で業務をしています。 ! [image15]! [image16] そして、「UX志向」を元に、よりよいプロダクトの実現目指しています。 是非、本noteを読んで頂き当社ラクスのプロダクト部に興味を持っていただけたデザイナーやプロダクトマネージャーの方はカジュアル面談からでも構いませんで、ご応募頂ければと思っています。
2025年、AI技術は新たなフェーズに突入しました。特に、自律的にタスクを計画・実行する「AIエージェント」の登場は、ソフトウェア開発の世界に大きなインパクトを与えています。私たちラクスでも、この技術革新の波を捉え、自社サービスを進化させるべく、社内のR&D活動「技術推進プロジェクト」にて「垂直型AIエージェント」の調査・研究に取り組みました。 申し遅れました、普段は技術広報を担当しているkawa3です。今回は久々にR&D活動を通じてエンジニア業務を担当しました。楽しかったです! 本記事では、その成果発表の内容をもとに、AIエージェントの基本からSaaSへの応用可能性、そしてPoC(概念実証)を通じて見えてきたリアルな課題と解決策までを簡潔にご紹介します。 この記事を通して、垂直型AIエージェントの技術的な面白さをお伝えするとともに、ラクスがどのように技術のキャッチアップと社内へのナレッジ展開に挑戦しているか、その一端を感じていただければ幸いです。 なぜ今「垂直型AIエージェント」なのか? AIエージェントの基本:従来のAIとの違いと得意なこと 実装への道筋:自律レベルによる6つのパターン分類 SaaS業界の最前線:競合他社のAIエージェント活用事例 AIエージェントを構成する8つのコア技術モジュール ラクス製品への反映アイデア やってみて分かったこと:AIエージェント開発フレームワークを用いたPoCのリアルな手応え 本番導入を阻む「3つの壁」と、その乗り越え方 知識を組織の力に:「垂直型AIエージェント実装ナレッジベース」 まとめと今後の展望 なぜ今「垂直型AIエージェント」なのか? AIエージェントには、分野を問わず汎用的に使える「水平型」と、特定の業界や業務(ドメイン)に特化した「垂直型」が存在します。私たちが今回「垂直型」に焦点を当てた理由は、ラクスが提供する多様なSaaSサービスへのAI導入を考えた際に、より現実的で高い価値を提供できると考えたからです。 プロジェクトの目標は以下の3つに設定しました。 国内外のAIエージェント活用パターンやユースケースの調査 ラクスが提供するサービスへの適用候補機能の洗い出し PoCによる実現可能性の判断 AIエージェントの基本:従来のAIとの違いと得意なこと まず、「AIエージェント」そのものについて整理しましょう。 AIエージェント : 自律的に計画・意思決定を行いながら作業を実行するAIです。タスク遂行に必要な情報を自ら収集し、状況に応じて最適な行動を判断・実行します。開発支援AI「Devin」などがその代表例です。 従来の生成AI・AIアシスタント : ユーザーの指示に基づき、コンテンツ出力や支援を行うAIです。対話や補助的な作業が主な役割となります。 そして、垂直型AIエージェントは、その高い専門性を活かして、特に以下の3つの領域を得意としています。 専門知識が必要な、高度な分析や判断支援 ルールに基づいた定型業務の自動化と効率化 大量のデータに基づく予測と最適化 医療の画像診断支援から金融の不正利用検知、法務の契約書レビューまで、その応用範囲は多岐にわたります。 実装への道筋:自律レベルによる6つのパターン分類 一口にAIエージェントと言っても、その自律レベルは様々です。私たちは、人間がどの程度関与するかに応じて、自律レベルを弱い方から Assist(支援) 、 Copilot(副操縦士) 、 Autonomous(自律型) の3段階に分けました。 さらに、これを具体的な機能パターンとして6つに分類しました。これにより、AIエージェント導入の難易度やステップをより明確にイメージできます。 パターン分類 自律レベル ①リサーチ&要約アシスト アシスト ②ドラフティング・ジェネレーター アシスト〜コパイロット ③意思決定コパイロット コパイロット ④業務フロー・オートメーター コパイロット〜自律 ⑤自律オペレーター 自律 ⑥モニタリング&ガーディアン 自律(単機能) SaaS業界の最前線:競合他社のAIエージェント活用事例 SaaS業界では、まさに「AIエージェント元年」とも言える状況で、各社が次々と新機能をリリースしています。 本記事では割愛しますが、社内向けの成果発表では各社のAIエージェント機能の事例を紹介しました。 各社とも、具体的な業務プロセスにAIを組み込み、「完全自動運転」を目指す動きが加速していることがわかりました。 AIエージェントを構成する8つのコア技術モジュール 私たちは、AIエージェントを構成する技術要素を8つのコアモジュールとして整理しました。 成果発表では、これらのコアモジュールの役割や技術例について解説を行いました。 LLM Core(GPT/Claude/Gemini等) プロンプト・推論エンジン(タスク分解・計画・自己修正) メモリ管理(短期/長期) RAG(幻覚抑制・ドメイン知識注入) 外部ツール連携(API/MCP/関数呼出) マルチモーダル(音声/画像/動画) 安全性・ガバナンス(Moderation/Guardrails/ルールエンジン) モニタリング・分析(Langfuse/LangSmith/OTel等) ラクス製品への反映アイデア ここでは、ラクスが展開する「楽楽明細」「楽楽販売」「メールディーラー」などの9製品を対象にAIエージェント機能の候補を洗い出しました。 9製品×6種の自律レベル = 合計54機能の実装案を検討しました。 これら案のうち、「楽楽勤怠」でのシフト自動作成機能をPoCで実施することが決まりました。 やってみて分かったこと:AIエージェント開発フレームワークを用いたPoCのリアルな手応え 私たちは、AIエージェント開発フレームワークを用いて、簡単なシングルエージェントを実装し、シフト作成の自動化を試みるPoCを実施しました。 【PoCの概要】 目的 : 従業員のスキルや希望勤務時間、各時間帯に必要な人数といった要件から、最適なシフトを自動生成する。 流れ : シフト要件を登録 従業員の勤怠情報(スキル、希望シフト等)を登録 AIエージェントにシフト作成をリクエスト AIエージェントが推論やRAG、外部ツールなどを使用しシフト案を作成 生成されたシフト案と充足状況を確認 【PoCから得られた知見】 良かった点 : AIエージェント開発フレームワークを用いることで、簡単なワークフローであれば容易に実装可能であることが確認できました。 気になった点(課題) : 処理速度 : 入力データが増えるほどレスポンスが遅くなり、タイムアウトのリスクがありました。単純なシフト作成であれば、専門の最適化ツール(例:ORTools)の方が適している可能性も感じました。 マルチエージェント化の必要性 : シフトを「作成するエージェント」「評価するエージェント」「修正するエージェント」のように役割分担させるマルチエージェント構成の方が、より真価を発揮できると感じました。 セキュリティ・保守性 : 今回のPoCでは簡単な検証に留まりましたが、本番運用を見据えると重要な課題が浮き彫りとなりました。 本番導入を阻む「3つの壁」と、その乗り越え方 PoCを通じて、AIエージェントのワークフロー自動化へのポテンシャルを実感する一方で、本番導入にはいくつかの大きな壁があることも明らかになりました。 本番導入を困難にする三大要因: 性能 : 処理が遅い、精度が低い。 コスト : API利用料などが高額になり、赤字になる可能性がある。 セキュリティ : プロンプトインジェクションのような攻撃への対処や、不適切な出力内容の精査など、企業としての責任が問われる。 これらの課題は決して低くありませんが、乗り越えるための技術やノウハウも存在します。 性能改善 : モデルの最適化、RAG(Retrieval-Augmented Generation)の精度向上、マルチエージェントによる並列化など。 コスト圧縮 : 軽量モデルの活用、推論キャッシュ、ライセンスの最適化など。 セキュリティ強化 : ガードレール(不適切な入出力を防ぐ仕組み)、ヒューマン・イン・ザ・ループ(人間の確認・介入)、モデレーションAPIの活用など。 今後は、これらの技術要素の研究やナレッジを組織として蓄積していくことが、競争力の源泉になると考えています。 知識を組織の力に:「垂直型AIエージェント実装ナレッジベース」 今回のプロジェクトの最終成果物として、私たちは調査・研究で得た知見を「垂直型AIエージェント実装ナレッジベース」としてGitHub Wikiにまとめ、社内に公開しました。 これは、AIエージェントを本番実装するための、設計から運用、評価までの実務知識を集約したものです。 ナレッジベースの主な構成 1. 基本概念 : 垂直型AIエージェントの定義や価値、ユースケースを整理。 2. 開発フレームワーク : LangChainやGoogle ADKなど、主要なフレームワークを比較。 3. 観測・分析 : Langfuseなど、トレーシングやデバッグを支えるツール群を紹介。 4. フルマネージド統合AI開発プラットフォーム : Azure, AWS, GoogleのPaaSを比較。 5. 本番運用・実践ガイド : パフォーマンス、コスト、セキュリティの観点からタスクを体系化。 6. 評価 (Evaluation) : 品質保証のための指標や評価フローを整理。 7. LLMOps : プロンプト管理や継続的改善のサイクルについて紹介。 8. 実装基盤 : アーキテクチャ設計やUX、組織体制など実装を支える土台を整理。 8-1. アーキテクチャ設計 - エージェント構成パターン / フェイルオーバ / 設計チェックリスト 8-2. ナレッジ&データ運用 - ナレッジライフサイクル / データ契約 / ドリフト監視 8-3. CI/CD & プラットフォーム自動化 - 環境分離 / パイプライン / Secrets管理 8-4. 責任あるAIとコンプライアンス - 規制マッピング / バイアス評価 / 透明性 8-5. プロダクト体験とUX - 会話設計 / 失敗UX / KPI 8-6. テストと信頼性 - 負荷/カオス/DRテスト / 演習テンプレ 8-7. 外部連携パターン - ツール連携 / Function Calling設計 / 失敗緩和 8-8. 組織と運用モデル - 役割/RACI / オンボーディング / 定例運用 このようなナレッジをオープンに共有し、誰でもアクセスできる状態にすることで、各プロダクトへのAIエージェント導入を加速させ、組織全体の技術力を底上げすることが狙いです。 まとめと今後の展望 今回の技術推進プロジェクトを通じて、私たちは垂直型AIエージェントがSaaSビジネスに大きな変革をもたらすポテンシャルを秘めていることを再確認しました。定型業務の自動化から高度な意思決定支援まで、その応用範囲は無限大です。 一方で、PoCを通じて明らかになったように、性能、コスト、セキュリティといった本番導入への壁は決して低くありません。しかし、それらを乗り越えるための技術やアプローチも着実に進化しています。 私たちラクスは、今後もこのようなR&D活動を積極的に続け、最新技術を迅速にキャッチアップし、実践的なノウハウを蓄積していきます。そして、その成果を「楽楽シリーズ」をはじめとする自社サービスに反映させ、お客様に新たな価値を提供し続けることを目指します。 AIエージェントが当たり前になる未来は、もうすぐそこまで来ています。このエキサイティングな技術革新の旅に、これからもご期待ください。
こんにちは、開発推進部 SRE課のimamotoです。 SRE課ではSlackを使ってアラートを通知しているのですが、 今回はそのアラートを確認する運用を自動化してGitHub上で運用を完結させた話をしたいと思います。 これまでのアラート確認運用について 運用の流れ 解決したかった困りごと ①見落とし・対応漏れのリスク ②手順書作成コスト ③見るべきツールが多い ④横展開の必要性 改善方針:GitHub Actionsを活用したアラート確認運用の自動化 1. GitHub ActionsによるSlackメッセージの自動分類 Slack Alert Analyzerの機能 作成されたIssue例 2. 未知のアラートのRunbookを自動作成 Issueテンプレート Pull Request 3. 対応完了処理 Slack Alert Resolverの機能 他チームへの横展開:Reusable workflowとTemplate Repositoryを利用 Template Repositoryについて フォルダ構成 README 効果 おわりに これまでのアラート確認運用について まず、今回改善した運用について簡単に説明します。 運用の流れ 毎朝Slackのチャンネル(対象:7チャンネル)を目視して、アラートの見落としが無いか確認 アラート内容の分析・判断 必要に応じて対応の実施・手順書の作成 対応状況を記録(チェックボックス) 解決したかった困りごと ①見落とし・対応漏れのリスク Slackチャンネルを日次で目視確認し、「その日の運用を実施したか」のチェックボックスをチェックする運用にしていました この方法だと「どのアラートまで確認済みか」の管理まではできないため、アラートの見落としリスクがありました。 ②手順書作成コスト 新しいアラートが発生するたびに手順書を作成する際、作成のコストがありました。 また、「一時的に無視しても良いアラート」についてはあまり手順化されておらず、アラート対応の属人化につながっていました。 ③見るべきツールが多い 7つのSlackチャンネルを目視確認し、必要に応じて適切なGrafanaダッシュボードを確認し、各所に分散した手順書を参照するので、運用が面倒でした ④横展開の必要性 SRE課以外でスプレッドシートでアラート情報を管理しているという事例がありました。 何らかの仕組みで管理を楽にして横展開したいと考えていました。 改善方針:GitHub Actionsを活用したアラート確認運用の自動化 前述した課題を解決するため、GitHub Actionsを活用してSlackアラートの確認を自動化しました。 運用は以下のような手順になりました。 毎朝GitHub Actionsをスケジュール起動して、Slackメッセージの収集・分類・GitHub Issueの作成を自動化 分類されたアラートに対応したRunbookリンクがGitHub Issue上に記載されるので、それを元に運用を実施 Runbookがない場合は、GitHubのIssueテンプレートからIssueを起票するとRunbookのマークダウンファイルを自動生成 GitHub Issueに確認終了のラベルを付与すると、Slackに対応済みである旨を記録 1. GitHub ActionsによるSlackメッセージの自動分類 Slackチャンネルの目視によるアラート分類の判断やRunbookの存在確認が非常に面倒だったため、 Slack Alert Analyzer というツールを作成してGitHub Actionsから日次で実行する形にしました。 Slack Alert Analyzerの機能 Slackアラートメッセージの収集・分類・フィルタリング GitHub Issueを起票して、分類したアラートの概要や対応するRunbook、メッセージ本文等をIssueに記載 作成されたIssue例 Slack Alert Analyzerを実行すると、以下のようなIssueが起票されます。 Issue本文:その日のアラート内容のサマリを記載 過去1週間分のアラートを収集(対応済みのアラートを除く) Issueコメント:グルーピングされたアラート情報を記載 正規表現ベースでグルーピング アラート件数、発生元チャンネル、Runbookリンク等 Slackメッセージ本文もIssueコメント上から確認可能 対応が完了したアラートのチェックボックスにチェックを入れる✅️ 2. 未知のアラートのRunbookを自動作成 Slack Alert Analyzerの設定ファイルで「既知のアラート」として登録されていないメッセージの場合、GitHub Issue上にRunbookが表示されません。 既知のアラート登録用のIssueテンプレートを使用してIssueを作成すると、以下の2つの変更を行うPull Requestが作成されるようになっています。 Slack Alert Analyzerの設定ファイルに既知のアラートとして追加 Runbookとなるマークダウンファイルを作成 Issueテンプレート こんな感じのIssueテンプレートを使っています。 Issueに add-known-alert ラベルが付与されます。 Pull Request add-known-alert ラベルがついたIssue作成をトリガーにPull Requestが作成されます。 knowledge/known-alerts.yaml に既知のアラートとして情報を追記 runbooks フォルダに新しいRunbookファイルが作成される 3. 対応完了処理 一通り確認が完了したら resolved-alerts ラベルをGitHub Issueに付与すると Slack Alert Resolver というツールがGitHub Actionsで自動実行されます。 Slack Alert Resolverの機能 Issueコメント上でチェックが付いているSlackメッセージに対して「✅️」リアクションを付与 ✅️リアクションを付与すると、次回以降に確認対象として拾われなくなる Issueを自動クローズ 他チームへの横展開:Reusable workflowとTemplate Repositoryを利用 元々SREチームでのみ使っていたツールだったのですが、 他チームにツールを展開するにあたって以下の対応を実施しました。 これによって、Template Repositoryから作成したリポジトリから迅速にセットアップして利用できるようになりました。 また、Reusable workflowにすることで、バグフィックス等の迅速な横展開も可能となりました。 Reusable workflow : Slack Alert Analyzer, Slack Alert Resolverの実行用ワークフローを再利用可能な共通ワークフローとして展開 Template Repository : Reusable workflowを呼び出すワークフローや、必要な設定ファイル・フォルダが準備された雛形のリポジトリを作成 必要なセットアップ方法も、READMEに記載 Template Repositoryについて フォルダ構成 必要なワークフローやIssueテンプレート、設定ファイルが含まれています。 README こんな感じでREADMEにセットアップ方法を記載しています。 効果 定常的な運用の動線を減らして運用を楽にすると、運用者の負荷を下げるだけでなく、品質も向上しました。 特にRunbook作成へのモチベーションが上がりました。 他チームへの横展開も迅速に開始することができました。 おわりに 今回のツールはチームの為に作ったという建付けなのですが、私自身も本当に運用作業が苦手でして、、 実は自分が一番このツールの存在を喜んでいるかもしれません。 また、今回のツール作成はGitHub CopilotのAgentモードを使って行いましたが、要件の壁打ちも含めて初版は2時間以内に作成することができました。 ちょっとした自作ツールの作成ハードルが生成AIの活用によって本当に低くなっていると感じるので、 「運用つれぇ…」 と思ったら何か作ってみるのもオススメです。 (適材適所で、生成物の品質やメンテナンスについては責任を持つ前提で) それでは、ブログをご覧いただきありがとうございました!
はじめに こんにちは、 @rs_tukki です。 ラクスの開発部では、これまで社内で利用していなかった技術要素を自社の開発に適合するか検証し、ビジネス要求に対して迅速に応えられるようにそなえる 「技術推進プロジェクト」 というプロジェクトがあります。 今回、このプロジェクトで「Passkey」に関する検証を行なったので、その結果を報告させていただきます。 はじめに Passkeyとは何か 基本概念 対応環境 ブラウザ要件 OS要件 技術的な仕組み FIDO2規格の構成 1. Web Authentication (WebAuthn) 2. Client to Authenticator Protocol (CTAP) 登録フロー 認証フロー フィッシング攻撃への耐性 リプレイ攻撃への耐性 他の認証方式との比較 多要素認証(MFA)との違い FIDO1との違い 1. UAF(Universal Authentication Framework) 2. U2F(Universal 2nd Factor) Spring Bootでの実装 システム構成 実装のポイント 1. データベース設計 2. ドメイン設定の制約 3. Passkeyの削除 BtoB SaaSへの導入に向けて Passkey導入のメリット/デメリット メリット デメリット まとめ 参考リンク Passkeyとは何か 基本概念 まず、具体的な検証内容の説明に入る前に、Passkeyについて軽く説明します。 PasskeyとはFIDOアライアンスとW3Cが共同で策定した新しい認証技術です。 従来のIDとパスワードを入力する認証方式とは異なり、ユーザはデバイスのロック解除に使う方法(生体認証やPINコードなど)を使ってWebアプリにログインすることができます。 Passkeyを利用することによるメリットは2点。 ユーザ体験を損なわず2要素認証を実現できる 点と、 複数のデバイス間で認証情報を共有できる 点です。 これにより、従来の認証方式と比較して、一定のセキュリティを担保しつつ、ユーザ体験を向上させることが可能になります。 対応環境 Passkeyを利用するには、ブラウザとOSでそれぞれ異なるバージョン要件が必要になります。 バージョンは以下の通りですが、近年の環境であればそれほど意識せずともPasskeyを利用できるかと思います。 ブラウザ要件 Chrome : バージョン109以上(Windows/Mac/Android/iOS/iPadOS) Safari : バージョン16以上(Mac/iOS/iPadOS) Edge : バージョン109以上(Chromiumベース) Firefox : バージョン122以上 OS要件 Windows : 10以上 macOS : 13(Ventura)以上 iOS/iPadOS : 16以上 Android : 9以上(Google Play開発者サービス搭載端末) 技術的な仕組み FIDO2規格の構成 実は、「Passkey」という言葉を定義する場合、 広義のPasskey と 狭義のPasskey という2種類の定義方法があります。 まずは広義のPasskeyについてですが、これは以下2つの技術仕様をまとめた、 FIDO2 を基盤として構成した認証技術です。 1. Web Authentication (WebAuthn) W3Cによって標準化された、ブラウザ上で公開鍵認証方式を利用するためのJavaScript APIです。開発者が直接操作するのは主にこのAPIになります。 2. Client to Authenticator Protocol (CTAP) FIDOアライアンスによって定められた、ブラウザと認証器(デバイスの生体認証センサーやセキュリティキーなど)との間で通信を行うための規格です。WebAuthnが内部で利用しており、開発者が直接意識することは少ないです。 この「広義のPasskey」では、認証情報を認証器(PCやスマホ端末)そのものが保持していました。 そのため、たとえばPCに対してPasskeyを登録していても、同じユーザがスマホ端末からログインしたい場合、また別の端末にPasskeyを登録し直すところから始める必要があります。 一方で「狭義のPasskey」では、上記のFIDO2仕様に加え、これらの情報を クラウド上のパスワードマネージャーに保管する ことを前提としています。 これにより、複数の端末を利用していても、パスワードマネージャーを共有することで、再登録の手間なく認証を行うことができます。 本記事では、単にPasskeyと呼ぶ場合、この「狭義のPasskey」を指して説明しています。 また、狭義のPasskeyはその特性から、 MDC(Multi-Device FIDO Credential) とも呼ばれることもあります。 登録フロー Passkeyによる認証は、「登録」と「認証」の2つのフローから成り立ちます。 登録のフローは下記の通りです。 チャレンジの生成 : サーバーが一意のチャレンジ(乱数)を生成 ユーザー検証 : デバイスのスクリーンロック解除(生体認証、PIN等)でユーザーを認証 鍵ペアの生成 : デバイスがドメインに紐づいた秘密鍵と公開鍵のペアを生成 秘密鍵の保存 : パスワードマネージャー(Google Password Manager、iCloudキーチェーンなど)に暗号化して保存 公開鍵の送信 : 公開鍵をサーバーに送信し、データベースに保存 ここで重要なのは、ブラウザとサーバの間でやりとりしている情報が チャレンジ(乱数) 秘密鍵で署名したチャレンジ 公開鍵 認証器に関するメタデータ など、 機密性の低い情報に限られる ことです。 従来のパスワード認証におけるIDやパスワード、またユーザ認証に必要な生体情報やPINコードなど、機密性の高い情報は一切やり取りされていません。 これによって、Passkeyではセキュリティを担保しているというわけです。 認証フロー Passkeyを登録した後は、以下のフローでユーザの認証を行います。 チャレンジの発行 : サーバーが一意のチャレンジを生成 ユーザー検証 : デバイスのロック解除でユーザーを認証 署名の生成 : 秘密鍵でチャレンジに署名 署名の検証 : サーバーが公開鍵で署名を検証 認証の場合も、登録時のフローと同様、秘密鍵や生の認証情報など機密性の高い情報はやり取りされていません。 また、加えて以下の仕様によってセキュリティを担保しています。 フィッシング攻撃への耐性 認証器が作成した秘密鍵は、その鍵を作成したサイトのドメイン(=RPID)と紐づいています。 これにより、偽サイトでPasskeyを利用させようとしてもRPIDが異なるため署名することができません。 リプレイ攻撃への耐性 一度サーバが生成したチャレンジは、次回以降の認証に使用することができません。 そのため、認証に成功したリクエストを再現するリプレイ攻撃へも耐性を持っています。 他の認証方式との比較 ここまでPasskeyの仕様について説明してきました。 続いては、他の認証方式とPasskeyとの違いを説明していきます。 多要素認証(MFA)との違い 多要素認証とは、認証に使われる以下の3要素のうち、2種類以上の要素を利用して認証することです。 知識情報 :パスワードやPINコードなど 所有情報 :PCやスマートフォンなどの端末そのもの、もしくはクライアント証明書など 生体情報 :指紋認証や顔認証、虹彩認証など Passkeyによる認証は、認証器を利用して照合を行うため、認証器という所有情報と、ロック解除に使用する生体/知識情報を 同時に利用して認証 しています。 二要素認証というと、よくあるパターンはワンタイムパスワードや「ログインしようとしていますか?」といったプッシュ通知を利用する二段階認証ですが、Passkeyでは認証情報を2回入力する手間を省きつつ、二要素認証によってセキュリティを担保できているわけです。 また、先述の通り、認証情報そのものを送信せず、RPIDが異なると認証できないことから中間者攻撃やフィッシング攻撃に耐性がある他、 攻撃者が大量に認証を試行することでユーザのデバイスに通知が大量に送られる、いわゆるMFA疲労攻撃に対しても耐性を持っています。 FIDO1との違い 先ほど、広義のPasskeyはFIDO2仕様を基盤にしていると説明しました。 一方で、認証方式にはFIDO1という仕様もあります。これには UAF と U2F の2つの規格があります。 1. UAF(Universal Authentication Framework) 専用のセキュリティキーで生体認証を行うパスワードレス認証です。 公開鍵と秘密鍵を利用して署名を検証する点はPasskeyと同様ですが、PCそのものでは認証ができず、外部機器が必要となります。 2. U2F(Universal 2nd Factor) 上記のUAFに2段階認証の仕組みを加えたものです。 まず最初に通常のパスワードで認証を行ったあと、セキュリティキーを用いた二要素認証を行います。 記載の通り、FIDO1では専用の外部機器が必要なことに対し、PasskeyではOSとブラウザのバージョン要件を満たしていれば PCやスマホそのものを認証器として利用できるのが大きなメリットです。 Spring Bootでの実装 さて、ここからはWebアプリに実際にPasskeyによる認証を組み込む実装を見ていきます。 システム構成 今回の検証では、以下の技術スタックを使用しました。 - AlmaLinux8 - Apache 2.4.37 - Spring Boot 3.4.5 - spring-boot-starter-security - spring-security-web - webauthn4j-core:0.29.2.RELEASE - PostgreSQL 16.8 - SimpleWebAuthn - 証明書を備えたHTTPS環境 バックエンドの実装に関しては、各言語にPasskey認証を実現するためのフレームワークがあるかと思いますが、 今回は一番手軽な、Javaの spring-security-web + webauthn4j-core の組み合わせを使用します。 フロントエンドでは、 SimpleWebAuthn を利用して、バックエンドと認証器の繋ぎ込みを行います。 実装のポイント 今回の構成では、チャレンジの作成やCredentialの検証など基本的なフローはフレームワークが担ってくれます。 そのため細かい実装内容の解説は他の記事に譲りますが、ここでは特に注意すべきポイントをいくつか解説します。 1. データベース設計 先述のフロー図で、公開鍵等のサーバが保持する情報はDBに保存すると記載しました。 Credentialを作成しDBに保存する処理は自前でinterfaceを実装する必要があるため、以下のようなテーブルを作成しておきます。 Table " public.m_passkey_credential " Column | Type | Collation | Nullable | Default -------------------------------+---------+-----------+----------+---------------------------------- id | integer | | not null | generated by default as identity credential_id | bytea | | not null | user_id | text | | not null | pubkey | bytea | | | attested_credential | bytea | | | attestation_object | bytea | | | Indexes: " m_passkey_credential_pkey " PRIMARY KEY, btree (id) " m_passkey_credential_credential_id_key " UNIQUE CONSTRAINT, btree (credential_id) それぞれのカラムに格納する内容は以下の通りです。 id :DB管理用の主キー credential_id :各認証器が生成する一意のID user_id :Credentialが紐づくユーザのID pubkey :認証器が生成した公開鍵 attested_credential :メタデータを含む、認証器の登録時にクライアントが生成したデータ attestation_object :認証器の製造元情報、証明書チェーンなどを含む、認証器の信頼性を証明するデータ 2. ドメイン設定の制約 Spring Securityを用いたWebアプリでは、以下のようなSecurityFilterChainを実装することでPasskeyの仕組みを有効化することができます。 @Bean public SecurityFilterChain securityFilterChain( final HttpSecurity http) throws Exception { http.authorizeHttpRequests(authorizeHttpRequests -> { // リクエスト許可設定(ログイン用URLとwebauthnフレームワークの関連URLは認証を不要とする) authorizeHttpRequests .requestMatchers( "/login/**" , "/webauthn/**" ).permitAll() .anyRequest().authenticated(); }) // ログインフォームの設定(デフォルト) .formLogin(formLogin -> {}) // Passkeyに関する設定 .webAuthn(webauthn -> { webauthn .rpName( "Passkey" ) // Passkeyのサービス名。任意の名称でOK .rpId( "localhost" ) // サービスのドメイン名 .allowedOrigins(Set.of( // Passkeyを利用可能なURL(完全一致) "https://localhost:8443" )); }); return http.build(); } ここで重要な設定が、 rpId です。こちらは先ほど説明した通り、Passkeyを紐づけるドメイン情報です。 指定したドメイン(もしくはそのサブドメイン)以外から認証をしようとした場合、不正な操作としてエラーになります。 そして、このRpIdの指定ですが、以下のような制約があります。 単一ドメインのみ : 1つのドメインしか指定できない IPアドレス不可 : IPアドレスでは動作しない HTTPS必須 : localhost以外では正式な証明書が必要 そのため、ローカル環境以外では、 正式な証明書が発行され、DNSで関連づけられた正式なドメインでしか検証できない ということになります。 検証用にサーバを構築している方は注意が必要です。 3. Passkeyの削除 Passkeyはパスワードマネージャーとサーバ上のDBの両方でデータを管理していますが、 このうちパスワードマネージャ上に保存されたデータは、 webauthnのAPIで削除することができません。 ブラウザ上の操作で削除可能ものはサーバ側のデータのみであるため、Passkeyを完全に削除したい場合、パスワードマネージャーから手動で操作を行う必要があります。 二段階に分けて削除を行う必要があるため登録時より手間がかかります。ユーザには予め二段階の削除が必要な旨を案内しておくのが良いでしょう。 BtoB SaaSへの導入に向けて ここまでPasskeyという技術について説明しましたが、ラクスが提供しているようなBtoB SaaSの場合、Passkeyによる認証の需要はまだ少ないです。 Amazon等BtoCのサービスでは徐々に普及しつつあるものの、BtoBでは採用されているサービスはほとんどないのが現状です。 理由としては、ユーザ側がシングルサインオンやSSLクライアント認証などの認証方式を利用しているためそれらで十分というケースが挙げられます。 また、前提として一人当たり1端末(認証器)の利用を前提としているPasskeyは、例えば複数社員で一つの端末を共有している場合などでは相性が悪かったりもします。 しかし、今後ユーザーニーズが変わり、生体認証を含めた二要素認証への理解と需要が高まってきた時には、Passkeyを導入することも検討できるのではないかと思います。 Passkey導入のメリット/デメリット 最後に、Passkeyのメリットとデメリットをそれぞれまとめておきます。 メリット ユーザビリティの向上 パスワード記憶が不要 (指紋認証や顔認証であれば)ワンタッチでログイン可能 デバイス間での同期が可能 セキュリティの強化 フィッシング攻撃への耐性 総当たり攻撃への耐性 認証情報の窃取リスクの排除 運用コストの削減 パスワードリセットの対応がほぼ不要になる (適切に実装されていれば)セキュリティインシデントの軽減に繋がる デメリット 技術的ハードル フレームワークである程度カバーできるとはいえ、セキュリティリスクも考慮すると実装の難易度は高い CredentialIDの重複による乗っ取りの可能性 originやRPIDの適切な設定が欠けていることによるフィッシングの可能性 ユーザ検証の不備によるなりすましの可能性 etc... ユーザー教育 そもそも「Passkey」という認証方法に馴染みがないユーザもいる マニュアル等で適切なサポートを行わないと、かえってユーザ体験を損ねてしまう可能性がある 互換性の課題 非対応環境の存在も考慮しなければいけない 特にスマホ端末で、MDM(Mobile Device Management)を利用している場合は互換性がない可能性も まとめ ここまで、Passkeyを使った認証方式のメリットと実装方法、またBtoB SaaSでのPasskeyの需要について説明してきました。 Passkeyによる認証は、従来のID/パスワードによるログインと同等かそれ以上のユーザ体験の向上に加え、セキュリティ面でも向上が見込める新時代の認証方式です。 現状のBtoB SaaSにおいてはまだそこまで需要の高い認証方式ではありませんが、 今後、二要素認証に対する理解が深まり、需要が高まってきた時に備え、仕組みや実装について理解しておくとよいでしょう。 参考リンク FIDO Alliance W3C WebAuthn Specification webauthn4j Documentation Spring Security WebAuthn SimpleWebAuthn パスワードはおしまい! 認証はパスキーでやろう Spring Security 6.4.0-RC1 でパスキー認証を実装してみる Passkey認証の実装ミスに起因する脆弱性・セキュリティリスク
ラクスでメールディーラーのUIUXデザインを担当しているたけしまです。 AIを活用した製品づくりに向け、上流工程から参画し、日々業務に取り組んでいます。最近では、社内でも業務効率化やナレッジの蓄積、雑務の処理などにAIを活用するようになってきました。 顧客ヒアリングの情報を収集・分析する際にもAIを活用していますが、顧客の声が蓄積されるにつれ、顧客から見たAIがどんな存在で、何を期待しているのかが少しずつ見えてきました。今回は、その気づきをデザイナー視点のAIの価値と併せてご紹介したいと思います。 顧客視点 業務変革のパートナー 5つの価値的変化 6つの役割イメージ デザイナー視点 新たなUX設計の可能性 1. 製品のUX向上 2. 広く深い顧客理解 3. UI設計と業務効率化 顧客視点とデザイナー視点の比較 まとめ 最後に 顧客視点 業務変革のパートナー 顧客企業にとってAIは、単なる便利な機能ではなく「自社や仕事をどう変えてくれるか」という価値や役割として認識されています。彼らにとってのAIは、仮想的な同僚や参謀のような存在です。 これまで手間がかかっていた作業を自動化し、属人化していた業務を解消することで、企業の能力が高まり、エンドユーザーからの評価や信頼の向上が期待されます。さらに、活用次第では市場でのポジション拡大へとつながる可能性もあります。 5つの価値的変化 業務負荷の軽減  ➡ 手を動かす時間が減り、対応漏れも防げる 判断のサポート  ➡ 優先順位や次の一手が明確になる スピードと確実性 ➡ 即レスできて、内容の抜け漏れも防げる 質の安定     ➡ 誰が対応しても一定以上のクオリティが保たれる 学習と成長の支援 ➡ 過去のやり取りやナレッジから学べる 6つの役割イメージ アシスタント  : 面倒な作業を代行 ナビゲーター  : 対応の優先度や進め方を案内 品質管理者   : 誤字・不適切表現をチェック アナリスト   : 顧客傾向を分析し提案材料を提供 記録係/図書係 : 過去データを即座に検索 コーチ     : 成功事例や改善案を提示 デザイナー視点 新たなUX設計の可能性 UI/UXデザイナーにとってAIは、業務効率化とデータ活用の両面で価値を発揮し、利用者の体験全体を "気づかないレベル" で滑らかにすることが実現できる可能性を秘めた存在です。 1. 製品のUX向上 メールディーラーを例に挙げると、エンドユーザからのお問い合わせに対し、返信文案を生成できるAIが実装されています。文章の内容をチェックしたあと、本文に挿入するだけで対応の大部分が完了するという操作フローを実現。スタッフのスキルに関わらず、一定のレベルでカスタマーサポートを実現できます。 また、メールディーラーに関してのお問い合わせに対しても、お問い合わせフォームにAIを組み込むことにより、先回り型のサジェストで顧客自身が解決しやすい環境を構築するなど、業務効率化やコミュニケーションコストの軽減により、顧客体験の改善に繋げています。 2. 広く深い顧客理解 AIを活用することで、顧客との接点や行動データを集めて整理・分析が簡単になり、定性的な情報を定量化しやすい仕組みが作れます。これにより、より精度の高いニーズの抽出やペルソナ設計、ユーザージャーニー最適化が可能になります。 3. UI設計と業務効率化 UI設計や運用に必要なルール・ガイドライン、スタイル情報などをAIに学習させることで、定型的な対応や運用の一部を任せられるようになります。
 その結果、担当者はより創造的な業務に集中でき、全体の運用もスムーズに進められます。 顧客視点とデザイナー視点の比較 顧客視点とデザイナー視点の比較 まとめ AIが当たり前になった今、顧客は業務効率化や信頼性向上といった成果を重視し、デザイナーはユーザー体験の滑らかさを重視しています。 それぞれの期待や価値を理解したうえでUI/UX設計を進めることが、効果的なAI活用につながるポイントになりそうです。 今回は、顧客理解を深める中で得られた気づきを共有しました。 顧客が価値を感じられる開発を行うことで、より魅力的な製品を生み出すことができ、営業としても自信を持って提案しやすくなるはずです。 最後に ラクスでは、現在新しいデザイナーやエンジニアを積極的に募集しています!少しでも興味を持っていただけた方は、ぜひお気軽にご連絡ください。まずはカジュアルにお話ししましょう。 みなさまからのご連絡をお待ちしています。 career-recruit.rakus.co.jp career-recruit.rakus.co.jp
はじめに こんにちは!楽楽勤怠開発チームのoo_yoshiです。 勤怠管理システムは「打刻して残業時間や休暇を計算すれば終わり」と思われがちです。しかし、実際にシステムを開発・運用してみると、その裏には複雑なルールと例外が山ほど存在します。 勤務体系は企業ごとに違い、法律や就業規則も定期的に改正されます。有休の付与や消化ルール、代休や振休の扱い、残業の丸め処理など、ひとつひとつのルールが微妙に違い、組み合わせると膨大なパターンになります。 私たちのチームでは、そうした複雑さに対応するために9年前にDDD(ドメイン駆動設計)を採用し、勤怠システムをリニューアルしました。本記事では、その9年間で感じたこと、分かったことを振り返りたいと思います。 はじめに 旧勤怠管理システムで直面した課題 リニューアル時にDDDを導入して変わったこと 属人化の解消 9年経って実感したDDDの価値 まとめ 旧勤怠管理システムで直面した課題 リニューアル前の勤怠システムは、自作フレームワークをベースに作られていました。 そのため「どこにどんな処理があるのか」が一目で分からず、長く在籍しているメンバーが「知っているからなんとかなる」という属人化した仕組みになっていました。結果として「この処理の正解は◯◯さんしか知らない」という状況も珍しくありませんでした。 新しく入ったメンバーにとってもハードルは高く、普段は残業や休暇を申請していても、システムがどう計算しているかを理解できない。用語として「休暇残数」「振休」「代休」を知っていても、コードを読んでもピンと来ない。結果として教育コストは大きく膨らみました。 私自身も新卒2年目の頃から旧勤怠システムに関わらせてもらい、数えきれないほどの失敗を経験しました。。。振り返れば大きな糧になりましたが、正直に言えば「他の人にはあまりおすすめできない環境」だったと今では思います。 リニューアル時にDDDを導入して変わったこと 旧勤怠管理システムで一番困っていたのは「何をどこで処理しているのかが不明確」という点でした。 そこで新システムではDDDを採用。まず「勤怠ドメインを整理してモデルに落とし込む」ことから始めました。 要するに「現実の勤怠の仕組みを、チーム全員が同じ言葉で説明できるようにする」というアプローチです。 モデルの分け方の例 労働パターン 勤務区分(固定、フレックス、管理監督者など)や勤務カレンダーなど勤怠計算に必要な設定を定義します。 打刻 出勤・退勤・休憩といった「事実」だけを表す。ここには「遅刻」や「残業」といった解釈は入れず、純粋に「いつ押したか」だけを残す。 勤務実績 打刻で登録された値や勤怠計算で計上された、その日の「働いた結果」を表現。ユーザーに見せるのはここからだけにした。 休暇履歴 有休の付与・取得・消滅をすべて履歴として積み上げる。残日数は履歴をたどれば自動的に導けるようにして、「この値は正しいのか?」と悩む必要をなくした。 ドメインを整理してモデルに落とし込むことで、「どの処理がどこにあるのか」が明確になり、コードを追いやすくなりました。 ※DDDを導入する際に整理した代表的なモデルを、簡単ではありますが役割とドメインルールとあわせてまとめると以下のようになります。 属人化の解消 もう一つ大きな効果は、ユビキタス言語によるチームの共通理解です。 「労働パターン」「打刻」「勤務実績」「休暇履歴」といった言葉をチーム全体で使うようにしました。以前は「ロジック名」や「○○画面の処理」といった曖昧な表現をしていましたが、今では具体的なモデル名を指して会話できるようになり、仕様確認やレビューがスムーズになりました。 その結果、属人化は大きく解消されました。知識が特定のメンバーに偏るのではなく、モデルに閉じ込めたドメイン知識をチーム全体で共有できるようになったのです。 9年経って実感したDDDの価値 9年間システムを運用してきて実感したのは、DDDが「複雑な業務知識をチーム全体で維持し続ける仕組み」としてとてもよく機能しているということです。 新メンバーの立ち上がりが速くなった 「休暇履歴」「勤務実績」といった用語をベースに理解できるようになった 制度変更にも柔軟に対応できるようになった(該当モデルにルールを追加・修正するだけで済む) 旧システムのように「勤怠ドメインの理解に数カ月」かかることはなくなりました。 まとめ 勤怠管理システムは、一見シンプルに見えて実は非常に複雑です。その複雑さは、エンジニアがドメイン知識を理解するだけでも大きな負担になります。 9年前にDDDを導入してから、私たちは「休暇履歴」や「勤務実績」といったドメインを整理し、モデルに落とし込むことで属人化を解消し、新規メンバーでも理解しやすく、制度変更にも柔軟に対応できるシステムへと進化させることができました。 振り返ってみると、DDDって単なる設計手法じゃなくて、「複雑な勤怠ドメインをチームみんなで理解し続けるための心強い道具」だったなと思います。 そして9年経った今、「あのときDDDを選んでよかった」と胸を張って言えるのは本当に嬉しいことです。
目次: はじめに 前提知識 当時の課題 実施した改善策 結果 その他 まとめ はじめに こんにちは。今年に入って2ヶ月に1回以上K-POPなどのライブに行っている、楽楽債権管理開発チームの冨澤です。 楽楽債権管理は新サービスとして2025年7月1日から販売を開始した、ラクスの中では比較的新しいサービスであり、高速に開発することが求められます。 本記事ではそんな高速開発を支える取り組みとして、CIのテスト実行時間を短縮した話をご紹介したいと思います。 前提知識 本記事での取り組みでは、以下の内容を前提としています。 対応時期 2025年3月 技術スタック 言語 Java 21 FW Spring Boot 3 テストツール Spock ビルドツール Gradle(Groovy) CI GitHub Actions アーキテクチャ オニオンアーキテクチャ 1つのGradleプロジェクトで管理しており、 /app 配下に処理に必要なディレクトリが存在し、単体テストコードも実装と同じパッケージ構成で用意しています。 また層を跨ぐ依存関係にはテストダブルを利用することで、各層の単体テストを独立して実行できるようにしています。 以下はパッケージ構成の例です(物理パスはGradle標準のsrc/main/java、テストはsrc/test/groovyなど各プロジェクト標準に準拠) src/main/.../app ├── controller ├── domain ├── infrastructure ├── persistence └── usecase src/test/.../app ├── controller ├── domain ├── infrastructure ├── persistence └── usecase 改善前のCIワークフロー設定 lintジョブ Spotlessを使ったチェック unit-testジョブ DBコンテナを起動 DBマイグレーション 単体テスト実行 カバレッジの集計 当時の課題 プロダクトフェーズから考えると、CIの時間はもちろん早い方がいいです。 ですが、当時は以下のような状況でした。 計測時期 2025年3月 テストの平均実行時間(成功のみ) 20分50秒 成功したワークフローのみを対象としたのは、失敗したワークフロー(ビルド or テスト失敗や、 cancel-in-progress による早期打ち切りなど)を含めると平均実行時間が実態より短く算出され、改善効果を正確に測定できないためです。 実行時間が長い主因として、まず直列実行を疑いました。 キャッシュの未使用やマシンスペックの問題も考えられましたが、小さく早く試して効果を検証するという観点から、設定変更で実現できる並列実行が最も着手しやすい改善策だと判断しました。 そこで今回は「テストジョブの並列実行」を第一の改善策として採用し、その効果を検証することにしました。 実施した改善策 lintジョブ Spotlessを使ったチェック 4並列のテスト実行ジョブ A B C D カバレッジジョブ 各ジョブで実施した内容を集計 GradleとGitHub Actionsの組み合わせ テストジョブの並列実行を実現するために、GradleとGitHub Actionsの2つを組み合わせました。 ①:Gradleでカスタムタスクの作成 Gradleでの並列化としてよく maxParallelForks が紹介されますが、これは単一のタスク内でJVMプロセスの並列度を上げるための設定です。 一方、今回の取り組みではCI全体の実行時間を効率よく短縮することを目的とし、GitHub Actionsのジョブそのものを分割して並列実行できるようにする必要がありました。 そこで、workflowのmatrix戦略にそのまま割り当てられる実行単位としてカスタムタスクをGradle 側に定義しました。 この方法により、特定のテストグループをジョブ単位で独立して制御できるようになりました。加えて、カバレッジの集約も簡素化され、将来的な差分テストなどへの拡張性も確保できました。 以下にその定義例を示します。 // 共通のテスト設定 tasks.withType(Test).configureEach { useJUnitPlatform() } tasks. register ( 'A' , Test) { include '**/persistence/**/*.*' , '**/infrastructure/**/*.*' , '**/domain/**/*.*' } tasks. register ( 'B' , Test) { include '**/usecase/a**/**/*.*' , '**/usecase/b**/**/*.*' , '**/usecase/c**/**/*.*' , '**/usecase/d**/**/*.*' , '**/usecase/e**/**/*.*' , '**/usecase/f**/**/*.*' , '**/usecase/g**/**/*.*' , '**/usecase/h**/**/*.*' , '**/usecase/i**/**/*.*' , '**/usecase/j**/**/*.*' , '**/usecase/k**/**/*.*' , '**/usecase/l**/**/*.*' } tasks. register ( 'C' , Test) { include '**/usecase/m**/**/*.*' , '**/usecase/n**/**/*.*' , '**/usecase/o**/**/*.*' , '**/usecase/p**/**/*.*' , '**/usecase/q**/**/*.*' , '**/usecase/r**/**/*.*' , '**/usecase/s**/**/*.*' , '**/usecase/t**/**/*.*' , '**/usecase/u**/**/*.*' , '**/usecase/v**/**/*.*' , '**/usecase/w**/**/*.*' , '**/usecase/x**/**/*.*' , '**/usecase/y**/**/*.*' , '**/usecase/z**/**/*.*' } tasks. register ( 'D' , Test) { exclude '**/persistence/**' , '**/infrastructure/**' , '**/domain/**/*.*' , '**/usecase/**' } ./gradlew test --tests hoge から着想を得ました。 また公式ドキュメントにも「テストのグループ化」というセッションで、似たような方法を紹介しており、こちらも参考にしました。 またこの4つの分け方は、実行時間が均等になるように調整しました。 なぜこの書き方なのか GradleのTestタスクは、正規表現ではなくAnt形式の include/exclude パターンをサポートしています。 クロージャーやSpecを利用した柔軟なフィルタリングもできますが、当時はとにかく早く並列化を実現したかったので、Ant形式を採用しました。 ②:GitHub Actionsでのジョブの並列実行 こちらは matrix を使って、並列実行を実現しました。 unit-test: strategy: matrix: test-task: [ A, B, C, D ] シンプルに、先ほど作成したカスタムタスクを指定し、並列で実行するように設定しました。 ③:JaCoCoレポートの作成 改善前は、1つのジョブで全てのテストを実行していたのでJaCoCoレポートの作成も容易でした。 しかし、テスト実行ジョブを4つに分割して並列化したことで、実行結果をうまくマージしないとJaCoCoレポートが作成できない問題に直面しました。 これに関しては、各テストジョブ実行後に classes ディレクトリと jacoco/*.exec をアーティファクトにアップロードし、カバレッジジョブで集計するようにしました。 レポートを作成しないと、リファクタリングの結果(カバレッジは変わっていないが、実行時間が短縮されている)を正確に確認できないので、まずはこちらを先に設定することをおすすめします。 以下にその定義例を示します。 # 各テストジョブ - name: Upload Compiled Classes and Jacoco Execution Data uses: actions/upload-artifact@n.n.n with: name: compiled-classes-and-jacoco-${{ matrix.test-task }} path: | build/classes **/build/jacoco/*.exec retention-days: 1 # カバレッジジョブ - name: Download Combined Artifact (Compiled Classes & Jacoco Data) uses: actions/download-artifact@n.n.n with: pattern: 'compiled-classes-and-jacoco-*' path: build/download-artifacts - name: Restore Compiled Classes and Jacoco Exec Files run: | mkdir -p build/classes mkdir -p build/jacoco # 各アーティファクトディレクトリからクラスファイルと.execファイルを抽出 for d in build/download-artifacts/*; do echo "Processing artifact directory: $d" if [ -d "$d/build/classes" ]; then cp -R "$d/build/classes/." build/classes/ fi find "$d" -name "*.exec" -exec cp {} build/jacoco/ \; done - name: Generate Jacoco Report run: ./gradlew jacocoTestReport --info 結果 日付 平均実行時間(成功のみ) テスト実行方法 2025年3月 20分50秒 直列 2025年3月 10分40秒(最短10分5秒) 並列 2025年4月 12分5秒 並列 2025年5月 14分26秒 並列 2025年6月 12分5秒 並列 2025年7月 13分41秒 並列 2025年8月 14分47秒 並列 テスト数は増え続けています(3月から8月で、約1,000件増加)が平均実行時間にばらつきがあるのは、GitHub Actionsのランナーリソースが他のワークフローと競合する時間帯に、実行時間が伸びる傾向があるからです。 そのため、開発が活発な期間やバグを集中改修する期間は同時実行数が増え、ほぼ同じテスト数でもリソース待ちが発生し実行時間が伸びてしまっています。 その他 CI実行時間の短縮に直接関係のある話ではないですが、この対応に関連して行ったことを少し整理してみます。 費用対効果の確認と事前調整 今回の対応によってCIのテスト実行時間が短縮されること自体は望ましいですが、並列化によりジョブ数が増えるため、その分消費するGitHub Actions の利用時間も増加します。 私たちの組織では、Enterprise単位で利用可能なGitHub Actionsの分数に上限が設けられているため、この増加が他チームの上限枠を圧迫する可能性を考慮する必要がありました。 今回の対応はCPU/OS 種別の変更を伴わず、あくまでジョブ数の増加による実行時間消費への影響が論点となります。 そのため、改善策を導入する前に社内の横断的な技術サポート組織である開発管理課に相談し、利用状況や上限枠を確認しました。 あわせて、公開情報を参考にコストと削減時間を試算し、想定される費用対効果を数値として提示しました。 その結果、管理職から速やかに承認を得ることができ、問題になる前に先に相談したことで、気持ち的にも余裕を持って対応することができました。 gh コマンド+Claude Codeを使ったワークフロー統計分析 今回の対応を実施し、毎月の推移を集計・確認していました。 内容としては成功したワークフローの実行時間を集計し、平均実行時間を出していました。 ただ集計方法は、正直パワーで押し切っていました。画面からワークフロー結果をコピペし、スプレッドシートに記載して計算していました。 MCPを使って上手く集計できないか試したんですが、当時は適切なツールが無く(自分で開発出来ず)実現はできませんでした。 ただ今回の記事を書くにあたって、 gh コマンドとClaude Codeを使ってみたんですが、これが大成功でした! 以下のようにプロンプトを入力し、期待した結果を得られました。 もし同じような操作を行う場合、選択肢の1つとして覚えていただけたらと思います。 (ちゃんと手作業で計算した結果とほぼ一致していました!) ghコマンドを利用して、hogeリポジトリの「Parallel Tests」ワークフローの2025年n月分の成功時のみの平均実行時間を計算してください!全ブランチ対象で! ↓ 📊 2025年8月 「Parallel Tests」ワークフロー実行統計 全体サマリー - 平均実行時間: 14分47秒(886.93秒) 月間推移(3月→4月→5月→6月→7月→8月) - 平均時間: 10分40秒 → 12分5秒 → 14分26秒 → 12分5秒 → 13分41秒 → 14分47秒 社内へのナレッジ展開 月1回の技術発表の機会で発表してきました。 ラクスではJavaを使った商材が他にもあるので、何か参考になればと思い発表したのですが、後日他のプロダクトのエンジニアから直接声をかけられ、対応方法を聞かれました。 早速今回の対応が少しでも他チームに貢献できるチャンスがあると思うと、嬉しい限りです! 伸び代 最後に伸び代を書きます。 キャッシュの有効活用 重複しているビルド資材のキャッシュ活用 ジョブの再配置 テスト実行時間に偏りが生じてきたため、均等に再分配 セットアップの分離 DBのセットアップ部分を1つにまとめ、各テストへ配布 Self-Hosted Runnerの利用 費用面や速度面で、改善余地の可能性 発展的な改善案 PRで変更があった部分だけテスト実行 ここで「将来的な差分テストなどへの拡張性」が有効に機能する想定 @SpringBootTest などのテストが遅くなる設定を減らす 不要な場所でも使っているので、精査し削除していくことで早くなることを期待 まとめ 今回は、Gradle + GitHub Actionsを用いたCIのテスト実行時間短縮についてご紹介しました。 実施内容と成果: Gradleのカスタムタスクによるテストのグループ化とGitHub Actionsのmatrix機能を組み合わせ、テストを4並列で実行 実行時間を20分50秒から10分40秒へと約50%短縮を実現 テスト数が約1,000件増加した現在でも、14分台で実行完了 特に重要だったポイント: 小さく早く試す - 複雑な最適化より、まず設定変更で実現できる並列化から着手 計測の正確性 - 成功したワークフローのみを対象に改善効果を測定 事前の調整 - 費用対効果を算出し、関係部署と事前に相談することでスムーズな導入 今後もキャッシュ活用や変更箇所のみのテスト実行など、さらなる改善の余地があります。 実際に高速開発には様々な要因が影響するため、今回のCIテスト実行時間の短縮ですぐ効果が出る訳ではないと思います。 しかし、CI完了待ち時間のコンテキストスイッチ減少やPRのフィードバック高速化など、間接的に貢献できていると考えています。 本記事が同様の課題を抱える、特にJavaやGitHub Actionsを利用しているチームの参考になれば幸いです。 それでは!
はじめに こんにちは。楽楽請求でバックエンド開発を担当しているmarumoです。 楽楽請求は2024年10月にサービスを開始した新サービスで、請求書を一元管理し、経理業務を効率化する請求書受領システムです。 その中で、請求書の内容をデータ化するために OCR エンジンの API を活用し、自動データ化機能を提供しています。 請求書の自動データ化は、楽楽請求の中核を担う機能です。 サービスの価値を支えるこの仕組みを安定かつ効率的に動作させるためには、大量の請求書を迅速に処理できることが欠かせません。 大量の請求書を確実に捌くため、OCR エンジンの API 呼び出し処理は高並列化を前提に構築しました。 しかし、高並列処理の負荷検証中に「CPU やメモリは安定しているのに、スレッド数だけが増え続ける」という現象に遭遇しました。 本記事では、その原因をどのように特定し、どのように解決したのかを紹介します。 はじめに 利用技術 背景:スレッドが増え続ける 調査 解決策:HTTP クライアントの再利用 Before:毎回生成していたケース After:シングルトンで再利用するケース 検証 検証目的 検証方法 検証結果 結論 まとめ 利用技術 本記事で扱う実装は Spring Boot をベースにしています。主に利用している技術スタックは以下の通りです。 アプリケーションフレームワーク : Spring Boot 3.5 HTTP クライアント : RestTemplate(内部実装として Apache HttpClient 4 系を使用) この記事では、これらの技術を前提にHTTP クライアントのライフサイクル管理に起因するスレッド増加問題とその解決策を解説します。 なお、本文中のサンプルコードは Kotlin で記載していますが、問題の本質は言語に依存しません。 背景:スレッドが増え続ける OCR エンジンの API 呼び出し処理の負荷試験として、50 並列で OCR API を呼び出す構成に対して 500 ファイルを同時にアップロードしました。 すると CPU やメモリには大きな問題がないにもかかわらず、JVM のライブスレッド数だけが直線的に増加し、最終的には 1,000 を超える状態に達しました。 調査 最初に疑ったのは外部 API の遅延やハングでしたが、Thread Dump を確認すると RUNNABLE 状態の短命スレッドが大量に存在しており、この仮説は否定されました。 さらに Thread Dump を詳細に解析したところ、 java.net や org.apache.http に関連するスレッドが多数存在していることが分かりました。 これらは HTTP クライアントの内部スレッドであり、RestTemplate の利用に伴うものです。 HttpClient-2-SelectorManager@19,900 in group "main": RUNNING HttpClient-3-SelectorManager@19,902 in group "main": RUNNING HttpClient-32-SelectorManager@19,942 in group "main": RUNNING HttpClient-34-SelectorManager@19,944 in group "main": RUNNING HttpClient-35-SelectorManager@19,946 in group "main": RUNNING HttpClient-36-SelectorManager@19,949 in group "main": RUNNING HttpClient-37-SelectorManager@19,950 in group "main": RUNNING ... ... ... 解決策:HTTP クライアントの再利用 採用した方針は、 HTTP クライアントを再利用可能な形で管理すること です。 具体的には以下を実施しました。 RestTemplate を シングルトン Bean として定義 呼び出し側での build() 呼び出しを廃止し、DI 渡しのインスタンスを利用 Before:毎回生成していたケース この場合、リクエストのたびに HttpClient が新規生成され、内部で SelectorManager や Worker スレッドも作られるため、スレッド数が累積していきます。 class OcrServiceFactoryImpl { fun createRestTemplate(): RestTemplate { // 毎回 build() を呼び出して新しい HttpClient を生成 return RestTemplateBuilder() .setConnectTimeout( Duration .ofSeconds( 5 )) .setReadTimeout( Duration .ofSeconds( 30 )) .build() } fun callOcrApi(request: OcrRequest): OcrResponse { val restTemplate = createRestTemplate() return restTemplate.postForObject( "https://example.com/ocr" , request, OcrResponse :: class .java) !! } } After:シングルトンで再利用するケース この構成では RestTemplate がアプリケーション全体で 1 インスタンス共有されるため、内部の HttpClient も再利用され、スレッド数が増え続ける問題が解消される。 @Configuration class RestTemplateConfig { @Bean fun restTemplate(builder: RestTemplateBuilder): RestTemplate { return builder .setConnectTimeout( Duration .ofSeconds( 5 )) .setReadTimeout( Duration .ofSeconds( 30 )) .build() } } @Service class OcrService( private val restTemplate: RestTemplate ) { fun callOcrApi(request: OcrRequest): OcrResponse { return restTemplate.postForObject( "https://example.com/ocr" , request, OcrResponse :: class .java) !! } } 検証 検証目的 RestTemplate をシングルトンに変更した構成で、HTTP 通信が直列化していないか(並列性を維持しているか)を確認する 併せて、以前発生していた「スレッド数が通信ごとに増加する問題」が解消されているかを確認する 検証方法 以下を観測しました - RestTemplate の通信ログ(DEBUG)によるリクエストの並列性 - Grafana の jvm_threads_live_threads メトリクスによるスレッド数の挙動 - IntelliJ の Thread Dump によるスレッドの状態(処理中と処理完了後の2タイミング) 検証結果 スレッド数の挙動 修正前:アップロードのたびに HttpClient-SelectorManager が増加し、 jvm_threads_live_threads が400超に 修正後: RestTemplate の使い回しにより、スレッド数は一定値(100前後)で安定し、増加し続ける挙動は再現されなかった Thread Dump 通信中は HttpClient-1-SelectorManager が RUNNABLE 状態で非同期処理を担当 通信後も同一スレッドが残存していたが、新規増加は見られず再利用が確認された 並列性 通信ログのタイムスタンプを比較すると、複数のスレッドが同時にリクエストを発行しており直列化はされていない レスポンス順序もランダムで、リクエストが並列で処理されていることを確認 // リクエスト開始ログ 2025-04-15 10:49:26.204 DEBUG 1 --- [pool-5-thread-6] o.s.web.client.RestTemplate : HTTP POST https://example.com/ocr 2025-04-15 10:49:26.204 DEBUG 1 --- [pool-5-thread-7] o.s.web.client.RestTemplate : HTTP POST https://example.com/ocr 2025-04-15 10:49:26.204 DEBUG 1 --- [pool-5-thread-4] o.s.web.client.RestTemplate : HTTP POST https://example.com/ocr 2025-04-15 10:49:26.204 DEBUG 1 --- [pool-5-thread-5] o.s.web.client.RestTemplate : HTTP POST https://example.com/ocr ... // レスポンスログ 2025-04-15 10:49:31.756 DEBUG 1 --- [pool-5-thread-7] o.s.web.client.RestTemplate : Response 200 OK 2025-04-15 10:49:31.929 DEBUG 1 --- [pool-5-thread-5] o.s.web.client.RestTemplate : Response 200 OK 2025-04-15 10:49:32.029 DEBUG 1 --- [pool-5-thread-6] o.s.web.client.RestTemplate : Response 200 OK 2025-04-15 10:49:32.871 DEBUG 1 --- [pool-5-thread-4] o.s.web.client.RestTemplate : Response 200 OK 結論 RestTemplate をシングルトンにしたことで、内部の HttpClient も共有され、 通信スレッドの再利用が有効に働いた その結果、 スレッド数が通信ごとに増加する問題は解消 HttpClient-1-Worker-* および HttpClient-1-SelectorManager のスレッドが再利用されていることが Thread Dump により確認され、リソースの安定性が確保された まとめ 今回の事例、 HTTP クライアントのライフサイクル管理不足がスレッド増加の原因になり得る という点が明らかになりました。 RestTemplate をシングルトン化し用途ごとに分離することで、リソースの再利用・安定性・性能の維持を実現しました。
はじめに AI関連の話題 AI活用状況のスナップショット うまくいっていること オンボーディング支援 コードリーディングの支援 ボイラープレートの自動生成 単純で広範な一括修正の自動化 AIによるプルリクの一次レビュー うまくいかなかったこと(限界と落とし穴) 自立型コードエージェントによる実装 非決定性(出力の揺らぎ) コンテキスト忘却 Kotlin×IDEロックイン問題 学んだこと まとめ 参考文献 はじめに 楽楽請求開発チームのkyoshimotoです。 バックエンド開発チームに所属し、開発チームをスケールさせるための開発プロセス整備、チーム内でのAI活用の推進を担当しています。 本記事では、現時点のAI活用状況や、うまくいっている点・うまくいっていない点、学んだことを共有します。 AI関連の話題 AI活用に関する期待を一段と高める話題が増えています。代表例を挙げます。 AnthropicのAmodei氏「3–6ヶ月で90%のコード、12ヶ月で本質的にすべてをAIが書く可能性」に言及(Business Insider, 2025/03) MicrosoftのSatya Nadella氏「社内コードの20〜30%はAIが書いている」(TechCrunch, 2025/04) GoogleのChief Scientist Jeff Dean氏「1年以内にジュニアエンジニア相当の性能に到達し得る」(Business Insider, 2025/05) Windsurfチーム「コードの約95%はCascadeとWindsurf Tabで書かれている」(The Pragmatic Engineer, 2025/07) こうした記事を読むと心が踊る一方で、今後のエンジニアの仕事はどうなるのかと不安になる方もいると思います。とはいえ、商用プロダクトのソフトウェア開発の現場では、直近でエンジニアがAIに置き換わる気配は正直感じていません。 ツール導入の成果は確かに出ていますが、効果が顕著な領域はまだ限定的で、期待値は少し落ち着いたというのが正直な実感です。 AI活用状況のスナップショット 現在、チームで利用・検証している主なツールです。新しいツールが日々リリースされ乱立する中、IntelliJ+GitHub Copilot を標準的な開発環境としつつ、他のツールも試用しながら開発プロセスに組み込んでいます。 なお、バックエンドのプログラミング言語はKotlinがメインとなります。 AIツール 主な用途 ChatGPT ドキュメント作成/コード生成/コードレビュー Claude(検証中) コード生成 Codex コードレビュー Devin コード生成/ロジック調査/CIエラー対応/コードリーディング支援 Gemini ドキュメント作成/コード生成/コードレビュー GitHub Copilot コード補完/コード生成/ロジック調査/コードレビュー/コードリーディング支援 NotebookLM 仕様検索/新規メンバーのオンボーディング支援 うまくいっていること オンボーディング支援 外部設計書をNotebookLMに取り込み、仕様Q&Aを即時化。心理的安全性の高い質問窓口として機能し、新規メンバーの立ち上がりが加速しました。 2025年度はKotlin未経験者が10名以上ジョインしましたが、GitHub Copilotのタブ補完/チャットにより新しいプログラミング言語の習得が障壁になることなく、スムーズに実装タスクを開始できています。 NotebookLMイメージ コードリーディングの支援 IntelliJ+GitHub Copilotで既存ロジックの理解を効率化。実装から仕様を逆引きして整理でき、質問対応の精度も向上。 処理内容の要約や、UMLやER図の抽出など、コードリーディングの時間短縮やシステム理解に活用できています。 私の担当プロダクトは昨年10月にリリースしたばかりで、自社の別プロダクト(楽楽精算など)の機能実装を参考にする場面が多くあります。そこでDevinを用い、複数リポジトリを横断して実装を調査することで、調査コストを削減できています。プロダクト横断のコードリーディングが格段に楽になりました。 ボイラープレートの自動生成 マスタ系CRUDのAPI実装やValidationなどの定型実装の一部をDevinで自動生成できる成功事例が増えています。現在は、プロンプトのテンプレート化とプロセス組み込みを進めています。 プロンプトイメージ 単純で広範な一括修正の自動化 スキーマ変更に伴うエンティティ/テストデータ更新、APIリクエスト/レスポンス構造変更など、ロジックは単純だが影響範囲が広い作業は、数百ファイル規模でもDevinを使ってワンショットで完了するケースが多く、開発コスト削減の効果が出ています。 AIによるプルリクの一次レビュー PR作成直後にAIがタイポ、コメントの誤り、表記ゆれ、スタイル不一致といった軽微な指摘を先出しします。これにより、レビュアーは設計やロジックなど本質的な論点に専念できています。 (GitHub Copilot Code Review, OpenAI Codexを利用) GitHub Copilot Code Reviewイメージ うまくいかなかったこと(限界と落とし穴) 自立型コードエージェントによる実装 Devin、GitHub Copilot、Cursorを使って、実装タスクの自動化を検証しましたが、成功はボイラープレート生成と単純一括修正にほぼ限定されます。 その他では、以下のような課題が上がりました。 セッション(会話)が長引くほど生成精度が劣化し、生成コードの採用に至らない。 指示の具体化や前提整理に時間がかかり、人が実装した方が速い場面が多い。 非決定性(出力の揺らぎ) 同一プロンプトでも出力がぶれるため、AIツールで生成したコードの比較や精度評価の収束に難航しました。 AIの「非決定性」問題を認識せず、プロンプトやコンテキストに“おまじない”的な調整やハックに頼った結果、再現性のない成功パターン探しに時間を浪費してしまうことがありました。 コンテキスト忘却 プロンプトが長くなると冒頭・末尾が優先され中盤が抜けやすい(Lost in the Middle問題)。さらに、コンテキストウィンドウ上限を避けるためのコンテキスト圧縮過程で重要事項が脱落。結果として、 ガイドラインを与えても全項目の遵守は期待しづらい。 コードレビューもごく一部の規約しか参照されない。 セッションが長くなるほど残り10〜20%の実装をAIで完遂するのが難しい。 指定外リポジトリへのPR作成、プロンプトに明示した指示の無視が散見される(例:「PRを作成しないで」「プロパティファイルの定義は辞書順で」といった指示が無視される)。 Kotlin×IDEロックイン問題 標準の開発環境は IntelliJ+GitHub Copilotですが、VS Code版と比べて機能提供の遅れや不具合が目立ち、体験品質に課題が残ります。いっぽうでVS Code系(Copilot in VS Code/Cursor)はKotlinの言語サポートやコードジャンプが不十分で、AIツール導入の移行障壁になっています。結果として、Kotlinの採用により、IntelliJへの事実上のベンダーロックインが生じ、AI活用推進のボトルネックになっていると分かりました。 学んだこと 「ボイラープレートの自動生成」「単純だが広範な一括修正」「要約・逆引き」はAIの得意分野です。ここを主戦場に据えると、費用対効果は安定します。 一方で、非決定性と忘却の問題を抑え込めば、AI活用の適用範囲は広がります。プロンプトやコンテキストの小手先の最適化に偏るよりも、静的解析やユニットテストで出力を厳密に検証できる土台の強化に重きを置くべきだと考えます。期待値をコードで固定できれば、エージェントは失敗に一貫して反応し、自己修復ループに乗りやすくなります。 まとめ AIは「補助輪」です。走り出しを助け、速度を上げ、転びにくくし、学びを加速します。ただし、進むべき方向を決めるのは人間です。 できること/できないことをチームで共有し、使いどころを明確にする。こうした地道な運用こそが、いま現場で効いています。次は、この運用を標準化し、対象領域を段階的に拡大していくことが重要だと考えます。 参考文献 www.businessinsider.com techcrunch.com www.businessinsider.com Windsurf「約95%はCascade/Tabで」—LDX3講演スライド Lost in the Middle—TACL/論文(Liu et al., 2023)
はじめに こんにちは。メールディーラーAI開発課のmarronです。エンジニアブログ初投稿となります。よろしくお願いします。 私が所属しているメールディーラーAI開発課では、主にメールディーラーに搭載されるAI機能の開発を担当しています。 現在は10月にリリース予定の回答自動生成エージェントの開発を進めています。 この機能を開発するにあたって、新たにベクトルDBを利用したナレッジの検索機能が必要となりました。 本記事では、ベクトルDBでの検索精度を上げるために導入したハイブリッド検索についてご紹介します。 はじめに ベクトルDBの選定 ベクトルDBとは メールディーラーで採用したベクトルDB 密ベクトルを用いた検索 Qdrantでの密ベクトル検索 密ベクトル検索の欠点 疎ベクトルを用いた検索 疎ベクトルとは Qdrantでの疎ベクトル検索 両方の検索結果を組み合わせるハイブリッド検索 密ベクトルと疎ベクトルの両方を活用する Qdrantでのハイブリッド検索 まとめ 参考文献 ベクトルDBの選定 ベクトルDBとは そもそも「ベクトルDBとはなんぞや」という方もいらっしゃると思いますので、簡単に説明させていただきます。 開発中の機能では、過去の対応履歴やFAQをもとに作成したナレッジから問い合わせの回答に必要となる情報を検索します。 ナレッジの検索には全文検索を利用したキーワード検索を用いても良いのですが、キーワード検索の場合は問い合わせに含まれるキーワードがナレッジに含まれるキーワードと少しでも異なると情報がヒットしません。 この問題を解決するために、ナレッジや問い合わせを数値ベクトル化してベクトルの近さによる検索を行います。この数値ベクトルは、文章全体がどういった意味を示しているかを多次元で表したものになります。 検索時はベクトル同士の距離を測ることで、距離が近い = 意味が似ているデータを見つけることができます。ベクトル化(埋め込み表現)については参考文献もご参照ください。 このベクトルデータを保存するのに利用するのがベクトルDBになります。ベクトルDBはベクトルデータを保存することに特化しており、膨大なベクトルデータ同士の距離計算を効率よく高速に行ってくれるものになります。 メールディーラーで採用したベクトルDB メールディーラーでベクトルDBを利用するのは2回目なのですが、前回採用したChromaDBでは今回の機能を提供するにあたってパフォーマンス面で不安がありました。 そのため、別途ベクトルDB専用のサーバを構築することにしました。 利用するDBを選定するために以下のような条件を満たす製品を探すことにしました。 オンプレミス環境で動作すること マルチテナント構成に対応できること 複数台サーバを用いた負荷分散が行えること この条件を満たす製品として「Milvus」「Qdrant」「PGVector」を候補として、パフォーマンスや使いやすさを比較しました。 結論としてはタイトルにもある通り、Qdrantを採用することにしました。理由は下記の通りです。 Rust製で、高速に検索が行えることを謳っている Dockerコンテナだけでなく、単体バイナリとしても配布されており、オンプレミスのサーバに導入しやすい マルチテナントでの運用方法がドキュメントに記載されており、インデックスの最適化方法まで記載されている 簡単な設定で分散環境を構築することができる 利用するベクトルDBが決定したので、実際に利用してみます。 密ベクトルを用いた検索 Qdrantでの密ベクトル検索 最初に説明した文章全体の意味合いを示したベクトルデータを密ベクトル(Dense Vector)と呼びます。まずはこの密ベクトルを利用した検索を行ってみます。 ベクトルデータを投入するためにQdrant上にコレクションを準備します。 今回のサンプルコードはすべてPythonで記載しています。また、Qdrant公式のDockerイメージを利用して環境を構築しています。 from qdrant_client import QdrantClient, models qdrant = QdrantClient(url= "http://localhost:6333" ) qdrant.create_collection( collection_name= "dense_collection" , vectors_config=models.VectorParams(size= 1536 , distance=models.Distance.COSINE) ) size にはベクトルデータの次元数を指定します。今回はOpenAIのEmbeddings APIによるベクトル化を行い、モデルに text-embedding-3-small を利用するため、1536次元を指定しています。 distance にはベクトルデータの距離計算に利用する方式を指定します。今回はテキストの類似度を調べるのに最適とされるコサイン類似度を指定しています。 コレクションが作成出来たら、検索対象のデータを保存します。今回はChatGPTを利用して、5つの猫種の特徴を20件ずつ文章にしてもらい、計100件のデータを投入しました。 アメリカンショートヘアは筋肉質である。 スコティッシュフォールドは耳が折れている猫である。 メインクーンは世界最大級の猫である。 シャムは社交的な猫である。 ペルシャは長毛の猫である。 ... from qdrant_client import QdrantClient, models from openai import OpenAI qdrant = QdrantClient( "http://localhost:6333" ) openai = OpenAI() with open ( "cat.txt" , "r" , encoding= "utf-8" ) as f: for idx, line in enumerate (f): line = line.strip() response = openai.embeddings.create( input =line, model= "text-embedding-3-small" ) vector = response.data[ 0 ].embedding qdrant.upsert( collection_name= "dense_collection" , points=[ models.PointStruct( id =idx, vector=vector, payload={ "text" : line} ) ] ) Qdrantではベクトル以外のデータをpayloadという形式で保持します。今回はベクトル化対象の文章をpayloadに保持するようにしています。 準備が出来たので、投入したデータに対して検索を行ってみます。 from qdrant_client import QdrantClient, models from openai import OpenAI qdrant = QdrantClient( "http://localhost:6333" ) openai = OpenAI() query = "穏やかな性格の猫" response = openai.embeddings.create( input =query, model= "text-embedding-3-small" ) vector = response.data[ 0 ].embedding search_result = qdrant.query_points( collection_name= "dense_collection" , query=vector, with_payload= True , limit= 5 ) for point in search_result.points: print (f "Score: {point.score}, Text: {point.payload['text']}" ) 実行結果がこちらになります。 Score: 0.6045555, Text: メインクーンは落ち着いた雰囲気を持つ猫である。 Score: 0.59054977, Text: ペルシャは優雅な雰囲気を持つ猫である。 Score: 0.565565, Text: ペルシャは静かな生活を好む猫である。 Score: 0.55805016, Text: ペルシャは穏やかな性格である。 Score: 0.5490287, Text: スコティッシュフォールドは静かな環境を好む猫である。 無事似ている文章を検索することが出来ました。 このときのスコアはベクトルの類似度を示しており、コサイン類似度を利用した時は値が大きいものほど似ていることを示しています。 密ベクトル検索の欠点 密ベクトルを利用した検索では文章が似ているかどうかに着目して検索を行います。 このとき、あくまで類似度に着目して検索を行うため、全く同じキーワードを含んでいるかには着目していません。これが弱点となるパターンがあります。 例えば、「○○ではAボタンを決定として使う」と「××ではBボタンを決定として使う」というナレッジがあったとします。 このときに「○○ではどのボタンが決定ですか?」という問い合わせがあった場合に前者の情報を使いたいのにもかかわらず、後者の情報を取得する可能性があります。これでは回答文として利用できません。 そこでキーワード検索と同じ仕組みをベクトル検索でも行えるようにします。 疎ベクトルを用いた検索 疎ベクトルとは キーワード検索を行うためには文章内にどのような単語が含まれているかを知る必要があります。そこで利用するのが疎ベクトル(Sparse Vector)になります。 密ベクトルではベクトル内の値が0以外であることが多いのですが、疎ベクトルでは単語の頻出度や決まった単語との類似度を示すため、ベクトル内の値が0であることが多いです。そのため、値がスカスカのベクトルという意味で疎ベクトルと呼ばれます。 疎ベクトル同士を比較することで単語ごとの頻出度を調べ、検索したいキーワードが多く含まれる文章を取り出すことが出来ます。 Qdrantでの疎ベクトル検索 Qdrantでは1つの文章に対して、密ベクトルと疎ベクトルの両方を保持することが出来ます。 ただし、既存のコレクションに対して、保持するベクトルの個数や形状を変更することができないため、新たにコレクションを作成します。 from qdrant_client import QdrantClient, models qdrant = QdrantClient(url= "http://localhost:6333" ) qdrant.create_collection( collection_name= "sparse_collection" , vectors_config={ "dense" : models.VectorParams(size= 1536 , distance=models.Distance.COSINE)}, sparse_vectors_config={ "sparse" : models.SparseVectorParams()} ) 次に密ベクトルと一緒に疎ベクトルを投入します。 密ベクトルはOpenAI APIを利用することで生成することが出来ますが、疎ベクトルを生成するAPIは提供されていません。 そのため、疎ベクトル化は自ら行う必要があります。 また、今回疎ベクトル化にはQdrantが提供しているライブラリを採用したのですが、このライブラリは日本語のトークン化(文章の単語を切り分ける処理)に対応していません。そのため、トークン化の処理を自前で実装する必要があります。 疎ベクトル化の処理は参考文献を元に実装しています。今回のサンプルプログラムではこの疎ベクトル化の処理については省略させていただきます。 密ベクトルのところで利用した100件のデータから作成した密ベクトルと疎ベクトルを投入します。 from qdrant_client import QdrantClient, models from openai import OpenAI qdrant = QdrantClient( "http://localhost:6333" ) openai = OpenAI() # TextEmbedderの実装については省略 embedder = TextEmbedder() with open ( "cat.txt" , "r" , encoding= "utf-8" ) as f: for idx, line in enumerate (f): line = line.strip() response = openai.embeddings.create( input =line, model= "text-embedding-3-small" ) dense_vector = response.data[ 0 ].embedding sparse_vector = embedder.embed_query(query_text=line) qdrant.upsert( collection_name= "sparse_collection" , points=[ models.PointStruct( id =idx, vector={ "dense" : dense_vector, "sparse" : models.SparseVector( indices=sparse_vector.indices.tolist(), values=sparse_vector.values.tolist(), ) }, payload={ "text" : line} ) ] ) 疎ベクトルだけを使って検索を行ってみます。 from qdrant_client import QdrantClient, models from openai import OpenAI qdrant = QdrantClient( "http://localhost:6333" ) openai = OpenAI() query = "穏やかな性格の猫" # TextEmbedderの実装については省略 embedder = TextEmbedder() sparse_vector = embedder.embed_query(query_text=query) search_result = qdrant.query_points( collection_name= "sparse_collection" , query=models.SparseVector( indices=sparse_vector.indices.tolist(), values=sparse_vector.values.tolist() ), using= "sparse" , with_payload= True , limit= 5 ) for point in search_result.points: print (f "Score: {point.score}, Text: {point.payload['text']}" ) 実行結果がこちらになります。 Score: 2.0, Text: スコティッシュフォールドは穏やかな性格である。 Score: 2.0, Text: ペルシャは穏やかな性格である。 Score: 2.0, Text: アメリカンショートヘアは穏やかな性格である。 Score: 2.0, Text: メインクーンは穏やかな性格である。 Score: 1.0, Text: メインクーンは子どもと仲良くできる猫である。 単語単位で似ている文章が取り出せていることが分かります。 このときのスコアは似ている単語がどの程度文章に含まれているかを示しています。 両方の検索結果を組み合わせるハイブリッド検索 密ベクトルと疎ベクトルの両方を活用する これで文章の類似度による検索とキーワードを利用した検索を行えるようになりました。 この2種類の検索方法は互いの弱点を補うものですので、検索結果を統合することで精度を上げることが出来ます。 問題はこの2つの検索結果をどうやって統合するかです。それぞれの検索結果を元に類似度が高い文章を取り出したいので、それぞれのスコア(順位)から再度スコア計算を行う必要があります。 この仕組みをハイブリッド検索と呼びます。ハイブリッド検索を簡単に行える方法をQdrantが提供していますので、実際に使ってみます。 Qdrantでのハイブリッド検索 Qdrantでハイブリッド検索を行う場合、まず prefetch という機能で先に密ベクトル、疎ベクトルのそれぞれの検索結果を作成しておきます。 その後、検索結果を統合するために検索クエリに fusion を指定します。このとき、統合する方法にはRRF手法とDBSF手法を利用できます。 RRF手法は検索結果の順位に基づいて新しいスコアを計算します。一方、DBSF手法は検索結果そのもののスコアを利用して新しいスコアを算出します。 RRF手法は順位ベースのため、異なる性質を持つベクトル(今回の場合は密ベクトルと疎ベクトル)を比較する際に有効です。そこで今回はRRF手法を採用しました。 逆に、同種のベクトルで内容が異なる複数の検索結果を統合する場合には、DBSF手法の方が適していると考えられます。手法の詳しい内容については参考文献をご参照ください。 では、ハイブリッド検索を実際に行ってみます。コレクションは疎ベクトルの時に作成したものを利用します。 from qdrant_client import QdrantClient, models from openai import OpenAI qdrant = QdrantClient( "http://localhost:6333" ) openai = OpenAI() query = "穏やかな性格の猫" response = openai.embeddings.create( input =query, model= "text-embedding-3-small" ) dense_vector = response.data[ 0 ].embedding # TextEmbedderの実装については省略 embedder = TextEmbedder() sparse_vector = embedder.embed_query(query_text=query) search_result = qdrant.query_points( collection_name= "sparse_collection" , prefetch=[ models.Prefetch( query=dense_vector, using= "dense" , limit= 5 ), models.Prefetch( query=models.SparseVector( indices=sparse_vector.indices.tolist(), values=sparse_vector.values.tolist() ), using= "sparse" , limit= 5 ) ], query=models.FusionQuery(fusion=models.Fusion.RRF), with_payload= True , limit= 3 ) for point in search_result.points: print (f "Score: {point.score}, Text: {point.payload['text']}" ) 検索結果を統合するため、prefetchで取得する件数は最終的に必要な件数より大きくしておく必要があります。prefetchの取得件数が少ないと統合した際にどちらかのベクトルに偏ったデータも取得されてしまいます。 実行結果がこちらになります。 Score: 0.53333336, Text: ペルシャは穏やかな性格である。 Score: 0.5, Text: メインクーンは落ち着いた雰囲気を持つ猫である。 Score: 0.5, Text: スコティッシュフォールドは穏やかな性格である。 文章の類似度と単語単位での類似度を元に文章が取り出せています。 このときのスコアはそれぞれのベクトルでの順位を元に算出されたものとなります。 まとめ Qdrantを利用したハイブリッド検索を活用することで、ナレッジの検索精度を向上させました。 みなさんもRAGのような文章検索を行う場面がありましたら、ぜひハイブリッド検索を利用した精度向上を行ってみてください。 参考文献 atmarkit.itmedia.co.jp qdrant.tech qiita.com dev.classmethod.jp
はじめに こんにちは。楽楽明細開発チームのtkktです。 楽楽明細は2013年のサービス開始以来、10年以上の運用を続け、現在では1万を超えるお客様にご利用いただいています。 しかし、この成長の裏側では、長年の機能追加や複雑化による技術的負債が蓄積し、 新機能追加のたびに調査やテスト工数が増大するなど、サービスの成長スピードを阻害する課題に直面していました。 今回は、その課題を乗り越えるために取り組んだシステム刷新の一部をお話ししたいと思います。 目次 刷新の背景 実際の取り組み リリース後の課題と改善 成果と効果 今後の展望 まとめ 刷新の背景 長年の運用により、システムには以下のような課題が生じていました。 古いフレームワーク利用によるリスク サポート終了が迫り、セキュリティリスクが高まっていた 周辺のミドルウェアやライブラリのバージョンアップも妨げられていた 開発効率の低下 度重なる機能追加によりコードが複雑化 影響調査やテストに多くの工数が必要 本番環境での障害やインシデントも少なくなかった 当初の開発メンバーが離職しており、仕様把握が難しくなっていた 顧客数増加による影響 操作の多様化でこれまで出なかった不具合が顕在化し、障害対応工数が増加 クラスタ数の増加でリリース時間も長くなっていた さらに、手動テスト中心だったため、開発全体の工数に占めるテスト工数の比率が大きくなり、改善活動に時間を割けない状況もありました。 こうした背景から「今後の安全性・開発効率を確保するには刷新が必須」となり、プロジェクトが動き出しました。 実際の取り組み 刷新の第一歩として、利用者が限定的な一部機能を対象にしました。 セキュリティリスクが高く、既存システムからの切り離しが容易だったためです。 移行は二段階で実施しました。 第1弾 :既存アプリ内の処理を新フレームワークに書き換え、まずは安定稼働を実現 第2弾 :本体アプリから切り離して独立稼働させ、フロントで受けたリクエストを新アプリに振り分ける構成に変更 段階的なシステム刷新のイメージ この二段構えにより、障害や不具合発生のリスクを抑えつつ、新基盤への移行を進めることができました。 リリース後の課題と改善 第1弾リリース直後には、想定外の問い合わせや不具合対応に苦労しました。 特に、利用者環境に依存する問題(ブラウザ拡張機能やセキュリティソフトの影響など)が発生し、リクエストがアプリに届かないケースでは原因調査に時間を要しました。 チームでは、問い合わせいただいたお客様に協力いただき、スクリーンショットやコンソールログを取得して原因を特定するなど、地道な調査を重ねました。 その経験を踏まえ、以降はフロントエンドの操作状況やエラーを分析できる仕組みを導入。さらに次の段階では、システムの監視基盤を強化し、不具合の早期発見と原因特定をしやすくする取り組みを進めています。 成果と効果 刷新の効果は、コード品質やテスト体制の改善に顕著に表れています。 コードの複雑度が大幅に改善 刷新前は数十クラスで「変更すると誤修正を招きやすい」と評価される状態 刷新後はそうした箇所がほぼ解消され、多くのクラスが低い複雑度に テスト体制の強化 リリース前はテストの大半が手動で、カバレッジは全体で30%程度 刷新部分では自動テストを整備し、カバレッジが80%を超える水準に向上 フロントエンドとバックエンドを分離したことで、テストのしやすさも格段に向上 刷新直後は不具合対応が発生しましたが、マイナーリリースを重ねて早期に収束。現在ではアプリケーション起因の不具合は減り、安定性が高まっています。 今後の展望 刷新はまだ道半ばです。 今後は、サポートが終了していくフレームワークやライブラリを計画的に置き換えるとともに、アーキテクチャの刷新にも取り組んでいきます。 機能や要件によっては、新しいサービス基盤への移行も視野に入れています。 また、アプリケーションの稼働環境についても改善を進め、より柔軟かつ効率的に運用できる仕組みを整備する予定です。 さらに、組織全体としてもAIを積極的に活用しており、刷新の過程でも開発効率化や品質向上に役立てていく方針です。 まとめ 今回紹介した取り組みは、まだ刷新プロジェクトの一部に過ぎません。 今後も継続して改善を進め、お客様により安心して利用いただけるシステムを目指すとともに、開発者にとっても挑戦しがいのある環境を築いていきます。
こんにちは、株式会社ラクスでデザイナーをしているかっつです。 今回は私が所属しているプロダクトデザイン3課でデザインレビューを始めることになった話をご紹介します。 デザインレビューをする文化がなかったところからなぜ始めることになったのか、 上手く行っていること・行かなかったことなどお話しします。 これからデザインレビューをチーム内で始めようとしている方の参考になれば嬉しいです。 1. デザインレビューを始めるきっかけ 2. どう始めたか いきなりレビューではなく、まず輪読会から レビュー用フォーマットの導入 3. 上手くいったこと 議論が建設的になった 副次的にチームの課題を解決できた 4. 課題とこれから 5. まとめ 1. デザインレビューを始めるきっかけ ラクスのデザイナーはそれぞれが担当するプロダクトを持ち、デザインを進めています。 その状況もあり、プロダクトをまたいだレビューの機会はあまりありませんでした。 ただ最近、課内で案件の進捗を共有する時間が設けられるようになり、そこで偶発的に「そのデザイン、こうしたらもっと良さそう」と意見を言い合ったり、議論が生まれるようになってきました。 また、採用が進んでメンバーが増えてきたこともあり自然と「レビュー」の必要性が高まっていきました。 とはいえ、お互いに「レビュー」に対する認識が揃っていなかったので、フィードバックがしづらかったり、受け取り方にズレが出てしまうこともありました。 そこで、私たちは課として「デザインレビュー」を正式に始めることにしました。 目的は大きく2つあり、 1つ目は楽楽シリーズ全体のデザイン品質を高めること。2つ目はレビューを通してお互いに学び合い、スキルを高めていくことです。 2. どう始めたか いきなりレビューではなく、まず輪読会から 「レビュー」に対する認識やお作法をそろえるために、まずは 『みんなで始めるデザイン批評』 という本を使って輪読会を始めました。 みんなではじめるデザイン批評―目的達成のためのコラボレーション&コミュニケーション改善ガイド 作者: アーロン・イリザリー , アダム・コナー ビー・エヌ・エヌ新社 Amazon 輪読会の目的は、本の内容を理解することよりも以下を重要視しています。 みんなが何に関心を持っているか どんなところでつまずきやすいか 「レビュー」という言葉をどう解釈しているか こうした違いを理解して、お互いの認識のズレを解消することを意識しました。 本という共通のオブジェクトがあることで、「Aという事例を見て自分はこう解釈した」と話せるので、議論がとてもスムーズになります。これがないと、同じ言葉を使っていても実は違うイメージで話している…なんてことが起きてしまいます。 輪読会では何を議論したいかをグルーピングして議論していきます 実際に輪読会をしてみると、 「レビューって承認のことだと思っていた」 「人柄がわからない相手には意見しづらい」 「デザインレビューは手段にすぎなく、担当外のスプリントレビューに他デザイナーも入るで良いのでは?」 「ファシリテーションスキルが大事になる。どうしたら伸ばせる?」 など、想定していなかった課題や施策も見えてきました。これらは輪読会の時間の中で一つずつ解決を試みました。 輪読会で議論した様子 ※輪読会の効果や具体的な進め方は別の記事で紹介しようと思います。 レビュー用フォーマットの導入 輪読会を重ねる中で「仕組み化した方がいいよね」という流れになり、レビュー用のフォーマットを作ることにしました。 このフォーマットは、輪読会で出てきた「これだけは大事にしたい」という要素を集めて型化したものです。 レビュールールとフォーマット また運用ルールも作成しました。 事前にNotionに書き込んでチャットで連絡 週2回レビュー用の時間を確保して、利用しても良い時間にする Notionの起票場所 私たちのレビューの特徴は、 承認フローではなく「意見交換の場」として位置づけていること です。 最終的なオーナーシップはあくまでデザイナー本人にあり、レビューはより良くするためのヒントを持ち寄る場としています。 3. 上手くいったこと 実際にデザインレビューを始めるにあたり、いくつかのことが改善されました。 議論が建設的になった 今までは案件の共有の中でデザインレビューが偶発的に行われていたので、論点があっちこっちに行っていました。 それがレビューフォーマットを導入したことや、レビューへの概念が認識統一されたことで、コミュニケーションが建設的になりました。 結果として、レビューを依頼する人は欲しい情報を持って帰れるようになりました。 副次的にチームの課題を解決できた 前述した通り、デザインレビューをする前段の部分である相互理解、そもそものファシリテーションスキルをどう高めるかといった部分まで議論ができ、それに向けてアクションをすることができました。 特に相互理解の部分は、お互いの過去の経験や得意なこと・苦手なことなどを知る良い機会となり、チームで協業する土台を作ることができました。 ※『実際にあなたのチームは機能していますか』という本では、信頼の欠如がチームの機能不全となる要因と書かれており、お互いの歴史や強みを知ることは効果的とされています。 あなたのチームは、機能してますか? 作者: パトリック・レンシオーニ 翔泳社 Amazon 4. 課題とこれから もちろん、課題もまだあります。 一番大きいのは、 レビューが任意であるため、参加や活用にばらつきがあることです。 困ったときに声をかけやすくなったという良い変化はあるものの、「楽楽シリーズ全体のデザイン品質をどう高めるか」という視点で見ると、デザイナー個人の意思に依存してしまっているのが現状です。 ただし、レビューは「チェックの門番」ではなく、あくまで品質を高めるための一つの手段だと私たちは考えています。 強制力を持たせるとスピードも落ちてしまい、本来の目的からずれてしまう恐れがあります。 だからこそ、課としてデザイン品質をどう担保していくのか方針を決めて、デザインレビューの位置付けを決めていく必要があります。 最終的には、レビューという時間の意識もなくなり、気軽に相互に聞いて改善する意識が根付いていくとより良いなと感じています。 5. まとめ まだ始めたばかりで試行錯誤の連続です。完璧なレビュー文化は一朝一夕には作れませんが、小さく試して改善していくことを目指しています。 課題もたくさんありますが、それも含めてチームで取り組んでいることが、私たちのチームらしさだと思っています。 こういう文化を大切にしているチームで一緒に働きたい人はぜひお声がけください! career-recruit.rakus.co.jp
こんにちは。40代インフラエンジニアのAと申します。 今回は今さらながら、 Kubernetesを勉強し始めた エンジニアのポエムになります。 あまり技術的な内容はありませんがご容赦ください。 経歴 手作業のインフラの時代 新しい時代の幕開け、そして焦り 重い腰を上げ下げしてようやくKubernetesへ 金はかけるがコスト感をもつ (参考)AWSのマニュアルにあるゲーム(2048)をデプロイしてみる 難しく考えず、実は根本はあまりかわらないよ おわりに 経歴 私はエンジニア歴20年以上の世間では「ベテラン」といわれるインフラエンジニアで 経歴としてはSIerでOSインストールや機器設定を現場でがむしゃらにこなす所から始まり、 SaaS業界で運用を経て、昨今はオンプレ機器の導入・設計をリードする立場で働いてまいりました。 また、現在の会社は在籍年数も10年以上と長く、何やらベテラン臭のする、 「発言ばかりが目立つベテラン社員」に何となく居座っている状況でありました。 手作業のインフラの時代 少し時代をさかのぼりますが、20年前のインフラは手作業と物理作業が当たり前でした。 データセンターで徹夜のサーバチューニング、手動でのシステム再起動、 無駄に長いストレージのビルドを待ちながらの仮眠。 エンジニアのとてつもない長時間労働によって、システムの安定稼働が支えられていた時代です。 しかし、そんな私が長年培ってきた「手作業のインフラの時代」は、 多様な選択肢の中から最適なソリューションを設計することが求められる時代になっていました。 これまで自分が拠り所としてきた経験そのものが、 時代遅れになってしまうのではないかという不安 を抱かせるには十分すぎる変化でした。 新しい時代の幕開け、そして焦り グローバルクラウドが席巻したかと思えばコンテナが時代の旗手となり 世の中はIaC(Infrastructure as Code)の時代の幕開けとなっていました。 作業の自動化、人的ミスのない再現性、バージョン管理、 我々が苦労して手動で支えてきた内容が、 今ではきれいにコードで完結 するようになってきました。 新しく業界に入ってくる若者たちはこれが当たり前かのように周りにある時代、刷新された知識で業務をこなし始めていきます。 そんな中でベテランと名の付く座に安座しているのであれば、 偉そうにこれらの知識も「どや顔」で弁舌しなければメンツを保てないわけで、 ChatGPTとかで非常に表面的に「コンテナのメリットとは?」みたいに調べて知った気になって、 「何となくどこでも動かせるポータブルな技術だよね。お前ら、まだ手動でやってんのか?」 と「どや顔」で言ってはいるものの、 実は自分は何も動かしたことがない虚構の存在になりつつある自分がいました。 そんな自分に嫌気がさし、夜中にふと目が覚めると、「コンテナ 初心者」「IaC やり方」と検索窓に打ち込んでは、溜め息をつく日々。 ただ、物理の世界で生きてきたおじさんにはこのコンテナというものが、 どうにも宙に浮いた、つかみどころのない幻のように感じられたのでした。 重い腰を上げ下げしてようやくKubernetesへ 「石の上にも三年」といいますが、重い腰を上げるのに5年かけてようやくKubernetesを勉強する気になりました。 このきっかけといえば、実は異動になって業務で使うことになったことも大きいですが、何よりも、 自分よりも年上のベテランエンジニアがKubernetesを使っていたことが、大きな精神的な転換点 でした。 若い世代に負けることよりも、自分よりも人生の先輩たちが軽々と新しい技術を使いこなしていることに驚きを覚えたのです。 それは、自分自身が長年の経験に胡坐をかき、新しい時代の変化を傍観していたことへの強烈な否定でした。 いつのまにか、自分の得意なインフラは、主流の座を新しい技術に明け渡し大きく様変わりしていました。 若いエンジニアたちの軽快なフットワークについていけない自分を、心の中では「仕方ない」と割り切ろうとしていたのです。 しかしその先輩の姿は、そんな言い訳を打ち砕きました。 積み重ねた経験からくる自信と、新しい知への探究心がそこには見えました。 その時、私はこのままではいけないなと強く感じたのでした。 重い腰は上がりました。そして、Kubernetesという、今まで手をつけてこなかった新しい世界の扉を叩くことにしました。 別に40代からでも遅くはないのです。今からでも大丈夫。 同じような不安や焦りを抱えている方々にとって、少しでも勇気を与えられる存在になれたら幸いです。 金はかけるがコスト感をもつ 細かい技術の話をしてもあれなので私が今も続けられている勉強方法について記載します。 まずは、多少お金をかけることです。 これはジムと同じ理屈で、無料のサービスだけではどうしても甘えが出てしまいます。 多少でもお金を払うことで、「せっかくお金を払ったんだから、元を取るぞ」という気持ちが生まれるからです。 あと、重い本を何冊も買うよりもマシに思えたからです。 ただし、コスト感は徹底的に意識しています。 無駄な出費は避け、できる限り節約するのが自分のルールです。 そんな中で選んだのが、AWSのマネージドサービスである EKS(Amazon Elastic Kubernetes Service) でした。 グローバルクラウドのサービスに触れられるので、まさに一石二鳥。 EKSの裏側にある難しいコントロールプレーンのことは一旦忘れて、まずはとにかく Deploymentを動かしてみる。 そこに重きを置いて、手を動かして遊んでみることにしました。 ※ちなみに、油断するとすぐに1万円に到達してしまうので、かなりの注意が必要です。 常にコストをモニタリングし、不要なリソースはすぐに削除する癖をつけなければなりません。 手始めにやったのは、「Hello, World」のコンテナを動かすことです。 まずは、シンプルなアプリケーションを動かすための Podと、そのPodを管理する Deploymentを作ってみました。 YAMLファイルにたった数行のコードを書くだけで、いとも簡単に立ち上がる。 その瞬間の感動は今でも忘れられません「おお、動いたわ~・・」。 次に、そのコンテナに外部からアクセスできるように Ingress、Serviceを設定しました。 この一連の流れを体験したことで、これまで漠然と「宙に浮いた話」に感じていたコンテナの世界が、 少しずつ現実のものとして手触り感を持つようになりました。 (参考)AWSのマニュアルにあるゲーム(2048)をデプロイしてみる HelloWorldが動かせたら、次は実際にAWSが公開しているサンプルアプリをデプロイしてみると面白いです。 docs.aws.amazon.com アプリのDeploymentはたったこれだけです。ほかにServiceとIngressはありますが、その十数行でkubernetesで実際にゲームが動きよりリアルなイメージとなってきます。たったそれだけで。 ※読むだけではコードの羅列ですが動かすと世界が変わります apiVersion: apps/v1 kind: Deployment metadata: namespace: game-2048 name: deployment-2048 spec: selector: matchLabels: app.kubernetes.io/name: app-2048 replicas: 5 template: metadata: labels: app.kubernetes.io/name: app-2048 spec: containers: - image: public.ecr.aws/l6m2t8p7/docker-2048:latest imagePullPolicy: Always name: app-2048 ports: - containerPort: 80 resources: requests: cpu: "0.5" このYAMLをapplyすればOKです。 kubectl apply -f 02-deployment.yaml あとはブラウザから確認すればゲームが動きます! 難しく考えず、実は根本はあまりかわらないよ 物理インフラで培った経験は、決して無駄ではありません 。 なぜなら、目に見える物理的な機器から、コードという抽象的な概念に変わっただけで、 サービスを安定稼働させるという本質は何も変わらない からです。 むしろ「ベテラン」は昔ながらの苦労を乗り越えてきたからこそ、新しい技術の効率性や便利さにより深く感動できる。 そう気づいた時、「 時代遅れになってしまうのではないか 」という焦りではなく、新しい知識を手に入れた喜びで満たされていました。 おわりに ということで終始ポエムな記事で物足りなかったかもしれませんが、たまにはこういう記事もありなのかなと。。 現在はGitHubにソースを上げるとGitHub ActionsでECRにイメージが自動で作成されるようになっており、 次はこれ以降のCICDでも作ろうかなと思っています。 わからないことは今やAIにガンガン聞けますし そして何より、 わからないことを恥ずかしがらずに行動することこそが、これからのエンジニアに必要な姿勢 なのだと、あらためて感じています。 もしこの記事が、 少しでも「やってみようかな」と思っている方の背中を押せたなら 、うれしい限りです。
こんにちは!!ラクス技術広報担当です。 2025年8月7日(木)に、オンラインにて「RAKUS Tech Conference 2025」を開催いたしました。平日にもかかわらず、多くの皆様にご参加いただき、大盛況のうちに幕を閉じることができました。登壇者、関係者の皆様、そして何よりご視聴いただいた皆様に、心より感謝申し上げます。 本イベントの開催目的は、私たちラクス開発本部が最も大切にしている 「顧客志向」 という概念を多くの方に知ってもらうことでした。参加後アンケートの結果から、9割の視聴者の方に「ラクスが顧客志向な開発組織であるという印象が伝わった」という回答をいただき、イベントの目的が果たせたことは大変良かったと思います。 各セッションでは、ラクスのサービスが日々どのように顧客と向き合い、その価値を技術で実現しているのか、7つのセッションを通じて開発の裏側にある「強み」や「想い」をお届けしました。 また、クロージングトークではラクス開発本部のAIの取り組みについてご紹介しました。 本記事では、各セッションで語られた内容をダイジェストでご紹介します。 各セッションの紹介 1. 『楽楽電子保存』開発チームが挑む「AI駆動開発」の全貌 2. 数字と感情で語るスクラム導入効果。『楽楽勤怠』開発チームの変革の軌跡 3. 分割と統合で学んだサイロ突破術—『楽楽販売』開発組織10年の軌跡と持続的成長の仕組み 4. 『メールディーラー』へのAI機能実装─”20年”の歴史を持つ製品への導入プロセス 5. 新サービス『楽楽請求』!何を作るかより“なぜ作るか” 顧客価値から逆算する開発現場のリアル 6. なぜ、成熟市場で”売上120%成長”を続けられるのか?『配配メール』の顧客志向型プロダクト開発戦略 7. 『楽楽精算』15年の進化と未来への挑戦 〜経理の”楽”から、すべての働く人の”楽”へ〜 クロージングトーク:AI開発の最前線と未来 おわりに 各セッションの紹介 1. 『楽楽電子保存』開発チームが挑む「AI駆動開発」の全貌 登壇者:楽楽明細開発部 開発2課 小栗 朗、フロントエンド開発課 伊藤 彪我 電子帳簿保存法の改正により市場が3年で3.5倍に拡大する中、『楽楽電子保存』開発チームは、激化する競争を勝ち抜くために「AI駆動開発」による開発プロセス全体の刷新に挑んでいます。 セッションでは、まず設計フェーズにおいて、PMM・PdM・開発者間で分散していたドキュメントをGoogle Docsに一元化し、AIが読み取りやすいよう構造化。AIに設計ドラフトの作成やレビューをさせることで、設計期間を平均30%以上短縮することに成功しました。実装フェーズでは、DevinやGitHub Copilotといった複数のAIツールをタスクに応じて戦略的に使い分け、平均30%の工数削減を実現。さらに、ラクスベトナムとのグローバル開発体制においても、AIによる翻訳やレビュー支援がコミュニケーションコストの削減や業務のボトルネック解消に繋がり、開発力を大きく向上させている事例が紹介されました。 speakerdeck.com 2. 数字と感情で語るスクラム導入効果。『楽楽勤怠』開発チームの変革の軌跡 登壇者:楽楽勤怠開発部 開発1課 加藤 祐也 働き方改革を追い風に市場が成長する一方、圧倒的なマーケットリーダーが不在で競合がひしめく勤怠管理システム市場。後発サービスである『楽楽勤怠』がシェアを拡大するためには、開発スピードとボリュームの向上が必須でした。 そこで白羽の矢が立ったのが、ラクスではまだ主流ではなかった「アジャイル(スクラム)開発」の導入です。本セッションでは、スクラム導入後の具体的な成果が「数字」と「感情」の両面から語られました。数字の面では、リリース案件数が導入後に大幅に増加し、受注件数も過去最高を記録。感情の面では、エンジニアの満足度は10段階中平均8ptと高く、「コミュニケーション頻度が上がった」「チームで助け合いが出来る」といったポジティブな声が多く挙がりました。プロセスの導入だけでなく、失敗を恐れず変化を楽しむマインドの醸成が成功の鍵であることが示されました。 speakerdeck.com 3. 分割と統合で学んだサイロ突破術—『楽楽販売』開発組織10年の軌跡と持続的成長の仕組み 登壇者:楽楽販売開発部 部長 藤井 高志、同 テックリード 山内 覚 年間売上高55億円を超え、SaaS型販売管理システムでシェアNo.1を達成した『楽楽販売』。そのプロダクトの成長を支えてきたのは、10年間にわたる開発組織の変革の歴史でした。 セッションでは、7名体制だった10年前から24名体制の現在に至るまで、組織がどのように変化してきたかが語られました。当初は全員が全ての領域をカバーしていましたが、組織の拡大に伴い、企画、要件、実装といった職能別のチームに分割。しかし、これがチーム間の連携を妨げる「サイロ化」という新たな課題を生み出しました。この課題を乗り越えるため、再び要件から実装、技術負債改善までを一気通貫で担うチームへと統合。日々の接点が多い作業は同一チームで推進すべきであるという学びや、改善の取り組みをチームで推進し、暗黙知を共有する重要性が共有されました。 speakerdeck.com 4. 『メールディーラー』へのAI機能実装─”20年”の歴史を持つ製品への導入プロセス 登壇者:メールディーラーAI開発課 神山 賢太郎、メールディーラー開発課 廣部 知生 2001年に販売開始され、16年連続シェアNo.1を誇る『メールディーラー』。この歴史あるレガシーシステムに、いかにしてAIという新しい技術を迅速に実装したのか、そのプロセスが語られました。 「世はまさにAI戦国時代」 という市場の変化に対し、ビジネスサイドからは「うかうかしているとディスラプトされる」という強い危機感が示される一方、開発サイドではビジネス価値への懐疑的な見方もあり、当初はスピード感にずれがありました。しかし、展示会でAIエージェントへの注目度の高さを目の当たりにし、危機感を共有。ウォーターフォール型開発が主流だった組織の中で、AI開発に特化した課を新設し、完全アジャイル開発にシフト。PMM、PdM、デザイナーが密に連携し、顧客ヒアリングを週1ペースで行いながら高速でPDCAを回すことで、開発スケジュールを大幅に短縮し、価値提供を早めた事例が紹介されました。 speakerdeck.com 5. 新サービス『楽楽請求』!何を作るかより“なぜ作るか” 顧客価値から逆算する開発現場のリアル 登壇者:楽楽請求開発部 開発1課 巽 隆氏、庄禮 有佑 2024年10月にサービスを開始した新サービス『楽楽請求』。ゼロからイチを創り出す過程で直面した、数々の成功と失敗がリアルに語られました。 リリース当初は、ドメイン知識ゼロの状態から書籍や競合サービスの機能分析を基に仮説を立てて開発を進めました(仮説ドリブン)。しかし、リリース後、実業務にそぐわない仕様や、顧客が本当に求めていた機能とのズレが明らかになりました。この経験から、開発プロセスを全面的に見直し、営業やサポートが収集した顧客の声(VoC)を起点とする「VoCドリブン」へと大きく舵を切りました。現在は、2000件を超える要望DBを基に、「何を作るか」よりも「なぜそれが必要なのか」という顧客課題の深掘りを徹底し、顧客が本当に求める価値を提供できる開発体制を築いています。 speakerdeck.com 6. なぜ、成熟市場で”売上120%成長”を続けられるのか?『配配メール』の顧客志向型プロダクト開発戦略 登壇者:ラクスクラウド開発部 配配メール開発課 西尾 敬太 サービス開始から18年目を迎え、国内市場が成熟期にある中で、年間売上成長率120%超を達成し続ける『配配メール』。その力強い成長の源泉は、徹底した「顧客志向型プロダクト開発戦略」にありました。 配配メールは、単なるメール配信ツールから、リード獲得・育成・商談化というマーケティングプロセス全体をカバーするプラットフォームへと進化を遂げてきました。この戦略を支えるのが、「迅速かつ柔軟に価値を届ける」リリース戦略と、「開発者全員が深く顧客を理解する」ための取り組みです。新機能は価値の単位で分割して段階的にリリースし、顧客からのフィードバックを迅速に反映。開発チーム自らが製品を日常的に利用するドッグフーディングや、PMM主催の勉強会を通じて、全部門一体で顧客理解を深める文化を醸成しています。 speakerdeck.com 7. 『楽楽精算』15年の進化と未来への挑戦 〜経理の”楽”から、すべての働く人の”楽”へ〜 登壇者:楽楽精算開発部 開発2課 課長 高波 顕二郎 15年以上の歴史を持ち、導入社数1.9万社を超えるまでに成長した『楽楽精算』。その成長は、電子帳簿保存法やインボイス制度といった法制度ニーズへの迅速な対応によって加速してきました。しかし、その長い歴史は150万行を超えるコードベースや700以上の画面といった技術的負債も生み出し、開発スピードの低下という課題に繋がっていました。 本セッションでは、これまで注力してきた「経理担当者の楽」の実現に加え、今後は経費精算に不慣れな「申請者の楽」も実現すべくUI/UXの根本的な改善に着手していることが語られました。さらに、AIエージェントが自律的に経費精算を行う未来を見据え、レガシーな開発から脱却するための技術戦略として、モダンな新基盤の構築とAI駆動開発を両輪で進める挑戦が紹介されました。 speakerdeck.com クロージングトーク:AI開発の最前線と未来 登壇者: 大阪開発統括部 統括部長 矢成 行雄、東京開発統括部 プロダクト部 副部長 稲垣 剛之、AIエージェント開発課 課長 石田 浩章 イベントの最後には、AI開発を牽引する3名によるクロージングトークが行われました。 ここでは、各プロダクトへのAI機能の実装や、開発本部全体でのAIツール活用など、セッション本編では語りきれなかったテーマについて深掘りしました。 AI開発の組織体制について、ラクスでは単一の部門が集約的に開発するのではなく、各事業部の事情に合わせて多様なスタイルで取り組んでいることが語られました。 例えば、プロダクトマネージャーとデザイナーが所属するプロダクト部では、複数製品を横断する形でAIのロードマップ策定に関与しています。 一方で、新設されたAIエージェント開発課では、事業部直下に開発・営業・CSが一体となったコンパクトなチームを組成し、意思決定のスピードを重視した開発を進めています。 AIの進化に伴いエンジニアに求められるスキルについて、「審美眼」「適応力」「言語化能力」の3つが挙げられました。 AIが生成したアウトプットが本当に正しいかを見抜く力、変化の早い技術や常識を常にアップデートしていく力、そしてAIが理解できる形でコンテキストを的確に伝える力が、今後ますます重要になるとの展望が語られました。 おわりに 改めて、「RAKUS Tech Conference 2025」にご参加いただいた皆様、そしてご協力いただいた関係者の皆様、本当にありがとうございました。 今回のカンファレンスを通じて、ラクスの各プロダクト開発チームが、それぞれの事業フェーズや市場環境の中で、いかに「顧客志向」を貫き、それを実現するために組織や開発プロセスを柔軟に変化させ、新しい技術に挑戦し続けているか、その一端を感じていただけたなら幸いです。 今後もラクスは、「顧客志向のSaaS開発組織」として、お客様の期待を超える価値を提供できるよう、真摯にプロダクト開発と向き合ってまいります。引き続き、ラクス開発本部の活動にご注目ください!