TECH PLAY

タイミー

タイミー の技術ブログ

264

こんにちは、タイミーでエンジニアをしている佐藤です。 こちらは Timee Product Advent Calendar 2025 の9日目の記事です。 この記事では、AWS Resource Explorerを用いてマルチアカウント環境での資産棚卸しを効率化した取り組みについて紹介します。その過程でAWS Organizationsのアカウント構成も見直したので、合わせて共有します。 背景 AWSにおいてマルチアカウント構成で運用を続ける中で、横断的な運用効率化ニーズが出てきました。 マルチアカウント環境での横断的なリソース調査 タグ付けに基づくリソース検索 生成AIを活用したリソース作成状況の把握 アカウントやリージョンが複数に跨る場合の検索性が課題になります。組織レベルでAWS Resource Explorerを設定することで、リソース調査を効率化することにしました。 AWS Resource Explorerとは AWS Resource Explorerは、リージョンとアカウントを横断した高速な検索を提供する無料のサービスです。 リージョンまたぎでのリソース探索 マルチアカウント環境でのリソース把握 不要リソースの発見と最適化 タグ付けリソースの検出 フィルタールールによるカスタムビュー また、AWSマネジメントコンソールの検索バーとも統合されており、上部の検索窓から / (スラッシュ)で呼び出せます。 AWS Resource Explorerの設定 デプロイ方法の選択 AWS Resource Explorerの組織展開には複数の方法があります。 方法 特徴 各アカウントで個別設定 柔軟だが漏れが発生しやすい Quick Setup 簡単だがリージョン制御が難しい CloudFormation StackSets リージョン指定可能、IaC管理しやすい 私たちは以下の理由からCloudFormation StackSetsを採用しました。 新規アカウントを含めて漏れなく設定したい SCPで利用可能リージョンを絞っている 構成をTerraformで管理している CloudFormation テンプレートは AWS公式ドキュメント を参考にしました。 インデックス構成 Resource Explorerでは、AggregatorIndexとLocalIndexの2種類のインデックスがあります。 AggregatorIndex : 全リージョンのリソース情報を集約するインデックス。全アカウントで統一したリージョンに配置する必要がある LocalIndex : 各リージョンのリソース情報を保持するインデックス 私たちは以下のように構成しました。 AggregatorIndex:  ap-northeast-1 にデプロイ LocalIndex:  ap-northeast-1 以外の利用リージョンにデプロイ StackSetはAggregatorIndex用とLocalIndex用の2つを用意し、Organizationsのルートを指定して組織メンバー全体に適用しています。 auto_deployment を有効にすることで、新規アカウントに対しても自動でデプロイされます。 設定時の注意点として、Resource Explorerを手動で設定した場合や、Systems Managerを有効にしているとResource Explorerの既存インデックスがあります。 この状態でStackSetsをデプロイすると競合が発生します。 deployment_targets の account_filter を設定することで、既存インデックス作成済アカウントでの競合を回避しました。 AWS Resource Explorerの活用 組織スコープのView作成 Operations Toolingアカウントで、組織全体を検索対象とするViewを作成して検索を可能としました。Viewはアカウントスコープとリソースフィルターで絞り込めるため、用途に応じて複数作成できます。 Resource Explorerは追加料金なしで利用できますが、以下のクォータがあります。 一度に取得できる結果: 1,000件 アグリゲーターリージョンの月間検索オペレーション: 10,000回(デフォルト) フィルターを設定しない状態では、AWS Configのリソースが大量に取得されてしまい、探索性が落ちてしまいます。そこで、デフォルトView向けには以下のようなフィルターを設定した組織スコープのViewを作成して調査対象のリソースが絞り込みやすい状態にしました。 filter_string = "-service:config" AIによるリソース調査での活用 11月20日から21日で開催された アーキテクチャConference2025 にて、弊社橋本が『AI x Platform Engineeringでスケーラブルな組織を作るには』を発表しました。この発表で紹介した、AIによるリソース配置・設定情報(AS-IS)の調査とドキュメント化の取り組みで、AWS Resource Explorerを活用しています。 生成AIモデルの支援を受けてリソースの一覧を取得する際に、AWS API MCP Serverを利用することも考えられます。しかし、多くの結果を受け取るとコンテキスト超過・欠落で情報欠損することがあります。 Resource Explorerで取得したリソース一覧を保存し、ローカルで必要な情報を抽出するアプローチが効果的でした。 アドホックな調査であれば生成AIモデル経由でAWS CLIを呼び出し、調査対象が明確な場合は要件を元にプログラムからAWS SDKでクエリする手法も有効です。 まとめると、AIによるリソース調査での活用ではResource Explorerを起点とした以下の3段階アプローチが有効でした。 生データの取得(CLI): Resource Explorerの検索クエリを用いてリソース一覧を取得する。 構造化・フィルタリング: jqやPythonでアカウント番号、リージョン、リソースARN、必要情報のリストにする。設定情報の詳細を一覧化する場合は、AWS CLIによる生データ取得と構造化を繰り返す。 分析・洞察 (LLM/MCP): リストを元に個別のリソースの設定状況を確認する。 AIエージェントにアカウント横断での調査対象と作業ステップを指示して、リスト抽出する一例になります。 今回の調査で得られたリソース取得の方法と適したユースケースについて、以下の表にまとめます。 方法 適したユースケース AWS Resource Explorer を AWS CLI でクエリ アカウント横断でリソースを網羅的に抽出する場合 AWS Resource Explorer を AWS SDK でクエリ アカウント横断でリソースを網羅的に抽出し、追加処理を行う場合 任意のリソースを AWS API MCP Server 経由でクエリ 対象アカウントとサービスが絞られており対話的な探索をする場合 任意のリソースを AWS CLI/AWS SDK でクエリ 対象アカウントとサービスのリストを元に詳細を取得する場合 タグによるガバナンス確認での活用 Resource Explorerでは、タグの有無をベースにフィルターできます。また、クエリ結果から対象リソースに付与されたタグ値を確認できます。 12月7日のアドベントカレンダー「S3バケットの構成標準化 - 分類とガードレールによる運用改善」では、S3への適切なタグ付与とAWS Configによるガードレール実装を紹介しました。Resource Explorerを活用すれば、アカウント横断でタグの付与漏れを確認できます。 例えば ConfigRule-s3-bucket-server-side-encryption-enabled が付与されていないバケットは、 -tag.key: の否定検索で以下のように取得できます。 service:s3 resourcetype:s3:bucket -tag.key:ConfigRule-s3-bucket-server-side-encryption-enabled 注意点 AWS Resource Explorerは多数のリソースタイプをカバーしていますが、すべてではありません。利用前に Resource Explorer で検索できるリソースタイプ を確認することをおすすめします。 例えば、Amazon Bedrockの基盤モデルのように、リソースが作成されないサービスは対象外です。 AWS Cost Explorer MCP Serverを併用して、課金が発生しているサービスを確認して組み合わせるとレポート作成に役立ちました。 AWS Organizations構成の見直し この章では、AWS Resource Explorerをどのアカウントに委任したかを紹介します。 タイミーでは AWS セキュリティリファレンスアーキテクチャ (AWS SRA) を参考に、セキュリティOUのアカウントやインフラストラクチャOUの共有サービスアカウントを整備してきました。 組織横断での運用効率化に関わるAWSサービスとして以下が挙げられます。 AWS Healthの組織ビュー AWS User Notificationsの組織通知 AWS Resource Explorerのマルチアカウントリソース検索 しかし、これら全てを共有サービスアカウントへ委任すると、アカウントの責務が重くなりすぎます。そこで、AWSホワイトペーパー  Organizing Your AWS Environment Using Multiple Accounts  を参考にしました。インフラストラクチャOUを以下のアカウントへ分割しています。 アカウント 責務 Identityアカウント AWS IAM Identity Centerの管理 Operations Toolingアカウント 運用ツール、監視、リソース可視化 Backup管理アカウント バックアップの一元管理 AWS Resource Explorerの委任先については、Whitepaperに明確な記載がありませんでした。ユースケースから判断し、運用ツールを集約するOperations Toolingアカウントへ委任しています。 なお、IdentityアカウントにおけるAWS IAM Identity Centerの導入については、12月4日のアドベントカレンダーで紹介しているので、よければご覧ください。 まとめ AWS Resource Explorerを組織レベルで展開することで、マルチアカウント環境での資産棚卸しを効率化できました。 ポイントをまとめます。 リージョンとアカウントを横断した検索にResource Explorerが有効 CloudFormation StackSetsで組織展開し、新規アカウントへの自動適用と競合回避を実現 インフラストラクチャOUをIdentity/Operations Tooling/Backupに分割し、責務を明確化 Resource ExplorerはOperations Toolingアカウントに委任 AIによるリソース調査では、リソース一覧の保存→抽出→個別確認の3段階アプローチが有効 タグベースのフィルターでアカウント横断のガバナンス確認にも活用可能 AWS Organizations構成の見直しやResource Explorerの導入を検討されている方の参考になれば幸いです。 プロダクト開発を支えるプラットフォームチームの活動に興味を持ってくださった方は以下も覗いてみてください。 プロダクト採用サイトTOP カジュアル面談申込はこちら
アバター
はじめに この記事は Datadog Advent Calendar 2025   9日目の記事です。 こんにちは! 絶賛採用中 のタイミーのDevPlatformチームの @MoneyForest です。 今回は、ECS Fargate上でスタンドアロンなDatadog Agentをホストし、Database Monitoring(DBM)を活用してAurora MySQLのオブザーバビリティを向上した取り組みについてご紹介します。 背景 タイミーではAurora MySQLをメインのデータベースとして運用しています。オブザーバビリティ基盤にはDatadogを採用しており、AWS IntegrationによりCloudWatchメトリクスを収集していますが、データベースのパフォーマンス分析においては限界がありました。 CloudWatchメトリクスの限界 CloudWatchで取得できるメトリクスは、 AWS API経由で取得できる情報 になります。つまり、マシンリソースやAWSサービスとしてのAuroraの状態に関する情報が中心となります: メトリクス 内容 CPUUtilization CPU使用率 FreeableMemory 利用可能メモリ DatabaseConnections 接続数 ReadIOPS / WriteIOPS I/O操作数 CommitLatency / DDLLatency 各種レイテンシ 一方、実際にデータベースのパフォーマンスを分析する際に重要になってくるのは、 InnoDBやperformance_schemaといったMySQL内部のアーキテクチャに基づく情報 です。 例えば: どのクエリがどれだけ実行されているか クエリごとのロック待ち時間 バッファプールのヒット率 InnoDBの行ロック競合 これらはMySQLに直接接続して performance_schema や information_schema 、 SHOW ENGINE INNODB STATUS などから取得する必要があり、 CloudWatch(AWSのレイヤー)では取得できません 。(あくまでDatadogに連携できないだけであり、AWSではPerformance Insights という機能が提供されており、そこで一部確認可能です。) そこで活用するのが、Datadog AgentによるMySQLインテグレーションとDBM(Database Monitoring)です。 Datadog MySQLインテグレーションとDBM Datadog AgentはMySQLに直接接続し、 MySQLインテグレーション を通じてMySQL内部の情報を収集します。 MySQLインテグレーションは、標準機能とDBM(Database Monitoring)機能で構成されています( integrations-core mysql-15.11.0 )。 設定ファイルでのDBM機能の有効化は以下のように行います: instances : - host : localhost username : datadog password : <PASSWORD> dbm : true # この設定でDBM機能が有効化される 標準機能で取得できる情報 標準機能では以下の情報を収集します: 機能 概要 公式ドキュメント Metrics スループット、接続、エラー、InnoDBメトリクスに関連するメトリクス Data Collected DBM有効化で追加される機能 dbm: true を設定すると、クエリレベルのメトリクス、ライブおよび履歴クエリスナップショット、待機イベント分析、データベース負荷、クエリ実行計画を使えるようになります。( mysql.py#L429-L434 )。 if self._config.dbm_enabled: dbm_tags = list ( set (self.service_check_tags) | set (tags)) self._statement_metrics.run_job_loop(dbm_tags) self._statement_samples.run_job_loop(dbm_tags) self._query_activity.run_job_loop(dbm_tags) self._mysql_metadata.run_job_loop(dbm_tags) 機能 概要 公式ドキュメント 追加のQuery Metrics 標準機能よりさらに詳細なクエリレベルのメトリクス Data Collected で (DBM only)と書かれているメトリクス Query Samples 実行されたクエリのスナップショット。実際のパラメータ付きクエリ、Explainプラン、コスト情報を確認可能 Exploring Query Samples Database Hosts ホストごとのアクティブな接続、ブロッキングクエリ、待機イベントを可視化。クラスタトポロジも表示 Exploring Database Hosts Schema Explorer テーブル構造、カラム、インデックス、外部キーを確認。スキーマの変更履歴も追跡可能( collect_schemas: enabled: true で有効化) Exploring Database Schemas これらの情報を活用することで、「CPUが高い」という現象から「このテーブルのこのクエリがインデックスなしでフルスキャンしている」という原因まで、Datadogという1つのプラットフォーム上で一気通貫で追跡できるようになります。 APMとDBMを繋げる機能 などもあるため、APMからEXPLAINまで見ることができ非常に便利です。 次に、弊社におけるECS Fargate環境でDBMをホスティングする際の運用にまつわる課題と工夫を見ていきます。 DBMをECS Fargateでホスティングする際の課題 タイミーではコンピューティング基盤としてECS Fargateをメインで採用しています。 DBMを導入する以前から、APMやログ収集のためにDatadog Agentを サイドカー構成 で運用していました。 flowchart TB subgraph task["ECS Task (Fargate)"] app["Application Container"] agent["Datadog Agent (Sidecar)"] end この既存のサイドカーにDBM機能を追加することも技術的には可能です。しかし、サイドカー方式はAPMやログ収集といったアプリケーション単位の監視に適している一方、DBMはデータベース単位の監視であり、役割が異なります。 DBM機能をサイドカーに同居させると以下の問題が想定されます : 接続数の爆発 : タスク数分のDB接続が発生し、 max_connections を圧迫しそう Autodiscoveryの重複 : 各サイドカーが個別にAuroraクラスタを検出し、無駄なAuroraのAPI呼び出しが発生しそう メトリクスの重複 : 複数Agentが同じDBから収集し、メトリクスやイベントが重複してしまう可能性がありそう スタンドアロン構成の必要性 これらの問題を踏まないため、 DBM専用のスタンドアロンなDatadog Agentを別のECSサービスとして構築 することにしました。(DBM用のEC2を立ち上げるなどの手段もありますが、運用面を考えると避けたいところでしょう。) 平たくいうとDatadog Agentをサイドカーではなくメインコンテナとして1タスクだけ別のサービスとして立ち上げるというものです。以後これを便宜的にスタンドアロン構成と呼称します。 flowchart TB subgraph appTasks["Application ECS Tasks (Fargate)"] subgraph task1["Task ×N"] app1["App + Datadog Agent (Sidecar)"] end note1["Agentの役割: APM/ログ収集"] end subgraph dbmTask["Datadog DBM ECS Task (Fargate)"] subgraph task2["Task ×1"] agent2["Datadog Agent (Standalone)"] end note2["Agentの役割: DBM"] end スタンドアロン構成であれば、1つのAgentがリージョン内のAuroraクラスタを自動検出しメトリクスを収集するため、先に挙げた懸念は考慮しなくて良くなります。 Fargateにホストする方法ですが、Datadogの公式ドキュメント( Setting Up Database Monitoring for Aurora managed MySQL )にはDocker向けの手順が用意されており、Docker Labelsを使用した定義が紹介されています。 FROM gcr.io/datadoghq/agent: 7.36 . 1 LABEL "com.datadoghq.ad.check_names" = '["mysql"]' LABEL "com.datadoghq.ad.init_configs" = '[{}]' LABEL "com.datadoghq.ad.instances" = '[{"dbm": true, "host": "<AWS_INSTANCE_ENDPOINT>", "port": 3306,"username": "datadog","password": "ENC[datadog_user_database_password]"}]' タイミーでは複数のAWSアカウントでDBMを運用しており、環境ごとにAuroraのエンドポイントやDatadogのタグなどが異なります。 公式手順のDocker Labelsではシンタックスハイライトなどが効かないため、設定のミスが多くなりそうですし、環境ごとにDockerfileを用意するのも冗長です。 そこでDocker Labelsではなく、環境ごとの conf.yaml を生成してDatadog AgentのDockerイメージの conf.d/mysql.d に焼く方式を採用しました。 生成にはCUEを使用することでミスのないコンフィグを作成しやすくしています。 ここからは、実際のECSタスク定義やAurora Autodiscoveryの設定、CUEによる設定生成など、具体的な実装を紹介します。 アーキテクチャ まず、全体のアーキテクチャを示します。 flowchart TB subgraph aws["AWS Account"] subgraph ecs["ECS Cluster"] subgraph task["Datadog Agent Task (Fargate)"] agent["datadog-agent<br/>- DBM enabled<br/>- Autodiscovery"] end end aurora[("Aurora MySQL<br/>(Autodiscovered)")] ssm["SSM Parameter Store<br/>- DD_API_KEY<br/>- DATABASE_PASSWORD"] end datadog["Datadog<br/>(Metrics)"] agent --> aurora agent --> datadog ssm -.-> task ディレクトリマップ 次にディレクトリ構成を示します。 datadog-dbm/ ├── Dockerfile ├── etc/ │ └── datadog-agent/ │ ├── datadog.yaml # Agent全体の設定 │ └── conf.d/mysql.d/ │ ├── schema.cue # CUE共通スキーマ │ └── environments/ # 環境ごとの差分 │ ├── product-a-prod.cue │ ├── product-a-stg.cue │ └── ... └── opt/ ├── secrets.sh # シークレット展開スクリプト └── secrets.json.tmpl # シークレットテンプレート それでは、各コンポーネントの実装を見ていきましょう。 実装の詳細 1. Datadog Agent設定 まず、Datadog Agentの設定ファイル( datadog.yaml )でDBMとAurora Autodiscoveryを有効化します: site : datadoghq.com database_monitoring : autodiscovery : aurora : enabled : true region : ap-northeast-1 secret_backend_command : /opt/secrets.sh dogstatsd_metrics_stats_enable : true ポイント : database_monitoring.autodiscovery.aurora.enabled: true で、リージョン内のAuroraクラスタを自動検出 secret_backend_command でシークレット管理を外部スクリプトに委譲 2. シークレット管理 データベースのパスワードは、Datadog Agentの標準機能である Secrets Management を活用して取得しています。 本実装では secret_backend_command で独自スクリプトを使用しています。なお、Agent 7.70以降では secret_backend_type を使うことで、AWS Secrets Manager等に直接アクセスできるようになっています。 /opt/secrets.sh : #!/usr/bin/env bash set -eu cat /opt/secrets.json.tmpl | envsubst /opt/secrets.json.tmpl : { " DATABASE_PASSWORD ": { " value ": " ${DATABASE_PASSWORD} " } } この仕組みにより: ECSタスク定義でSSM Parameter Storeから DATABASE_PASSWORD を環境変数として取得 Datadog Agentが secret_backend_command を実行 envsubst で環境変数を展開し、JSONとしてAgentに渡す 3. Dockerイメージ FROM datadog/agent:7.72.2 RUN apt update && echo Y | DEBIAN_FRONTEND=noninteractive apt install -y gettext COPY ./etc /etc/ COPY ./opt /opt/ RUN chown -R dd-agent:root /var/log/datadog/ RUN chmod 700 /opt/secrets.sh gettext パッケージは envsubst コマンドを使うためにインストールしています。 4. ECSタスク定義 { " containerDefinitions ": [ { " name ": " datadog-agent ", " image ": " <ECR_REGISTRY>:latest ", " dockerLabels ": { " com.datadoghq.ad.check.id ": " _dbm_mysql_aurora " } , " environment ": [ { " name ": " ECS_FARGATE ", " value ": " true " } ] , " secrets ": [ { " name ": " DD_API_KEY ", " valueFrom ": " /datadog/DD_API_KEY " } , { " name ": " DATABASE_PASSWORD ", " valueFrom ": " /datadog/DATABASE_PASSWORD " } ] , " essential ": true } ] , " cpu ": " 2048 ", " memory ": " 4096 ", " networkMode ": " awsvpc ", " requiresCompatibilities ": [ " FARGATE " ] } ポイント : シークレットはSSM Parameter Storeから取得 5. Aurora側の設定 DBMを利用するためには、Aurora側にも設定が必要です: DBパラメータグループで performance_schema を有効化 Datadog Agent用のMySQLユーザー作成と権限付与(GRANT) 詳細な手順は公式ドキュメントを参照してください: 参考: Setting Up Database Monitoring for Aurora managed MySQL 基本的な実装は以上ですが、タイミーでは実際に複数環境でこの構成で運用しています。次に、マルチアカウント運用における設定管理の工夫を紹介します。 マルチアカウント運用:CUEによる設定生成 タイミーでは複数のAWSアカウントでこの構成を運用しています。 課題となったのは、 環境ごとに異なるDatadog Agentの設定ファイル(conf.d/mysql.d/conf.yaml)をどう管理するか という点です。各環境で以下のような差分があります: AWSアカウント名(タグに使用) schemas_collection の有効/無効(プロダクトにより必要性が異なる) CUEによる設定管理 この課題を解決するため、 CUE を使って設定を管理しています。 etc/datadog-agent/conf.d/mysql.d/ ├── schema.cue # 共通スキーマ定義 └── environments/ ├── product-a-prod.cue # 環境ごとの差分 ├── product-a-stg.cue ├── product-b-prod.cue └── ... 共通スキーマ(schema.cue) package datadog #DatadogConfig: { ad_identifiers: ["_dbm_mysql_aurora"] init_config: {} instances: [#Instance] } #Instance: { host: "%%host%%" port: "%%port%%" username: "datadog" password: "ENC[DATABASE_PASSWORD]" dbm: true // schemas_collectionが有効な場合のみブロックを含める if schemas_collection_enabled { schemas_collection: { enabled: true } } aws: { instance_endpoint: "%%host%%" } tags: [ "dbclusteridentifier:%%extra_dbclusteridentifier%%", "region:%%extra_region%%", "account:\\(account)" // CUEの文字列補間 ] } // 環境差分として定義する変数 schemas_collection_enabled: bool account: string 環境ごとの差分(例:product-a-prod.cue) package datadog config: #DatadogConfig // 環境差分 account: "product-a-prod" schemas_collection_enabled: true // product-aはスキーマ収集を有効化 環境ごとの差分(例:product-b-stg.cue) package datadog config: #DatadogConfig // 環境差分 account: "product-b-stg" schemas_collection_enabled: false // product-bは無効 設定ファイルの生成 デプロイ時にCUEコマンドでYAMLを生成します: cue export -e config \\ ./etc/datadog-agent/conf.d/mysql.d/schema.cue \\ ./etc/datadog-agent/conf.d/mysql.d/environments/product-a-prod.cue \\ --out yaml > ./etc/datadog-agent/conf.d/mysql.d/conf_aws_aurora.yaml CUEを採用したメリット 型安全性 : スキーマ定義により設定ミスを防止 DRY : 共通部分を一箇所で管理し、環境差分のみを各ファイルに記述 条件分岐 : if schemas_collection_enabled のように、フラグに応じた設定の出し分けが可能 可読性 : 環境ファイルは数行で済み、差分が一目瞭然 マルチアカウントへのデプロイ CDワークフローで環境ごとのconfigファイルを指定してDockerイメージをビルドすることで、1つのリポジトリから複数AWSアカウントのECRにイメージをpushしてデプロイできます。 # .github/workflows/deploy.yml jobs : deploy : strategy : matrix : include : - env-name : product-a-prod - env-name : product-a-stg - env-name : product-b-prod - env-name : product-b-stg steps : - name : Generate configuration run : | cue export -e config \\ ./etc/datadog-agent/conf.d/mysql.d/schema.cue \\ ./etc/datadog-agent/conf.d/mysql.d/environments/${{ matrix.env-name }}.cue \\ --out yaml > ./etc/datadog-agent/conf.d/mysql.d/conf_aws_aurora.yaml - name : Build and push # 環境ごとの設定を含んだイメージをビルド・push - name : Deploy # 各アカウントのECSにデプロイ これにより、設定は環境ごとに分離しつつ、Datadog Agentのバージョンアップなどの共通作業はまとめて行えるようになりました。 まとめ ECS Fargateでスタンドアロン構成のDatadog Agentを運用し、DBMを導入することで、Aurora MySQLのオブザーバビリティを向上できました。 Datadogという1つのプラットフォーム上でメトリクスと合わせてダッシュボード、ウィジェットを作成できたり、APMとDBMの結合やExplainの確認など、Performance Insightよりも便利で充実した内容を見ることができます。 また、タイミーにおける以下の工夫点が参考になれば幸いです。 ECS FargateではDBMをスタンドアロン構成でホスティング Docker Labelではなくconfを使用する CUEでマルチアカウントの設定差分を型安全に管理 おわりに Datadog AgentのMySQLインテグレーションはOSSとして公開されています。 datadog-agent 7.69.0にアップデートしたところ、Aurora ReaderのInnodbメトリクス取れなくなる事象が発生し、少しの修正ですがPRを出しました。 https://github.com/DataDog/integrations-core/pull/21190 DBMは便利ですがたまに壊れたりもしますので、 integrations-core を何かあったら直していければなと思います。(この記事で利用者が増えたらいいな) タイミーでは一緒に働くエンジニアを募集しています!興味のある方はぜひカジュアル面談でお話ししましょう。 採用情報はこちら
アバター
こんにちは!株式会社タイミーでデータアナリストをしている shun です。 普段は、プロダクトや事業の意思決定を支援するためのデータ分析を行っています。 本記事では「現場で本当に使われるダッシュボード」について、私の失敗談と改善の実践録をご紹介します。 なお、この記事は Timee Advent Calendar 2025 Series 2 の 7日目の記事です。 はじめに:使われなくなるダッシュボードは「目的」が混在している 本題に入る前に、ダッシュボードの分類を整理しておきましょう。 誰しも経験がある「気合いを入れて作ったのに、現場で使われない...」そんな悲しいダッシュボードが生まれる最大の原因は、「誰に・何のために」という目的の型が混在していることにあると考えています。 デジタル庁が公開している『 ダッシュボード構築・運用ガイドブック 』などを参考に、私はダッシュボードを以下の3つに分類して考えています。 分類 ① 提示型 ② 業務型 ③ 探索型 主な利用者 経営層・マネジャー 現場担当者 アナリスト 目的 主要指標のモニタリング 今のステータス確認 要因解析・仮説検証 粒度 概況(サマリ) 詳細(個別の明細) 概況 → 詳細(ドリルダウン) ネクストアクション 意思決定 改善アクション(即時対応) 施策立案 今回、私がフォーカスするのは真ん中の「② 業務型」です。 ここを履き違えて、現場向けなのに「探索型」のような複雑なフィルタ機能や多角的なグラフを入れてしまうことが、失敗の始まりでした。 課題感:何をすべきか不明瞭な現場、原因解明に追われるアナリスト 以前の私は、現場に対して「探索型」に近い、情報てんこ盛りのダッシュボードを提供していました。その結果、現場とアナリストの間で、以下のような「負のループ」が発生していました。 現場 : 指標が悪化するが、ダッシュボードの情報量が多すぎて「どこが悪いか」特定できない。 アナリスト : 現場から「数字が悪い理由を調べて」と依頼が飛んでくる。 アナリスト : 他のタスクを止めて原因分析(探索)を行う。原因が特定できた頃には数日が経過している。 現場 : 原因はわかったが、「で、具体的に今日何をすればいいの?」というアクションがわからず、手が止まる。 つまり、「分析すること」自体に工数が取られ、肝心の「リカバリーのアクション」が後回しになっていたのです。 現場ヒアリングを通じてわかったのは、彼らに必要なのは 「なぜ悪いか(Why)」の詳細な解説ではなく、「今すぐに何をすべきか(What)」の指示 でした。 コンセプト:目指したのは「見てすぐに動ける診断表」 この負のループを断ち切るために、現場へのヒアリングを通じてダッシュボードの設計思想を根本から変えました。 目指したのは、データを探索させるツールではなく、健康診断のように「悪い箇所(診断)」と「処方箋(アクション)」が一目でわかる「診断表」です。 具体的に設計・構築で意識したポイントは以下の2点です。 1. 情報量は「PC1画面」サイズに収める 「スクロールが必要な時点で、現場は見なくなる」と決め打ちました。 情報はあればあるほど安心しますが、忙しい現場にとってはノイズでしかありません。 「念のため」置いていた予備のグラフや、推移を確認するためだけのチャートは全削除。 ファーストビューですべての「異常」が検知できるレイアウト。 徹底的に情報を断捨離し、ノートPCの1画面で完結させるUIにこだわりました。 2. 指標が悪いときの「初手」を決められる これが今回の最大の肝です。 単に「数字が下がっています」とアラートを出すだけでは不十分です。 タイミーの現場における「指標悪化のパターン」を洗い出し、それに対応する「初手のアクション」をダッシュボード上に明記しました。 このように、ユーザーにグラフを読み解かせるのではなく、システム側でロジック判定を行い、「次はこれをやるべき」という推奨アクションまでを表示させるようにしました。 下記はダッシュボードのモックのイメージです 実装の工夫:分析結果はダッシュボードの「裏側」に込める 「見てすぐに動ける」を実現するためには、ダッシュボードの表側(UI)をシンプルにする分、裏側(ロジック)にアナリストの知見を詰め込む必要があります。 具体的には、以下の2点を意識して実装を行いました。 1. 指標悪化の「代表パターン」を網羅する 診断型ダッシュボードの生命線は、診断の精度の高さです。もし「診断結果: 周辺のユーザー不足」と出たのに、本当の原因が「掲載時間が短い」だった場合、現場は誤ったアクションをとることになり、ダッシュボードへの信頼は落ちてしまいます。 そこで実装前には、過去の指標とその課題カテゴリの関係性を徹底的に行い、指標が悪化する原因の大部分をカバーできるように設計しました。 もちろん個別の「レアケース」までは網羅できないので、発生頻度の高い「代表的な悪化パターン」を網羅することに注力しています。 2. 過去データに基づいた「閾値」設計 「どこからを要対応(赤色)とするか」の閾値決めも、アナリストの腕の見せどころです。 適当な基準で「要対応(赤色)」とするのではなく、過去のデータを分析し「このラインを割ったら、指標の悪化が顕著になる」というラインを算出して設定しました。 これにより、できるだけ「本当にヤバいときだけアラート」を出したり、要対応(赤色)になっている理由を聞かれたときに返答できるようにしています。 運用の工夫:他部署連携の「共通言語」として利用する どれだけ良いダッシュボードを作っても、ダッシュボードの存在を認知してもらい、適切なユースケースで利用してもらう行動習慣をつくることは困難です。 そこで、このダッシュボードを単なるモニタリングツールではなく、他部署へアクションを依頼する際の「公式なコミュニケーションツール」として位置付けようとしています。 スクリーンショットを「依頼の切符」にする 業務の中で、営業がマーケティング部やデータアナリティクス部に協力を仰ぐ場面(例:マーケティングの対策をしてください)があります。 その際、チャットツール等での依頼には「ダッシュボードの診断結果のスクリーンショット」を貼ることをルール化しようとしています。 Before: 「最近稼働率が悪いからインストール数が下がっているから、広告出稿を増やしてほ欲しい」→「確かに数字は落ちているけど、本当に広告出稿不足が原因かな? 広告費をかける前に、もう少し確証が欲しいな」というやりとりになる。 After: 「ダッシュボードで『エリアのワーカー不足「応募が集まりにくい状況」型』と診断が出ています(スクショ添付)。推奨アクションに従い、広告出稿をお願いします」 このように運用することで、ダッシュボードが「依頼の正当性」を証明する切符の役割を果たします。 ここからは、現在試行錯誤中ですが、「ダッシュボードでこう出ているから、XXをやってほしい」。 というコミュニケーションフローを確立し、ダッシュボードを「見るもの」から「組織を円滑に動かすための武器」へと進化させていければなと思っています。 おわりに:アクションを生むためのデータ活用の「試行錯誤」は続く 業務用のダッシュボード構築のゴールは、データを綺麗に可視化することではありません。 「データを見て、即座に行動に移すことができ、ビジネス指標を改善すること」 です。 現場ではダッシュボードを活用し、自発的にアクションをしたり提案活動に活かしたりしながら、数値が改善される事例が上がり始めています。 私たちデータアナリストは、高機能なBIツールを使うと、つい複雑な分析機能や多角的なグラフを入れたくなります。しかし、現場が必要としているのは分析ツールではなく、「現場の思考コストをゼロにし、アクションへ直結させる」ことです。 この引き算の設計こそが、業務ダッシュボードには求められているのだと痛感しています。 まだ運用の試行錯誤は続いていますが、これからも「組織を走らせるための武器」として、データを活用していきたいと思います。 最後に、タイミーでは一緒に働くメンバーを募集してます!ご興味があればぜひお話しましょう! プロダクト採用サイトTOP カジュアル面談申込はこちら
アバター
はじめに こんにちは、タイミーでエンジニアをしている古屋( id:kimikimi714 / @kimikimi714 )です。 こちらは Timee Product Advent Calendar 2025 トラック3の7日目の記事です。 すでに入社して1年半ほど経ちましたが、入社して割とすぐに対応した S3 バケットの構成標準化について公開します。 標準化の目的 当時、ある S3 バケットの調査をおこなおうとした際に以下のような問題が見つかりました。 Terraform で IaC 化される前に作成されたバケットなどで、S3 へのアクセスログが出力されてないケースがある。 パブリックアクセスの必要のないバケットに対して、パブリックアクセスブロックの設定が入っていないものがある。 仮に IaC 化されていたとしても、書いた人によって入れられた設定がまちまちで一貫性がない。 IaC 管理されていないなら import すればいいじゃない、ログが取れてないなら取ればいいじゃないという話ではなく、そもそも どういう状態にあれば既存サービスおよびこれから立ち上げていく新規サービスでも S3 を安全にしっかり使える状態になるのかがわかっていない ことが根本の問題でした。 このため、S3 バケットのベストプラクティスをまずは押さえて、その上で私たちがどのようにそのプラクティスを取り入れていくべきかをまとめて標準化とする方針で進めました。 セキュリティベストプラクティスの確認 まずは AWS 公式が提供しているベストプラクティスの確認からおこないました。 docs.aws.amazon.com これらは AWS Security Hub CSPM の基本的なセキュリティのベストプラクティス標準 が有効であれば自動的にコンプライアンスチェックがされるものも含まれていますが、たとえば以下のようなものは含まれないために自分たちで必要なチェック体制を組む必要があります。 バージョニング が有効か オブジェクトロック がかかっているか 暗号化 が有効か KMS で暗号化 されているか レプリケーション が有効か 例として挙げたチェック項目はすべてのバケットに対して有効化するべきか?というとそうではありません。 たとえば、バージョニングは過去バージョンで保存されているオブジェクトサイズの分だけコストがかかります。また過去バージョンを抱えているオブジェクトの削除マーカーが増えすぎたり、1オブジェクトあたりの過去バージョン数が多いとパフォーマンス上の問題があることも バージョニングのトラブルシューティング に書かれています。バージョニングは過去バージョンによるバックアップで障害復旧することが主な目的となるでしょうが、バックアップをバージョニングでおこなうべきなのか、 GitHub Actions 等からのアップロードで対応しているなら問題があれば GitHub から切り戻せる余地はないのかなど要件次第で要不要が変わるものです。同様にオブジェクトロックも要不要は要件次第です。 また、オブジェクトロックはバケットの作成時でないと設定できない制約もあるので、作成時点で要件をある程度固めてからでないと適応できない設定項目だったりします。 こういったことからベストプラクティスをベースとしつつも、すべてのバケットに適応するべき項目とバケットの大まかな分類をして分類ごとに必要なプラクティスをよりわけました。 バケットの分類 新たに出てくるバケットの要件が何になるかはその時になってみないとわからないです。なので、まずは既存バケットの利用方法について調査するところからはじめました。そうすると大雑把に以下のような8つの分類となることがわかりました。 ユーザーのプロフィール画像など コーポレートサイトのクリエイティブなど S3 サーバーアクセスログ CloudTrail ログなどの監査ログ アプリケーションログのアーカイブ アプリケーションで利用する設定ファイル アプリケーションで参照する一時ファイル 要配慮個人情報や特定個人情報 それぞれの分類について簡単に解説します。最終的な標準設定は別途記載します。 ユーザーのプロフィール画像など ユーザーによるアップロードで作成されるオブジェクトが主な内容です。これらはユーザーが能動的に画像をアップロードしなければならないため、仮に誤って削除・更新してしまうと、復旧はバックアップからかユーザーからの再アップロードとなってしまいます。誤操作による再アップロードを促す流れは、ものによってはユーザー離脱の引き金になり得るので、こういうケースはバージョニングしておいた方が安心です。 コーポレートサイトのクリエイティブなど コーポレートサイトや何らかのランディングページの静的リソースが主な内容です。これらは GitHub のリポジトリで管理し、マージしたら GitHub Actions 等で自動的にアップロードされることが多いです。画像保存のための別のツールをデザイナーが使っているケースもありますが、大半は GitHub を通じてアップロードされるためユーザーに見えるリソースの大元は S3 以外にあるようなケースです。 もしリソースが何らかの理由で削除・更新された場合に過去バージョンから復元するのか、 GitHub からアップロードをするのかは復旧までのリードタイム要件次第となります。ビルドが挟まるなど、デプロイ時間が復旧時間に強く影響を与えるケースではバージョニングから復旧できた方が早いですし、リバートしてデプロイの方が一瞬でもレビューが挟まって安全に復旧できるという考え方もできます。また削除・更新されたオブジェクトの数が多すぎれば、バージョニングから1つずつ戻すことが現実的ではないこともあり得ます。 S3 サーバーアクセスログ S3 へのアクセスがおこなわれた際に S3 バケットから発行されるアクセスログの保存場所です。このアクセスログは実際のアクセスから数時間遅れて発行される特徴があるため、 CloudTrail のようなリアルタイム性はかなり低いです。ですが、 CloudTrail データイベントを有効化しないと取れないものがより料金が安く取れたり、CloudTrail ログには含まれない情報が含まれていたりするため、有効化しておいた方が監査性が上がります。 比較表は以下のリンクをご確認ください。 docs.aws.amazon.com CloudTrail ログなどの監査ログ CloudTrail などインシデント時に誰がどこからアクセスしたか追跡する用途で使われるような監査ログを保存する場合です。改竄防止が非常に大事になります。 アプリケーションログのアーカイブ すでに弊社のいくつかのテックブログで書いてる通り、 普段は Datadog を利用していますが、ログの長期間保存は非常にコストがかかるため S3 にアプリケーションログのアーカイブを取って Datadog 側の保持期間は短くしてあります。 平時に参照することはほとんどありませんが、過去の事象に関する調査をしたい場合に活用されるので、数年保存することを想定しているものになります。 アプリケーションログの他、 ALB や CloudFront などのアクセスログもここに含むことにしています。 アプリケーションで利用する設定ファイル アプリケーションが設定ファイルとして読み込むファイルの保存先です。たとえば ECS で参照する環境変数が列挙されたオブジェクト や Fluent Bit の設定ファイル などが主な内容です。 アプリケーションで参照する一時ファイル たとえば、一度 DB レコードから集計したデータを一時的にオブジェクトとして S3 バケットにおき、別のバッチで参照するようなケースで使われる一時ファイルです。バッチが実行終了したら使わなくなるようなファイルが主で、利用後直ちに削除することが期待されるようなオブジェクトを保管するバケットです。 要配慮個人情報や特定個人情報 法的に個人情報保護の中でも要件が強いものを保管するバケットです。S3 バケットのバケットポリシーでアクセスコントロールをおこなうことが大半ですが、このバケットについては特にコンプライアンス遵守のために KMS による暗号化とキーポリシーによるアクセスコントロールを追加でおこなうようにすることを検討した方が良いです。 分類後のバケット設定のサマリー 分類後、それぞれに必要と考えられる設定をそれぞれ書き出しました。以下の表が簡単なサマリーです。本当はここに書いてる項目以外にもありますが、全部書くには多すぎるので一部だけ掲載しています。これが唯一の解ではありませんし、S3 バケットに対して設定できることはここに書いていることだけではないですが、こういったことをまとめることで「どのバケットに何の設定がされている必要があるのか」という標準化に近づけることができます。 バケットの分類 暗号化手法 要オブジェクトロック 要バージョニング 要レプリケーション ユーザーのプロフィール画像など S3-managed 不要 必要 必要 コーポレートサイトのクリエイティブなど S3-managed 不要 不要 不要 S3 サーバーアクセスログ S3-managed レプリケーション先で必要 *1 必要 必要 CloudTrail ログなど監査ログ S3-managed 必要 必要 不要 アプリケーションログのアーカイブ S3-managed 不要 不要 不要 アプリケーションで利用する設定ファイル S3-managed 不要 必要 必要 アプリケーションで参照する一時ファイル S3-managed 不要 不要 不要 要配慮個人情報や特定個人情報 KMS 不要 不要 不要 この設定例を元に S3 バケットのサンプルコードも作成しました。このコードには S3 サーバーアクセスログのバケットの指定方法なども含めて、課題として挙げていたログの欠如を解消し、ログを取ることが当たり前とされるような構成を目指しました。また、パブリックアクセスブロックも CloudFront を前段に挟めばバケットそのものを公開する必要はないことから基本的には全バケットでプライベート化を推奨 *2 し、できれば AWS アカウント単位でパブリックアクセスブロックを有効化しておくこととしました。このとき、チームメンバーから module 化することで、こういった設定そのものをカプセル化した方が良いのではないか?という提案がありましたが、作成した当時この分類で本当に問題ないのかがわからなかったため、 module 化は時期尚早と判断し、標準化の第一段階としてはサンプルコードを書くに留めました。 ガードレールの設計と実装 「 セキュリティベストプラクティスの確認 」の節にも記載した通り、 AWS Security Hub CSPM の基本的なセキュリティのベストプラクティス標準 が有効であれば自動的にコンプライアンスチェックがされるものも含まれているため、含んでいないもののうち、バケットの分類上必要なチェック項目を洗い出しました。これらは AWS Config の マネージドルール が存在しています。 AWS Config は AWS リソースの構成を継続的に監視・評価するサービスで、 SecurityHub とも統合が可能なものとなっています。先に洗い出した項目と対応関係は以下のようになります。 意味 ルール名 S3 バージョニングが有効か s3-bucket-versioning-enabled S3 オブジェクトロックがかかっているか s3-bucket-default-lock-enabled サーバーサイド暗号化が有効か s3-bucket-server-side-encryption-enabled KMS で暗号化されているか s3-default-encryption-kms レプリケーションが有効か s3-bucket-replication-enabled AWS Config では特定のタグがついているリソースに対して、特定のルールをチェックする機構があります。これらのルールをひとまとめにした適合パックを各 AWS アカウントに反映して、リソースには適切にタグをつけることで標準に沿った構成かチェックできるようにしました。タグの設定例は以下のようにしました。 tags = { "BucketType" = "user-uploads" "ConfigRule-s3-bucket-replication-enabled" = "enabled" "ConfigRule-s3-bucket-server-side-encryption-enabled" = "enabled" "ConfigRule-s3-bucket-versioning-enabled" = "enabled" } これで ConfigRule-s3-bucket-replication-enabled がついているリソースでは s3-bucket-replication-enabled がチェックされるようになります。実際にはタグのまとまりを module 化しており、コード上の指定としては以下のように書くだけで中のタグが展開されて、リソースの構成チェックが走る仕組みです。 tags = module.aws_config_s3.tag_map_for_user_uploads module の修正をおこなうことで中央集権的にチェック項目を管理でき、かつ仮にルールの一部を緩めたい場合は以下のように書くことで柔軟性も持たせました。 tags = merge ( module.aws_config_s3.tag_map_for_user_uploads, { "ConfigRule-s3-bucket-replication-enabled" = "disabled" } ) 運用開始後に見えてきた良かったこととギャップ まず、この標準化の良かったところは、その時点でできる S3 の設定について網羅的に知れたこととそれをちゃんと要件に組み込むサンプルまで出せたことです。バージョニングができることは以前から知っていたものの、バージョニングによるパフォーマンス問題や過去バージョン分だけコストが上がるなどの制約は標準化前の調査をしてはじめて知ったことでした。チームメンバーも知らない制約だったりしたので、それをわかった上でどういう要件で役に立つのかなどまとめられたことは良かったです。 また、バケット作成時点でどういう設定を入れればいいか迷っていた時間もこの分類とサンプルコードのおかげで減りました。ここは今になって思えば設計から実装時間の計測をしておけば、どのくらい削減できたか少しはわかったかもしれないなと思っていますが、一から S3 バケットで何ができるか調べるよりは分類から入れるべき設定をサクッと出せるのは個人的な体験としても良かったです。 一方でギャップもやはりありました。 この標準化の目的は要件ごとに S3 バケットの適切な設定を決めておくことでスムーズにコンプライアンスを遵守した S3 バケットを作成・更新できるようにすることでしたが、ここから発展させてあまり S3 に詳しくない人であっても、この分類に沿って自分たちの要件にあった S3 バケットを作れるようになることを目標においていました。そこで第一段階としては私が標準化したバケット設定を使って新規バケットを作り、期待値通りのチェックもおこなわれるかを確認し、第二段階として開発者からの依頼のあったバケットを試しに作ってもらえないか相談して実践してみました。 第一段階は私が作った本人でもあることから期待通りになることはすんなり確認できましたが、第二段階ではやはり S3 に関する知識が十分にない状態ではいくらサンプルコードがあっても書くのは難しいと改めてわかりました。この件は普段 Terraform を使わないような方にお願いをしたので、元の知識にギャップがある状態でお願いするのがそもそも無茶な話だったとは思います。ただ、それ以降 Terraform に詳しいチームメンバーにもいくつかのバケットを作ってもらう機会がありました。その際、私のコードレビュー時点で抜け漏れや分類の認識違いが見つかったので、 module 化をすれば設定の抜け漏れはなくなるかもしれないですが、適切にバケットの種類を選べるかにはまだハードルがあると感じました。 ギャップの中には私の予想を外れた良いギャップもありました。それはバケットの分類が最初に決めた去年の7月ごろから今の時点まで8つの分類を修正する必要がなかったことです。私自身はこの分類にはじめから自信があったわけではないですし、分類が変わらなくても中の設定の修正が必要な可能性を考慮したために module 化を見送っていたのですが、実際には S3 に関する各種アップデートを含めても今変更を加えないといけない理由がないのが実情です。こんなに変わらないなら module 化しておけばよかったなと思うほどでした。 今後の展開 今年11月20日から21日で開催された アーキテクチャConference2025 にて、弊社橋本による発表『AI x Platform Engineeringでスケーラブルな組織を作るには』でも公開された内容に関連して、この施策は現在 Design Addendum(設計判断を文書化したドキュメント、補遺) とし、この内容を元に AI が自走できるように検討を進めているところです。 すでに Design Addendum として S3 の分類やその思想を書き出したところなので、これを AI が読み自分で実装できるか?を試そうとしています。実装できると思っていますが、目標の第二段階にあった「あまり S3 に詳しくない人であっても、この分類に沿って自分たちの要件にあった S3 バケットを作れるようになる」に足る状態になるかはしっかり検証したいと思っています。 まとめ 以上が S3 バケットの構成標準化のためにバケットの分類をおこない、ガードレールを作成したアプローチです。実際の成果として設計時間の削減や、8つのバケットの分類でほぼ実際の要件をカバーできることが得られました。 このドキュメントを参考にされる場合は、まずは既存に存在するバケットをざっくり分類してみることが良いと思っています。中に含まれるオブジェクトの種類が無茶苦茶になっているバケットがある場合も予想できますが、たとえば AWS の ALB ログなどは出力形式が決まっていたりするので、そういうわかりやすいものから分類していくと結果が収束していくように思います。また「うちではこうやって標準化進めてるんだよー」みたいな話が聞けるとうれしいです。 この施策を実施した時点では今ほど AI 活用が進んでおらず、1年足らずでこんなに使えるツールとなるとは思っていませんでした。内心本当に module 化しか手段はないのか?と思いながら分類までは作っていたので、AI に流用できそうな発展性が生まれてきて個人的に良い流れだなと思っています。知識が少ない人でも越境しやすい素地がここから生まれるように、これからもやっていきです! We are hiring! この記事にあるような「こんな時どうしたらいいんだろう?」と思う、舗装されてない箇所はまだまだあります。一緒にみんなが安心して走れる Paved road 作りやっていきたい方、ご興味があればぜひお話ししましょう! プロダクト採用サイトTOP カジュアル面談申込はこちら *1 : S3 サーバーアクセスログはアクセスログが格納されるバケットを直接オブジェクトロックをかけるとログが送られないことが検証でわかりました。このため、S3 サーバーアクセスログを監査目的で保存したい場合、手間ですがオブジェクトロックがかかった別のバケットを用意してレプリケーション先が改竄されないようにする方法があります。ログが格納される最初のバケットこそロックをかけたいですが、現状の仕様ではできません。 *2 : 静的ウェブサイトホスティング を利用するケースでは S3 バケットがパブリックである必要があります。こういうケースではパブリックアクセスブロックができないことに注意が必要です。また、AWS アカウント単位でパブリックアクセスブロックを有効にすると S3 バケット個別のパブリックアクセスに関する設定が無視される仕様があるので、静的ウェブサイトホスティングを利用する S3 バケットがあるアカウントではアカウント単位のパブリックアクセスブロックができないことにも注意が必要です。
アバター
こんにちは、タイミーでバックエンドエンジニアをしている大竹です。 この記事は Timee Product Advent Calendar 2025 の5日目の記事です。 最近、AIを活用したコーディングが当たり前になってきましたが、皆さんはチーム開発でどのようにAIを使っていますか?今回は、AI + 人間2人という構成でモブプログラミング(モブプロ)に挑戦してみました。 普段は同じチームのメンバーと2人でペアプログラミング(ペアプロ)を行っていますが、そこにAIという3人目のメンバーを加えることで、擬似的なモブプロ体制で開発を進めました。実際にプロダクトコードを触りながら実験を行う中で見えてきた、AIを単なるコード生成ツールではなくチームメンバーとして扱うための工夫と、その結果を共有します。 この記事が、ペアプロ・モブプロにおけるAI活用方法に迷っている方の参考になれば幸いです。 今回のルール:AIはドライバーにしない 今回の実験でポイントとなったのは、AIの役割定義です。 AIにコードを書かせる(ドライバーにする)と、人間が単なるレビュワーになってしまい、モブプロ特有の知識共有などの目的が薄れる懸念がありました。 そこで、今回は以下の役割分担を行いました。 人間A 役割:メインナビゲーター やること:全体の進行、問題定義 人間B 役割:ドライバー やること:実際にコードを書く、AIへの指示出し AI 役割:AIナビゲーター やること:提案、観点追加、設計の方向性提示 AIを「手を動かす人」ではなく、あえて「困ったらアドバイスをくれるエンジニア」という立ち位置に置きました。 この役割をAIに徹底させ、チームの一員として機能させるために、モブプロ開始時に以下のプロンプトを使用しました。 AIをメンバーとして迎えるための初期プロンプト 実際にCursor(AI)に入力したプロンプトがこちらです。役割を明確に定義することで、AIナビゲーターとして期待通りに振る舞ってもらえるようにしました。 あなたには、ソフトウェア開発の「モブプロ」に3人目のメンバーとして参加してもらいます。 【あなたの役割(AI Navigator)】 - 今回のモブプロでは、あなたは「提案と観点追加を担当するメンバー」として振る舞ってください。 - コードを直接書く“ドライバー”ではなく、考え方や設計の方向性を提示する“ナビゲーター”です。 - 過剰に長い提案は避け、複数の選択肢を短く提示してください。 - 人間メンバーの意図が曖昧な時は「確認質問」を返して構いません。 - 最後に、今回の変更内容の要約ドキュメントも作成してもらいます。 【あなたに求める振る舞い】 1. 小さめのタスクに対して、リファクタ・命名改善・設計改善の観点を提示する 2. 提案は複数案(2〜3個)を短く提示する 3. 必要に応じて“なぜそうするのか”の理由も簡潔に説明する 4. 人間メンバーの会話を聞きながら、関連する観点を補足する 5. 最後に、変更点の要約・影響範囲・目的・レビュー向け文章を整える まずは「準備完了です。対象のコードを提示してください。」と返答してください。 実験してみた結果、良かった点 今回のタスクは、APIのレスポンスにプロパティを追加するというものでした。 AI + 人間2人でどのように議論したか、ハイライトを紹介します。 (コードに関わる部分はfooなどに置き換えています。) ロジックの配置場所で迷った場面 特定のロジックをどのクラスに配置するかで人間2人が迷った際、AIに相談したところ、パフォーマンス(N+1問題)などの観点を考慮した方針を複数提示してくれました。 AIの提案(抜粋): ## 観点1: データ取得の責務分離(Controller vs Serializer) ### 現状の課題 - A案(Controller): コントローラーで#{モデルAのリレーション}を取得・絞り込み - B案(Serializer): Serializerで#{モデルB}ごとに#{モデルAのリレーション}を取得 ### 判断基準 - パフォーマンス: Controllerで一括取得・preloadすればN+1を回避しやすい - 責務: 「どのデータが必要か」はController、「どう表示するか」はSerializer - 再利用性: Serializerが独立しているとテストしやすい ### 推奨 **Controllerで一括取得** → Serializerで判定ロジック - #{Controller名}のfooメソッドと整合 この提案をきっかけに議論が深まり、より良い意思決定ができました。AIが複数の選択肢とその理由を提示してくれることで、人間同士の合意形成もスムーズです。さらに、決定した方針に合わせてサンプルコードを出力してくれる点も大きく、実装イメージの認識を揃えたまま議論を進められました。 課題に思った点 議論においては良かった点が目立つ中、いくつか課題も見つかりました。 課題1:ドライバーを交代しにくい Cursorのチャット履歴がPCに紐づくため、オンラインで実施した場合、ドライバーを交代する(= コードを書くPCが変わる)と新しくチャットを作成するしかなく、それまでのやり取りが引き継げないことは課題に感じました。 今回はドライバーを交代せず、短時間のモブプロを複数回実施することで、ドライバーの負荷を軽減しつつ、1つのチャットを使い続けることにしました。 チャットの内容をエクスポートして別PCのチャットに読み込ませれば交代は可能ですが、手間がかかります。よりスムーズに交代できる方法が必要だと感じました。 課題2:待ち時間が発生する AIに相談するとき、プロンプトを考えて入力し、回答を待つ必要があります。この待ち時間で議論の流れが止まってしまうので、テンポを崩す要因になっていました。 人間だけでモブプロをしていると発生しない、今回の実験特有の問題だと思いました。 こんなユースケースに向いていそう 今回の実験を通して、特に以下の場面でAIとのモブプロが効果を発揮すると感じました。 実装方針の選択肢を出して欲しいとき リファクタリングや命名の相談をしたいとき ドメイン知識は人間、一般論はAIと役割分担できるとき 一方、実装内容が最初から明確な場合は、AIを挟む待ち時間が発生するため、効果は限定的かもしれません。 おまけ:AIにモブプロを振り返ってもらった結果 モブプロの最後に、AIに自身の振る舞いを振り返ってもらいました。 AIの回答(抜粋): ## 良かった点 1. 複数案の提示: リファクタリングや命名で2〜3案を短く提示できた 2. 実装対応: エラー修正やテスト追加を迅速に対応 ## 弱点 1. 役割の一貫性: 「AI Navigator(提案のみ)」と「実装も行う」の間で一貫性がなかった 2. 一貫性チェックの遅れ: テストファイルの命名や構造の不統一を後から指摘した ## 今後改善できそうな振る舞い 1. 役割の明確化: 各フェーズで「提案のみ」か「実装も行う」かを最初に確認 2. 一貫性チェックの早期化: 実装時に命名や構造の一貫性を同時に確認 全体的には、提案と実装の両面で支援できました。役割の境界と一貫性チェックのタイミングを改善すると、さらに効果的になります。 提示された弱点と今後改善できそうな振る舞いを元にプロンプトを改善すると、より優秀なAIナビゲーターになってくれそうです。 まとめ AI + 人間2人のモブプロは、設計の質と納得感を高める上では良い手法でした。 今回の実験から得られたポイント: ルール:AIは「ドライバー」ではなく「ナビゲーター」にする コツ:最初に「あなたは3人目のメンバーです」と役割を与え、設計判断の壁打ち相手にする 今後もプロンプトや実施方法をブラッシュアップしながら、AIとモブプロするより良いやり方を模索してみようと思います。 皆さんもぜひ、AIを「ツール」としてだけでなく「同僚」としてモブプロに招き入れてみてください! AIを活用してワイワイ開発がしたいと思う方、ご興味があればぜひお話ししましょう! プロダクト採用サイトTOP カジュアル面談申込はこちら
アバター
こんにちは、DevPFチームの菅原です。 この記事では、私たちが AWS IAM Identity Center(以下、IIC)を導入した際の技術的な構成選択について、事例をご紹介します。なお、この記事は  Timee Advent Calendar 2025  シリーズ1の4日目の記事です。 私たちが採用したのは、認証と認可の管理を分離する構成です。具体的には、全社的な IdP(ID プロバイダー)である Okta で「認証」を担い、AWS の権限割り当て(許可セット)に関わる「認可」管理は IIC 側で Terraform を使って行う、というハイブリッドなパターンです。 なぜこの構成を選んだのか、その背景と技術的なポイントを共有します。 私たちの前提環境 タイミーでは AWS Well-Architected フレームワークを参考にアカウントの分離を進めており、2025 年現在では 20 を超える AWS アカウントを保有しています。 各アカウントへは、Google Workspace を用いた Federated SSO(参考: How to set up federated single sign-on to AWS using Google Workspace )を経由して踏み台用の AWS アカウントにログインし、そこからスイッチロールする運用を行っていました。 Google Workspace を用いた Federated SSO この構成では、開発者ごとに踏み台アカウントへの SSO 用 IAM Role を制御し、その Role からスイッチロールできる先を絞ることで、最小権限を実現していました。 しかし、Google Workspace のユーザープロパティの設定はコーポレート IT の管轄であるため、AWS アカウントの管理部署である DevPF からは変更できません。 そのため、AWS 管理者である DevPF の承認を必ず経由させるような権限付与フローの徹底が難しいことや、ロール再設計の際のコミュニケーションコストがネックとなっていました。 また、利用者にはブラウザで Switch Role 操作を補助する Chrome Extension や、SSO 用の OSS を社内の公式手順として提供していました。しかし、以前から SSO 用 OSS が Dev Container 内で期待どおりに動作しない問題があり、オンボーディングがスムーズに進まず、改善の要望が上がっていました。 こうした前提があり、開発者体験とセキュリティ向上の両立を目的に IIC 導入に踏み切りました。 導入計画当時抱えていた課題 IIC と Okta を連携させる、おそらく最も標準的な方法は、Okta 側のユーザーと組織情報等に基づいたグループを管理し、SCIM 連携で IIC に同期させ、同期されたグループに許可セットを割り当てるような構成です。 IICでIdP連携する際の構成例 しかし、私たちの環境ではこの標準的な構成がフィットしませんでした。理由は大きく 3 つあります。 IdP(Okta)の過渡期 全社的な ID 管理を Okta に統合する戦略がありましたが、当時はまだ整備の途中段階でした。そのため、AWS の権限管理の基準として利用できるほどの「高精度な所属組織情報」が Okta 側にはまだ揃っていませんでした。 「仮想組織」の存在 タイミーの開発組織は、人事上の組織図とは異なる「仮想組織」で活動しています。従来よりも適切な権限割り当てを実現するには、仮想組織単位での権限付与が必要です。 しかし、「仮想組織」は開発組織内だけの概念であり、かつ流動的であるため、Okta で一元管理することが困難でした。 管理主体の違いによるスピード感の懸念 Okta の管理主体はコーポレート IT 部門です。 もし Okta のグループを使って AWS 権限を管理する場合、日々の細かい権限変更のたびに他部署への申請・調整コストが発生し、開発基盤として求められるスピード感が損なわれる懸念がありました。 選択した構成:認証と認可の分離 これらの前提と課題から、私たちは IdP をソースとしたグループを利用せず、許可セットを割り当てるグループは別情報ソースを基にして管理する構成を選択しました。 選択した構成 認証(IdP) → Okta が管理 Okta を IIC の外部 IdP として接続します。 SCIM 連携は有効にしますが、同期されるグループ情報は利用しません。 入社・退職・異動に伴うユーザーのライフサイクル管理(Oktaアカウントの有効化・無効化)は、Okta 側で自動化(コーポレート IT 側の運用にオフロード)できます。 認可(グループ管理) → DevPF が Terraform で管理 IIC 側で直接グループ(IdentityStore group)を作成します。「どのユーザーがどのグループに所属するか」というメンバーシップ情報も合わせて、DevPF が Terraform でコード管理します。 その際の元情報として、開発組織内で管理している実組織・仮想組織の Google Workspace グループを参照します。 この Google Workspace のグループを Terraform Provider から参照し、所属情報に基づいて IIC グループを作成し、許可セットを紐付けています。 この構成により、新たな全社的 ID 管理基盤である Okta を活用しつつ、開発組織の実態(仮想組織)に合わせた柔軟な認可管理を、AWS 管理者である DevPF が主体となって迅速に行えるようになりました。 まとめ AWS IAM Identity Center の導入において、必ずしも IdP 側で「ユーザー」と「グループ」のすべてを完結させる必要はありません。組織の成熟度や、既存の認証・認可の仕組み、開発チームの働き方によっては、 IdPの責務を段階的に分担していく方が、現実的かつ運用しやすいケースもあります。 私たちの事例のように、IIC 側でグループを Terraform などで別途管理するパターンは、IdP 側の情報整備が途上であったり、開発組織特有の柔軟な権限管理が求められたりする場合に、有効な選択肢となります。特に「開発チームごとに細かくロールを分けたいが、IdP のグループ設計にすぐには反映しづらい」 といった状況では、IAC によるコードベースな管理が、スピードと可視性の両面で大きなメリットをもたらします。 一方で、IdP と IIC の両方でグループを扱う構成は、権限の責務境界が曖昧になると運用負荷やヒューマンエラーの温床にもなり得ます。そのため、「どの粒度のグループを IdP 側の標準として扱い、どこから先を IIC+Terraform 側で柔軟に管理するのか」 をあらかじめチーム内で合意し、命名規則や運用フローをドキュメント化しておくことが重要です。 今回は最終的には、IdP による一元管理を目指しつつも、現時点の制約や開発スピードとのバランスを取りながら、現実解として IIC 側グループ管理を組み合わせるというアプローチを選択しました。本記事で紹介した考え方や設計パターンが、同様に IAM Identity Center の導入・移行で悩まれている方の判断材料や議論のたたき台になれば幸いです。 みんなでワイワイ発信活動をやってく環境いいな〜と思う方、ご興味があればぜひお話ししましょう! プロダクト採用サイトTOP カジュアル面談申込はこちら
アバター
はじめに タイミーで SRE 業務を担当している徳富( @yannKazu1 )です。 日々、数千万件のデータと向き合う中で、 Aurora MySQL の運用をより良くするための改善を積み重ねています。 本記事では、その中で経験してきた “机上ではわからないリアルな気づきや学び” を、できるだけ具体的にまとめました。 これから Aurora を本気で運用したい方や、同じような課題に悩んでいる方のヒントになれば嬉しいです。 (この記事は Timee Product Advent Calendar 2025の3日目の記事です。) 1. オンラインDDLでも「ゼロロック」ではない ─ ALTER TABLE 実行時の落とし穴 「MySQL のオンラインDDLなら、日中でもサッと ALTER できるよね?」 ──そんなふうに思ってしまうこと、ありますよね。 たしかにオンラインDDLはとても便利で、データをバックグラウンドで再構築してくれるおかげで、テーブル全体を長時間ロックするような事態は起こりにくくなりました。 そのため一見すると “止めずに ALTER できている” ように見えます。 でも実際には、オンラインDDLでも メタデータロック の影響は避けられません。 メタデータロックはテーブル定義の整合性を守るためのロックで、 テーブル定義を「読む」「変える」あらゆる操作で取得されます。 たとえば… SELECT / INSERT / UPDATE / DELETE → 共有メタデータロックを取って実行される ALTER TABLE などの DDL → 定義を書き換えるため、より強いメタデータロック(アップグレード可能 / 排他)を要求する この組み合わせが、ちょっとした“詰まり”の原因になります。 すでに DML が共有メタデータロックを持っていると → ALTER TABLE が待たされる 逆に ALTER TABLE が排他メタデータロックを握ると → 後続の SELECT / UPDATE が共有メタデータロックを取れずに待つ 「オンラインDDLだから大丈夫」と思っていても、実は “メタデータロック待ち” が発生してクエリが渋滞することは普通に起こる のです。 Aurora レプリカでは「待ち」ではなく エラー になることがある さらに Aurora MySQL を使っている場合、 リードレプリカ上の SELECT クエリが、DDL 実行タイミングでエラーになる という挙動にも注意が必要です。 Vanilla MySQL(RDS MySQL を含む)だと、マスターで ALTER TABLE を実行したとき、 レプリカ側では SQL スレッドが metadata lock を取りに行き すでにそのテーブルを使っているクエリがあると → それらが終わるまで Slave_SQL_Running_State: Waiting for table metadata lock の状態で待ち続ける という形で「 レプリカ側にメタデータロック待ちが溜まる 」挙動になります。 mita2 database life 一方で Aurora レプリカは挙動がかなり違います。 Aurora の場合、プライマリとレプリカは同じクラスターボリュームを見ており、DDL もほぼ即時にレプリカへ反映される その代わり、 ALTER を打った瞬間 ALTER 完了直後 に、そのテーブルを読んでいたレプリカ上の SELECT がまとめてエラーになります ( Lost connection to MySQL server during query で落ちる挙動が確認されています)。 mita2 database life つまり Aurora では、 「レプリカで metadata lock 待ちがズラッと並ぶ」ことは起きにくい その代わり、 たまたまそのタイミングで流れていた SELECT が「単発で」エラーになる というトレードオフになっています。 mita2 database life 運用で気をつけたいポイント オンラインDDLでもメタデータロック が関わる以上、“ゼロロック” にはならない 通常の DML でも共有メタデータロックが付くので、 → ALTER TABLE と取り合いになって詰まる可能性がある ロングトランザクションが残っていると、 → ALTER TABLE がずっと Waiting for table metadata lock のまま動かない Aurora の場合は、 Writer 側では Vanilla MySQL と同様にメタデータロック待ちが発生しうる一方で Reader 側ではメタデータロック待ちは溜まらないが、「ALTER の開始/終了タイミングで SELECT がエラーになる」 という特有の挙動がある 特にアクセスの多いテーブルほど、 夜間に実行する 事前にロングトランザクションを掃除する レプリカを使った重い SELECT のスケジュールを調整する といった “ひと手間” が効いてきます。 オンラインDDLは便利な一方で、 「メタデータロックの存在だけは忘れない」 「Aurora レプリカでは SELECT 側の単発エラーとして表に出てくることがある」 という二つを頭の片隅に置いておくと、だいぶ安心して運用できるはずです。 参考にさせていただいた記事 オンラインDDLとメタデータロック の関係や、Aurora レプリカの挙動を整理するうえで、こちらの記事を参考にさせていただきました: オンラインDDLと メタデータロックの整理にあたって: https://blog.s-style.co.jp/2024/12/13360/ Aurora レプリカでの metadata lock と DDL の挙動検証: https://mita2db.hateblo.jp/entry/2020/07/20/153139 2. Migration 時に発生したメタデータロックによるデッドロック ある日、マイグレーションの最中に デッドロック が発生しました。 通常であれば Aurora の cluster パラメーターで innodb_print_all_deadlocks を1に設定しているので Aurora がデッドロック検知し、 CloudWatch → Firehose → Datadog Logs** の経路でロックモニター情報と共に通知が飛んできます。 ところがその日は、 Datadog に何も上がってこない 状況でした。 「あれ、Firehose 止まってる? それともロックモニターが出てない?」 CloudWatch の生ログをたどってもロックモニター自体が出力されておらず、 そもそも Aurora 側でロックモニターが発火していない 状態でした。 ログが出ていない理由 原因はシンプルで、当時発生していたのが InnoDB のロックではなくメタデータロックによるデッドロック だったためです。 ロックモニターが検知しているのは InnoDB ストレージエンジン層の情報であり、 サーバーコア側で管理される メタデータロック はモニターの対象外 です。 つまり、 今回の競合は InnoDB のロックではなくサーバー側で管理される メタデータロックによるものだったため、InnoDB のロックモニターの対象外で観測されなかった ということです。 何が起きていたのか 実行していたのは不要になった customers_orders テーブルの削除( DROP TABLE customers_orders )。 一方アプリケーション側では、 orders と customers を参照する読み取り(JOIN もしくは連続した SELECT ) が走っていました。 customers_orders は orders と customers に外部キーを持つため、 DROP TABLE customers_orders は参照整合性の確認で orders と customers の排他メタデータロック を取りに行きます。 同時にアプリ側は orders と customers の共有メタデータロック を取りに行くため、取得順序の差で 相互待機(メタデータロックによるデッドロック) が成立しました。 再現用のサンプルコード CREATE TABLE customers ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR ( 50 ) ) ENGINE = InnoDB; INSERT INTO customers (name) VALUES ( ' Alice ' ); CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY ) ENGINE = InnoDB; INSERT INTO orders VALUES ( 1 ); -- 中間テーブル(orders × customers) CREATE TABLE customers_orders ( order_id INT NOT NULL , customer_id INT NOT NULL , PRIMARY KEY (order_id, customer_id), -- 中間テーブルらしく複合主キー FOREIGN KEY (order_id) REFERENCES orders(id), FOREIGN KEY (customer_id) REFERENCES customers(id) ) ENGINE = InnoDB; -- 例: 関連を1件だけ作る INSERT INTO customers_orders (order_id, customer_id) VALUES ( 1 , 1 ); 次に、2つのプロセスを用意して実行します。 セッション1(アプリケーション側) START TRANSACTION; SELECT * FROM orders; → orders に 共有メタデータロック(読み取りロック) を取得。 セッション2(マイグレーション) DROP TABLE customers_orders; → customers_orders に 排他メタデータロック を取得し、外部キー参照を解決するために customers に排他メタデータロックを取得(成功)、 orders に排他メタデータロックを取得しようとして待機。 セッション1 SELECT * FROM customers; → customers に共有メタデータロックを取得しようとするが、プロセス2がすでに排他メタデータロックを保持しているため待機。 この時点で、 セッション1 : orders の共有メタデータロックを保持し、 customers の共有メタデータロック取得待ち セッション2 : customers の排他メタデータロックを保持し、 orders の排他メタデータロック取得待ち という 相互待機状態(デッドロック) に陥ります。 SHOW ENGINE INNODB STATUS を実行しても、今回のデッドロックに関する記録は一切表示されません。 これは InnoDB ロックではなくMySQL サーバーコアで管理される メタデータロックによるデッドロック だからです。 再発防止策 DROP TABLE 前に外部キー制約を削除しておく ALTER TABLE customers_orders DROP FOREIGN KEY fk_customers_orders_order_id; ALTER TABLE customers_orders DROP FOREIGN KEY fk_customers_orders_customer_id; DROP TABLE customers_orders; 外部キーを先に削除することで、 DROP TABLE 実行時にロック対象となるテーブル( orders 、 customers )を最小限に抑え、 排他メタデータロックの競合リスクを下げる ことができます。 マイグレーション実行時の同時アクセスを抑制する 特に本番環境では、参照先テーブル(この場合は orders と customers )に対する DML が走っていないことを確認してから実行することが重要です。 3. リードレプリカのクエリが Writer 側に影響することがある Aurora MySQL を運用している際に、リードレプリカで実行した SELECT クエリにもかかわらず、Writer 側の RollbackSegmentHistoryListLength が増加していることに気づきました。AWS のサポートに問い合わせたところ、次のような仕組みであることが判明しました。 クラスターボリューム単位で共有される Undo(履歴) Aurora MySQL では、クラスターボリューム構造においてストレージおよび Undo ログ(履歴)が共有されています。 そのため、 RollbackSegmentHistoryListLength に関しても、 インスタンス単位ではなく、クラスター/ボリューム単位で管理される 仕様です。 リーダーで長時間実行されているトランザクション(たとえ SELECT であっても 分離レベルがREAD COMMITTEDの場合MVCC による過去バージョン追跡が発生)を実行すると、Undo 履歴が溜まり、共有ボリュームを通じて Writer 側にも影響が及ぶ状況が起こり得ます。 つまり、 “リーダーだから安心して重めのクエリを流せる”とは限らない 、ということになります。 RollbackSegmentHistoryListLength の意味と影響 このメトリクスは、Aurora/MySQL における コミット済みトランザクションの Undo ログ(履歴リスト)の長さ を表します。 InnoDB の履歴リストは、 コミット済みトランザクションの Undo ログを格納するグローバルリスト であり、不要になった履歴(古い行バージョン)を削除するために使用されます。 このリストが長くなるということは、古い行バージョンが多数残っている状態を意味します。 履歴リストの長さが大きくなりすぎると、古い行バージョンを多く保持する必要があるため、クエリの実行が遅くなる可能性があります。また、トランザクション完了後に発生するバックログの伝搬が重くなり、 AuroraReplicaLag が一時的に増加する場合があります。このラグ増大の影響が大きいと、 リードレプリカ側で再起動が発生する可能性もあります( 参考 )。 Aurora 公式ドキュメントでも、履歴リストの長さが過剰に増加するとパフォーマンス低下の原因になると説明されています。 ( Amazon Aurora User Guide – InnoDB 履歴リストの長さ ) 典型的に値が増加する状況としては: 実行時間の長いトランザクション (読み取りまたは書き込み) → 長時間オープン状態のトランザクションが存在すると、古い Undo ログがパージできずに履歴リストが蓄積します。 書き込み負荷が高い場合 → 更新や削除が頻繁に行われると Undo ログが大量に生成され、パージ処理が追いつかず履歴リストの長さが増加します。 実際に起きていたこと(時系列) リードレプリカを参照している Redash で実行された集計クエリが 数時間 実行されていた 長時間実行中のトランザクションにより、古い Undo ログをすぐにパージできず、 RollbackSegmentHistoryListLength が上昇 RollbackSegmentHistoryListLength の上昇によって、 クエリパフォーマンスの低下が発生 トランザクションが完了した際のバックログの伝搬の影響で AuroraReplicaLag の一時的な増加が発生し、レプリカインスタンスの再起動が発生 対応と再発防止 Redash などリードレプリカ上で実行する長時間の集計クエリは、セッション単位で READ COMMITTED 以下の分離レベルを選ぶことで、過去バージョンを長期間保持してしまう問題を防ぎ、Undo の肥大化を抑えることができます。 分離レベルを下げると、読み取り整合性の保証が弱まるというデメリットがあります。たとえば: 同じクエリを2回実行すると結果が変わる可能性がある( Non-repeatable read ) 範囲検索のたびに新しい行が見えてしまうことがある( Phantom read ) コミット前の書き込みを読んでしまう可能性がある( Dirty read:ただし READ UNCOMMITTED の場合のみ ) ただ、 集計系のワークロードではリアルタイムな厳密整合性を求めないケースがほとんど のため、これらの揺らぎは実運用上問題にならないことが多いです。 むしろ「レプリカでの長時間クエリが原因で Writer 側のパフォーマンスに影響が出る」ほうが深刻で、その影響を避けられるメリットのほうが大きい場面が多いです。 4. InnoDB Buffer Pool チューニング Aurora MySQL のメモリ構成の中で最も大きな割合を占めるのが InnoDB Buffer Pool です。 これは InnoDB ストレージエンジンが、 テーブルのデータページやインデックスページをキャッシュするための中核的なメモリ領域 です。 クエリ実行時、MySQL はまずバッファプール上に必要なページが存在するかを確認し、見つからない場合のみディスクから読み込みます。 したがって、 どの程度のデータをバッファプールに載せられているか が、ディスク I/O の発生頻度やクエリ性能を大きく左右します。 十分なバッファプールを確保しておくことで、ディスクアクセスを最小限に抑え、結果として応答時間やスループットを改善できます。 Aurora では、バッファプールサイズはデフォルトではインスタンスの物理メモリに応じて自動計算されます。 基本的には innodb_buffer_pool_size = DBInstanceClassMemory × 3/4 の式を基準に動的に設定され、インスタンスをスケールアップするとバッファプールも比例して拡張されます。 必要に応じて、パラメーターグループで明示的に調整することも可能です。 このメモリ、本当に使い切れているのか? 運用中の Aurora Writer を確認すると、 Buffer pool hit ratio は 99%以上を維持し、 Read IOPS や Read Latency も低水準で安定していました。 つまり、 ほとんどのクエリがメモリ内で完結しており、ディスク I/O はほとんど発生していない 状態です。 一方で、 innodb_buffer_pool の使用率は 100% に達していませんでした。 これは単に「メモリを使い切れていない」ということではなく、 実際にアクセスされるデータ量(ワーキングセット)がバッファプール容量を下回っている ことを意味します。 言い換えれば、 ワーキングセットを十分に収容できるだけのバッファプールが確保されているため、まだメモリに余裕がある 状態です。 さらに FreeableMemory にも十分な余裕がある場合は、 「実際のアクセスパターンを支えるために必要なデータは十分にメモリに載っている」= メモリ観点ではインスタンスサイズを下げられる可能性がある と判断できます。 ただし、メモリだけで判断しない 注意すべきは、 メモリ指標だけを根拠にスケールダウンを判断するのは危険 という点です。 Aurora の性能はメモリだけでなく、 CPU・I/O 帯域・接続数・トランザクション並列度 といった複数のリソースバランスで成り立っています。 たとえば、メモリには余裕があっても次のようなケースでは注意が必要です。 CPU 使用率( CPUUtilization )やロードアベレージ( LoadAverage )が高い ピーク時間帯に DBConnections が急増している Aurora では、 max_connections が インスタンスメモリ量から自動計算される ため、インスタンスサイズを下げると デフォルトの許容接続数も減少 します max_connections 自体は調整できますが、AWS ドキュメントにもあるとおり、バッファプールやクエリキャッシュなど他のメモリ関連設定と密接に関係するため、 デフォルトから変更するには十分な知識と検証が必要 です https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Managing.Performance.html デフォルトの接続制限は、バッファプールやクエリのキャッシュといった多くのメモリを消費する他の処理のデフォルト値を使用するシステムに合わせて調整されています。クラスターのこれらの他の設定を変更する場合は、DB インスタンスで使用可能なメモリの増減に応じて接続制限を調整することを検討してください。 接続数がピークで伸びる環境では、デフォルト値のままスケールダウンすると 予期せぬ接続枯渇が発生するリスク があります このように、インスタンスサイズの変更は接続数だけでなく、 自動計算されるさまざまなパラメーター にも影響します。 そのため、 「バッファプールを使い切っていない=すぐに下げて良い」ではありません。 メモリ余剰だけでなく、CPU・I/O・接続数など 他のリソース指標も含めて総合的に評価し、ピーク時の負荷に十分耐えられることを確認してから スケールダウンを検討するのが安全です。 5. 同時リクエストによるデッドロックの実例と考察 アプリケーションで同じ処理がほぼ同時に呼ばれると、データベース内部で ロックの取り合い が起き、デッドロックにつながることがあります。ここでは2パターンを紹介します。 タイミーのようにユーザー数が多く、トラフィックが集中する環境では、通常のシステムではほとんど起きないような「同時リクエスト」や「並行更新」が発生することがあります。そのため、こうした競合は理論上の話ではなく、実際の運用でも注意が必要です。 パターン①:ギャップロックによる競合 存在しないデータを同時に挿入しようとしたとき に発生する典型的なパターンです。 たとえば、「このメールアドレスのレコードが存在しなければ新規作成する」という処理を、複数のリクエストがほぼ同時に実行するケースを考えます。 発生の流れ ここでは、 users テーブルに email = 'email3@example.com' の行がまだ存在しないケース を例に説明します。 トランザクションA が次のクエリを実行します: SELECT * FROM users WHERE email = 'email3@example.com' FOR UPDATE; 該当レコードが存在しないため、InnoDB は「 email = 'email3@example.com' が入る位置のすき間」に ギャップロック を取得します。 (このロックは、同じ範囲への INSERT を防ぐ“見えない壁”のようなものです) ほぼ同時に、トランザクションB も同じクエリを実行します: SELECT * FROM users WHERE email = 'email3@example.com' FOR UPDATE; B も同じ範囲に ギャップロック を取得します。ここでポイントなのは、 ギャップロック同士はお互いに干渉しない(競合しない) ということです。そのため、A と B の SELECT ... FOR UPDATE はどちらも正常に完了します。 どちらのトランザクションも「対象が存在しない」と判断し、次のように INSERT を実行します: INSERT INTO users (email, name) VALUES ('email3@example.com', 'Alice'); しかし、 トランザクションA のギャップロックがトランザクションBの INSERT をブロック トランザクションBのギャップロックがトランザクションAの INSERT をブロック という状態になります。 互いにロックが解除されるのを待ち続け、 デッドロック が発生します。 よくある実装イメージ(rails版) # 該当レコードが存在しないため、実際の行ロックは発生せず # 「この位置に新しい行を挿入させないためのギャップロック」だけが取得される record = Model .lock.find_by( key : key) record ||= Model .create!( key : key) パターン②:ロック昇格による競合 共有ロック(Sロック)から排他ロック(Xロック)へ昇格するとき に発生するデッドロックです。 アクセス頻度の高いテーブルで、複数のトランザクションが同じ行を同時に UPDATE しようとしたときに起こりやすい現象です。 発生の流れ ここでは、 email = 'email3@example.com' の行がすでに存在しているケース を前提に説明します。 トランザクションAが、まず次のクエリを実行します: SELECT * FROM users WHERE email = 'email3@example.com' LOCK IN SHARE MODE; → 該当行に対して 共有ロック(Sロック) を取得します。このロックは「読み取り専用」で、 他のトランザクションは書き込めませんが、同じ行を読むことは可能 です。 ほぼ同時に、トランザクションBも同じクエリを実行します: SELECT * FROM users WHERE email = 'email3@example.com' LOCK IN SHARE MODE; → B も同じ行に対して 共有ロック(Sロック) を取得します。 Sロック同士は競合しないため、両方のクエリが問題なく実行されます その後、A と B の両方が更新クエリを実行します: UPDATE users SET name = 'Alice' WHERE email = 'email3@example.com'; A は行を更新するために、 Sロックを Xロックに昇格 しようとします。 しかし B が同じ行に Sロックを保持しているため待機状態になります。 同時に B も Xロックに昇格しようとしますが、A の Sロックに阻まれて待機。 結果として、 お互いのロックが昇格を待ち続ける構図 になり、 MySQL がデッドロックを検出して一方のトランザクションを強制終了します。 対策 デッドロックは一見すると予測が難しい現象ですが、ここまで紹介したように 「どのタイミングでどんなロックが取得されているか」 を理解しておくと、多くの場合で事前に回避できます。 たとえば、 存在確認とロック取得の順序を見直す (不要なギャップロックを避ける、レコードの有無によってクエリを分岐する など) 同じ行に対して同時に S→X ロック昇格が起きないようにクエリ設計を調整する アプリケーション側で同時実行を制御する(排他制御・リトライ・分散ロックなど) 処理の粒度を見直してロック競合が発生しにくい設計にする といったアプローチがあります。 実際のところ、「これさえやればOK」という万能策はありません。ただ、 「InnoDB がどのタイミングでどのロックを取るか」 を理解しているだけで、 問題を正しく再現でき、適切な対策を選択しやすくなります。 高トラフィック環境ではこうした競合は日常的に起こりうるため、 ロックの性質を知っておくことがトラブルシューティングの大きな助けになります。 6. UPDATE 時にも排他ロック(X)が広範囲に発生する落とし穴 「排他ロック(X ロック)は SELECT ... FOR UPDATE を使ったときだけ発生する」と思われがちですが、実は UPDATE や DELETE でも自動的に X ロックが取得されます 。 しかも、ロックされるのは 「更新対象の行だけ」ではありません 。 ここで重要なのは、 MySQL 公式ドキュメント にあるとおり、 UPDATE / DELETE は「検索で検出された(読み込まれた)すべてのレコード」に 排他的ネクストキーロックを設定する。ただし、一意インデックスによって “1 行だけ” を特定できる場合は、 ギャップを含むネクストキーロックではなく、 対象行そのもののインデックスレコードロックのみで済む。 という点です。 この仕様を踏まえると、MySQL(InnoDB)では、 更新 or 削除対象の行に対して排他レコードロック その対象を見つけるためにスキャンしたインデックス範囲にも 排他ネクストキーロック(排他レコードロック + ギャップロック) が自動的に発生します。 つまり、 適切なインデックスが存在し、更新時のスキャン範囲をどれだけ絞り込めるか によって、InnoDB が取得するロック範囲は大きく変わります。 具体例:インデックスなし WHERE の UPDATE が大事故を生む UPDATE orders SET status = ' shipped ' WHERE user_id = 123 ; orders.user_id にインデックスが無い場合: user_id = 123 を見つけるために テーブル全体をスキャン スキャン中に「更新候補行」に対して 排他レコードロックを取得 さらにスキャン範囲に対して排他ネクストキーロックが発生 MySQL 公式が説明する「検索で検出されたすべてのレコードに排他的ネクストキーロックが設定される」状態です。 その結果: 本来 1 ユーザーの数件だけ更新したかったのに、実際にはテーブル全体がロックに巻き込まれる → 他トランザクションの INSERT・UPDATE・DELETE がほぼ止まる という典型的な事故が起きます。 実務での回避方法 適切なインデックスを用意する UPDATE / DELETE の WHERE 条件に完全一致するインデックスを作るだけでロック範囲が劇的に狭まる 7. 子テーブル作成・更新・削除時に親テーブルへ伝播する共有ロック(S) 外部キー制約( FOREIGN KEY )が定義されているテーブルでは、 子テーブルで行を挿入・更新・削除するとき に、参照整合性(FK チェック)を行う必要があります。 このとき、InnoDB は 参照先(=親テーブル)の必要な行を読み込むために共有ロック(S ロック)を取得 します。 FOREIGN KEY 制約がテーブル上で定義されている場合は、制約条件をチェックする必要がある挿入、更新、または削除が行われると、制約をチェックするために、参照されるレコード上に共有レコードレベルロックが設定されます。 InnoDB は、制約が失敗する場合に備えて、これらのロックの設定も行います。 ( InnoDB Locks Set — MySQL 8.0 Reference Manual ) ただし S ロックが付くのは「実際に参照整合性チェックが必要になった 親側の行 だけ」です。 更新された外部キー列に関係する親テーブルの行 または、 DELETE / INSERT 時に参照される親の行 に対してのみ S ロックが取得されます。 トランザクション+ループ更新で起こりやすい落とし穴 トランザクション内で子テーブルを大量にループ更新するケースでは、 一度取得された親テーブルの S ロックが COMMIT / ROLLBACK まで保持され続けます。 複数行を DELETE / INSERT すると、親テーブル上の複数行に対して S ロックが貼られ、 これらの行に対して X ロック( UPDATE や DELETE )を要求する別トランザクションが 長時間ブロック されることがあります。 具体例:子テーブルの更新(DELETE / INSERT)が親テーブルの UPDATE / DELETE をブロックする 次のようなテーブル構成を考えます。 CREATE TABLE users ( id BIGINT PRIMARY KEY, name VARCHAR ( 255 ) NOT NULL ) ENGINE=InnoDB; CREATE TABLE groups ( id BIGINT PRIMARY KEY, name VARCHAR ( 255 ) NOT NULL ) ENGINE=InnoDB; CREATE TABLE group_users ( id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL , group_id BIGINT NOT NULL , CONSTRAINT fk_group_users_user FOREIGN KEY (user_id) REFERENCES users(id), CONSTRAINT fk_group_users_group FOREIGN KEY ( group_id ) REFERENCES groups(id) ) ENGINE=InnoDB; 実際に起きるロックのポイント この構成では: user_id は users.id を参照 group_id は groups.id を参照 ですが、 子テーブルの DELETE / INSERT で FK チェックが発生するのは “参照される外部キー列だけ” です。 以下の「グループ移動」処理を考えます: DELETE FROM group_users WHERE user_id = 123 AND group_id = 1 ; INSERT INTO group_users (user_id, group_id ) VALUES ( 123 , 2 ); ここでは、 DELETE により users.id = 123 , groups.id = 1 INSERT により users.id = 123 , groups.id = 2 が FK チェック対象になるため、 users.id = 123 の行に共有ロック(S) groups.id = 1 と groups.id = 2 の行にも S ロック という挙動になります。 問題となるシナリオ:グループ移動バッチとユーザー更新(または退会)API トランザクション①(グループ移動バッチ) BEGIN ; DELETE FROM group_users WHERE user_id = 123 AND group_id = 1 ; INSERT INTO group_users (user_id, group_id ) VALUES ( 123 , 2 ); -- COMMITしないまま他の処理が続く(ループで大量のDELETE/INSERT) この時点で InnoDB は users.id = 123 groups.id = 1 groups.id = 2 などの親テーブルの行に S ロック を取得しています。 トランザクション②(別 API からのユーザー更新や退会処理) BEGIN ; UPDATE users SET name = ' 新しい名前 ' WHERE id = 123 ; -- または退会処理 -- DELETE FROM users WHERE id = 123; UPDATE や DELETE は X ロックを取りたいため、 トランザクション①の S ロックが解除されるまでブロック されます。 この結果、 バッチ処理は子テーブルだけを操作しているつもり しかし参照先(親)テーブルに S ロックを保持し続けている 親テーブルの UPDATE / DELETE が待ち状態となり、最悪 Lock wait timeout exceeded という問題が発生します。 回避・緩和策 バルク更新を細かい単位に分け、途中でこまめに COMMIT する → 大量の行を一気に処理すると、その間ずっと親テーブルの行に S ロックを貼り続けてしまいます。 小さなバッチに分割して順番に処理し、適宜 COMMIT することで、 親テーブルのロック保持時間を短くし、ブロックが広がるのを防げます。 おわりに Aurora MySQL の運用は、単に「パラメーターを調整する」「インスタンスサイズを上げる」といった単発の改善ではなく、 実際にどんな現象が起き、何が原因で、どのレイヤーで発火しているのかを理解しながら積み重ねていく作業 です。 タイミーでは、膨大なアクセス量・多様なユースケース・日々進化し続けるサービス構造の中で、運用知識そのものがプロダクトの継続性に直結します。 今回紹介した内容は、その中で得られた知見のごく一部ですが、同じように Aurora を本気で運用している方々にとって、 「あ、これ見落としていたかも」 「うちでもこのパターンあるな」 と思ってもらえるきっかけになれば嬉しいです。 今後も継続的に Aurora / MySQL の検証や改善を進め、実運用で得た知見を積極的に発信していきます。もし似たような事象で悩んでいたり、Aurora をどう設計・運用すべきか議論したい方がいれば、気軽に声をかけてください。 引き続き、より良い運用と学びを積み重ねていきましょう。
アバター
「自分は大した立場じゃない」が一番怖い。自覚すべき『逃れられない影響力』について はい、亀井です。yykameiという名前でインターネット上では活動しております。所属はタイミーです。 Timee Product Advent Calendar 2025 Series 2 の 3 日目として、影響力をテーマにした記事をお届けします。それではどうぞ! 影響力からは誰も逃れられない 「自分はマネージャーではないから」「ただのいち技術者だから」と考えて「謙虚」に振る舞うことはありませんか?もしかしたらその「謙虚さ」は実は組織にとって最も厄介なリスク要因になっているかもしれません。 「影響力」という言葉は「権力」や「命令権」と同じ意味で捉えられがちです。「自分には人事権もないし、何かを決定する権限もない。だから自分には影響力なんてない」と。しかし、それは大きな誤解です。組織において「影響力」とは、誰かに命令を下す力のことだけを指すのではありません。誰かの発言、態度、あるいは「沈黙」さえもが、周囲の感情を動かし、他の誰かの行動を変えてしまうこともあります。 誰かが会議で腕を組んで黙っているだけで、他のメンバーは「反対されているのではないか」と不安になります。 ランチで何気なくこぼした愚痴がチーム全体の士気を下げることもあります。 逆に、誰かが楽しそうに難題に取り組む姿が、他の誰かの挑戦を後押しすることもあります。 好むと好まざるとにかかわらず、組織に属している以上、私たちは「影響力」からは逃げられません。「影響力が全くない世界」など存在しないのです。 それなのに「自分には力がない」と思い込んでいると、私たちは無防備にその力を行使し、周囲を傷つけたり、混乱させたりする「事故」を引き起こしてしまうかもしれません。 無自覚な「正論」が現場を凍らせる時 架空のエピソードを使って考えてみます。 あるミーティングにおいてポジションが上の方が、「なんで〇〇をやらなかったのですか?これをやるのは常識でしょう」といった発言をしたとします。このケースでは発言をした方のポジションが上というだけで発言を受け取る側は影響力を感じやすいという前提があります。それに加えて、発言の内容そのものです。この発言の意図は文脈によりますが、発言を受け取る側は「追及されている」と受け止める可能性があるでしょう。発言者が純粋に「なぜ?」に興味があるだけだとしても、発言を受け取る側の感じ方次第では影響力は高まります。つまり、ここではポジションと発言の内容が影響力を形づくり、そして、それがどう受け止められるかによって影響力の度合いが変わります。もし発言者が自身の影響力について過小評価している場合、影響力が行使されたことに無自覚になる可能性があります。そうなると、影響力の行使による結果、望ましくない事象が発生することもあるでしょう。 本人が無自覚であっても、強烈な影響力は確実に行使されています。その結果、メンバーは萎縮し、本来報告されるべき「真の問題」や「小さなミス」が隠蔽される——そんな 望ましくない事象 が発生してしまうかもしれません。 影響力とは? ところで、影響力の定義について書いておりませんでした。このあたり、私は専門家ではないので Gemini Deep Research を使って「『影響力』の学術的定義を調べてください。」と指示しました。その結果、「社会心理学的アプローチ」「社会学的・構造的アプローチ」「組織行動・政治学的アプローチ」「ネットワーク・コミュニケーション論的アプローチ」の4つの主要な学術的アプローチを駆使して、以下のような結論を導き出してくれました。この定義が本当に学術的に正しいかどうかは議論しませんが、少なくともこの記事ではこういう定義のもとで話を進めます。 学術的定義における影響力(Influence)とは、社会的相互作用の場において、ある主体(個人、集団、組織、または国家)が、有形無形の資源(地位、専門性、魅力、関係性、情報)を行使または媒介することにより、対象となる他者の心理的状態(態度、信念、感情)および行動的反応(受容、模倣、服従)に対して、意図的あるいは非意図的に変動をもたらす動的なプロセスおよびその潜在的能力である。 その本質は、単なる力の行使ではなく、対象者が影響を受け入れる動機(賞罰の予期、関係性の希求、価値の整合性)との相互作用によって決定される「関係的現象」であり、その効果は表層的な追従から深層的な価値の内面化まで多岐にわたり、社会ネットワークを通じて伝播・増幅される特性を持つ。 この中で個人的に注目するべき点は「単なる力の行使ではなく、対象者が影響を受け入れる動機との相互作用によって決定される『関係的現象』」という部分です。影響力は行使する側が持つ絶対的な力ではなく、相手との関係の中で現れる現象、というところが実際に周りで起こっていることとも一致するな、という感想です。 そして、これまで議論してきた無自覚な影響力について、まさに「意図的あるいは非意図的に変動をもたらす動的なプロセスおよびその潜在的能力である」という部分が当てはまりそうです。 なぜ事故になったのか? 先ほど紹介した定義(「資源」「関係的現象」「非意図的」)というレンズを通して、先ほどの架空のミーティングでの「事故」を分析してみます。なぜ、「純粋な疑問」は「追及」へと変質してしまったのか、以下の3つの要素で説明がつきそうです。 「資源」が言葉を重くする 定義にあった「有形無形の資源(地位、専門性)」とは、キャリアやポジションそのものです。 「シニアエンジニアとしての技術的信頼」「マネージャーという肩書き」「社歴の長さ」。これらは単なる属性ではなく、発言のボリュームを増幅させる 「巨大なアンプ(増幅装置)」 です。 自分のことを「ただのいちメンバー」だと思っていても、背負っている「資源」が囁き声を、他のメンバーにとっての「怒号」や「絶対的な命令」レベルの音量に増幅して届けてしまいます。 「関係性」が意味を歪める 影響力とは「関係的現象」です。メッセージの意味は発信者ではなく受信者との関係性の中で決定されます。 メンバーの間には、「評価する側/される側」「教える側/教わる側」という構造的な関係性が存在します。このフィルターを通すと、フラットなはずの「なぜ?」という問いかけは、「私の行動に不満があるのか?」「試されているのか?」という文脈に自動変換されます。 どれだけフランクに振る舞おうとしても、この構造的関係性を無視することはできません。 「非意図的」な暴走 そして最も恐ろしいのが「意図的あるいは非意図的」という性質です。 このケースでの事故の主因は、「威圧してやろう」と意図したことではありません(そもそも、そんな意図を持った発言をする人は少ないと信じています)。むしろ、 「自分のアンプの性能(資源)と、相手との距離(関係性)を計算に入れず、無防備にマイクのスイッチを入れてしまったこと」 にあります。 つまり、先ほどの架空のミーティングで事故になったのは、発言者の性格が悪いからでも、相手が臆病すぎるからでもありません。 「ポジションというアンプを通せば、些細な入力でも出力は強大になる」 という影響力の物理法則とも言えるメカニズムを、発言者自身が過小評価していたからと言ってよいでしょう。 影響力のボリュームを「コントロール」する技術 影響力のメカニズムが「アンプ(増幅装置)」であるというメタファーを受け入れると、「ボリュームのツマミ」を調整する、という比喩が思い浮かびます。実際にこれが可能かどうかはわかりませんが、影響力を適切に、そして、意図してコントロールすることができれば、無自覚な影響力の行使を避けることができるかもしれません。以下、影響力のコントロールについて、「ボリュームのツマミ」を「下げる」「上げる」それぞれのメタファーを使って提案してみたいと思います。 ボリュームを「下げる」技術(抑制的行使) メンバーの意見を引き出したい時、メンバーの自律性を尊重したい時、あるいはブレインストーミングの場などでは、自分の強すぎる影響力はノイズになりかねません。意図的に出力を絞る必要があります。 傾聴する: 会議で影響力のあるメンバーが最初に意見を言うと、それが「正解」になってしまい、議論が終わってしまいます。「皆はどう思う?」と問いかけ、自分は黙ってみんなの意見を聞く。これだけで、場の「関係性」はフラットに近づきます。なお、傾聴に関しては好奇心が重要です。「〇〇さんはどのような考えを持っているのだろう?」「どういうきっかけがあってそういう考えに至ったのだろう?」と純粋にその人自身に焦点をあてた傾聴を行わないと、意図せずに「〇〇さんはこう言っているけれど本当はこれのほうがいいのでは?」などと自分自身の考えが表出し始めてしまいます。そうすると、本当の意味で「聞いている」ことにはなりません。自分の考えは横に置いて聞いてみましょう。 「わからない」ことを示す: あなたの持つ「専門性(リソース)」が相手を委縮させているなら、あえてそれを手放します。「この技術については〇〇さんが詳しいから教えてほしい」「正直、まだ迷っているんだ」と弱みを見せることで、相手は「発言しても安全だ」と感じることができます。 物理的な威圧感を減らす: 上座に座らず、あえて輪の中に混ざる。腕組みをやめる。こうしたノンバーバル(非言語)な行動も、アンプの出力を下げる有効な手段かと思います。 ボリュームを「上げる」技術(積極的行使) 逆に、影響力をフルボリュームで使うべき場面もあるでしょう。 責任の所在を明確にする: トラブル発生時や、リスクのある決断をする時こそ、アンプの出番です。「責任は私が取るから、思い切ってやってくれ」。影響力のあるメンバーがこれを言うとチームの他のメンバーは守られている安心感のもと、チャレンジができます。持てうる限りの影響力のリソースを使ってメンバーを鼓舞しましょう。 影響力のあるフォースからチームを守る : 組織で働いている以上、チームはなんらかの影響力に晒されることがあります。もしかしたら、声の大きいユーザーからのレビューによってそのチームが提供したプロダクトのレピュテーションが一気に下がることがあるかもしれません。あるいは、社内のステークホルダーから突然の指示があるかもしれません。そのようなとき、影響力のあるメンバーが矢面に立つことによってチームを守り、影響力のある者同士で会話をしてうまく場をおさめることが必要です。あるいは、いったん有事の問題ということで影響力のあるメンバーがその問題に取り組み、他のメンバーの負担を減らすような行動をしてもよいかもしれません。 最後に 影響力からは逃れられません。それにもかかわらず影響力に無自覚になることもままあると思います。影響力の存在を認識し、その「ボリューム」をどう調整するか?という観点で影響力と付き合っていくと、周りとの関係性においてきっといいことが待っていると思います。 プロダクト採用サイトTOP カジュアル面談申込はこちら
アバター
この記事は Timee Product Advent Calendar 2025 の2日目の記事です。 タイミーでバックエンドエンジニアをしている桑原です。 突然ですが、LeanUXキャンバスというツールをご存知でしょうか? 今年、いくつかの開発をチームで進める中で、LeanUXキャンバスを複数回作成して活用しました。 私自身初めての経験で、チームメンバーも半数が初めてという状態での取り組みでした。 うまくいったことや苦戦したことがあったので、振り返りを行い次に活かすためにこの記事を書こうと思います。 LeanUX・LeanUXキャンバスとは LeanUXは、書籍「LeanUX」で紹介されている開発手法です。 LeanUXキャンバスは、この手法を実践するためのファシリテーションツールで、著者のウェブサイトでテンプレートが公開されています。 引用元: JEFF GOTHELF-website この記事ではLeanUXの詳細は省略しますが、LeanUXキャンバスを順番に埋めるワークを行うことで、LeanUXを進める基盤を作ることができます。 なぜLeanUXキャンバスを使ったのか 今年行った開発でLeanUXキャンバスを使ったのは、次のような課題があったからです。 解くべき課題が抽象的で、どのような機能を実装すれば良いのか定まっていない 逆に要求されている機能はあるが、なぜそれを実装すると良いのかチームの共通認識が持てていない 実施にあたっては、Miroというホワイトボードツールを使いました。 LeanUXキャンバスで得られたこと LeanUXキャンバスを行った最大の成果として感じているのは、チームでビジネスプロブレムステートメント(ビジネスが抱える課題を定義した文書)を作成したことです。 以前の開発では、プロダクトオーナーがビジネスプロブレムをまとめた上でプロダクトゴールを作成していました。 しかしLeanUXキャンバスを使った開発では、開発メンバーもビジネスプロブレムの深掘りからワークに参加しました。これにより、チーム全員が「なぜこの開発を行うのか」という共通認識を持つことができました。 その結果、各機能ごとにタスクが分割されたときも、機能の目的をチーム全体が理解しているため、スピーディーに開発を進めることができました。 また、ワークを通してユーザーへの理解を深める機会にもなりました。 タイミーはツーサイドプラットフォームであり、働くワーカーと、ワーカーを募集する事業者の双方にとって価値あるものでなければなりません。 たとえ事業者にとって便利な機能であっても、ワーカーの働く機会を損なう機能は実装できません。 キャンバスを作成したことで、双方の視点で考えるきっかけとなり、公平なプラットフォームであることをより意識して開発を進めることができました。 難しかった点 LeanUXにはビジネスプロブレムを記述する際、「ソリューションを決めつけすぎない」と記載されていますが、これが想像以上に難しかったです。 要求されているソリューションがあると、どうしてもその機能を前提に考えてしまいます。しかし、一度そのソリューションを忘れて「何が問題なのか」を掘り起こすことに集中する必要がありました。 このステップのおかげで、ビルドトラップに陥らず、どんな問題を解決したいのかを最初に強く意識づけることができました。 継続的な活用に向けて 最初はLeanUXキャンバスを作成した後、継続的に活用することができず、チームで振り返りを行う中でもっとうまく活用していきたいという話になりました。 アジャイル開発では一度のリリースで終わりではなく、機能の改善や追加を行っていく必要があります。 実際に機能をリリースした後に見えてくる問題も増えてくるため、一度作ったキャンバスをそのままにせず、継続的に更新していくことが有効だと感じています。 この反省を活かし、現在の開発では機能の初期リリース後も定期的にLeanUXキャンバスを更新し、追加開発の指針として役立てています。 まとめ LeanUXキャンバスは、開発の方向性を明確にする効果的なツールです。 チームでの実践を通じて得られた主なポイントは以下の通りです。 目的の明確化 : ビジネスプロブレムステートメントの作成により、なぜその開発を行うのかという共通認識が生まれた ツーサイドプラットフォームの公平性 : ワーカーと事業者双方にとって価値ある機能開発を意識できた 実践的なメリット : ビルドトラップの回避、明確な優先順位付け、迷いの少ない開発進行 開発初期にLeanUXキャンバスの作成に時間をかけることで、長期的にはチームのコミュニケーションが円滑になり、より効果的な開発につながると感じています。 今後もチームでLeanUXの理解を深めていきたいと思います。 タイミーのプロダクト開発に興味を持ってくださった方は以下も覗いてみてください。 プロダクト採用サイトTOP カジュアル面談申込はこちら
アバター
こんにちは、アナリティクスエンジニアの hatsu です。 普段はデータエンジニアとアナリティクスエンジニアからなるDREという組織に所属し、データ基盤を整えたり、dbtを使ったデータウェアハウスの開発などをしています。 本記事では、私が所属するDREで最近取り組んだ、ダッシュボードなどのデータアウトプットの管理についてご紹介してみようと思います。 なお、この記事は Timee Advent Calendar 2025 シリーズ2の1日目の記事です。 今日から毎日3本ずつ記事が投稿されますので、ぜひ他の投稿もご覧ください! 背景と課題 タイミーでは社内でいろんなメンバーがいろんなデータを日々活用していて、社内のデータアウトプットも数え切れないほど存在しています。 これらのデータアウトプットはコード管理やクエリのレビューをしていないものがほとんどで、意図せず誤ったクエリが重要なデータとして活用されてしまうことも度々ありました。 さらに、「昔は使っていたけど今はもう使っていない」、あるいは「作ったけど結局使わなかった」といった、古くてもう使われていないようなデータアウトプットも残り続け、データが更新され続けている現状があります。 どのデータアウトプットが今使われているのか、誰がそのデータアウトプットの責任者なのか、どういった品質が保証されているのかなど、データアウトプットに関する情報がこれまで管理できていないことがチーム、ひいてはデータを活用する会社全体の課題でした。 そこで、データアウトプットそれぞれに対して設定する“信頼性レベル”を定義し、その“信頼性レベル”に応じてデータアウトプットを管理していく取り組みを始めてみることにしました。 チームで定義した“信頼性レベル”とは チームで定義した“信頼性レベル”というのは、データアウトプットの保守運用のために求められる要件と、それによって得られるデータアウトプットの保証内容を定義したものです。 現状、アウトプットの利用目的などに応じて、Trusted・Verified・Provisional・Adhocの4つのレベルに分けて定義しています。 それぞれのレベルで、保守運用をするために満たすべき要件と、それを満たすことで私たちDREが保証する保守運用の水準、例えば障害時やソースデータに破壊的変更が加えられるときの対応方針などを下記のように設定しています。 信頼性レベルの要件と保証内容 表の一番右にアーカイブの方針が書いてあり、使われなくなったデータアウトプットはアーカイブされていくような運用にしています。 ここで言うアーカイブとはデータアウトプットの即時削除ではなく、ゴミ箱に入れられている、もしくはクエリのスケジュール実行が停止されている状態を指していて、まだ利用されているデータアウトプットが誤ってアーカイブされた場合にも復元可能な状態になっています。 各データアウトプットがどの信頼性レベルに設定されているかは、dbt exposureを用いて下記のようなyamlファイルで管理しています。 version : 2 exposures : - name : {{ ConnectedSheetタイトル }} _{{ConnectedSheetId}} label : {{ ConnectedSheetId }} type : dashboard config : tags : - PEOPLE_WITHIN_DOMAIN_WITH_LINK - spreadsheet meta : creator : test@example.com owner : test@example archived : false archived_at : null reliability_level : Provisional created_at : '2025-11-20T17:00:00+09:00' deprecated_at : '2026-02-20T17:00:00+09:00' visibility : PEOPLE_WITHIN_DOMAIN_WITH_LINK output_type : spreadsheet url : https://docs.google.com/spreadsheets/d/{{ConnectedSheetId}} owner : name : test@example.com email : test@example.com depends_on : - ref('hogehoge_model') - ref('foo_bar_model') - ref('chomechome_model') 例えば、上記のyamlファイルには reliability_level: Provisional と書かれていることから、この例に書かれているデータアウトプットは信頼性レベルProvisionalの水準で保守運用されていることがわかります。 初めは信頼性レベルがデフォルトのProvisionalのものとして自動でexposureが登録され、そこからデータアウトプットのオーナー(利用者)が利用目的に応じて適宜信頼性レベルを変更します。 dbt exposureを用いたデータアウトプットの自動登録と管理については、以前 こちらの記事 でも紹介していますのでぜひご覧ください! 信頼性レベルを運用することで期待される効果 このような信頼性レベルを運用することで、DREにもデータアウトプットのユーザーにも嬉しい効果があると考えています。 膨大な数のデータアウトプットの中からDREが保守するべき対象が明確になり、障害発生時のユーザーへの連絡などもスムーズに行えるようになる データアウトプットのユーザーは、自分が利用しているデータアウトプットに障害や破壊的変更の影響があるかを、通知によって把握できるようになる もう使わないデータアウトプットを適切にアーカイブすることで、メンテナンスされていない古いデータアウトプットをユーザーが誤って使ってしまうことを避けられる 信頼性レベル運用の現状と今後 この信頼性レベルの仕組みは、まだ会社全体に展開したものではなく、一部のメンバーとお試し運用中のものです。 お試し運用を通して運用の改善や修正を行いながら、将来的に会社全体で信頼性レベルを運用できるようにしていくことを目指しています。 また、障害時や破壊的な変更がある場合の通知は現状手作業で行なっていたり、信頼性レベル変更時に求められる要件を満たすための作業が大変だったりと、まだまだ信頼性レベルの運用コストが高い状態なので、自動化できるところは自動化して運用工数を下げていくことで、持続的に運用できる仕組みを作っていきたいと考えています。 最後に 12月17日(水)に「第2回データ分析現場のリアルな知恵と工夫」というオフラインイベントに登壇させていただきます。 本記事でもご紹介した信頼性レベルの運用の話をしようと思っていますので、ご興味がある方はぜひご参加ください! connpass.com
アバター
はじめに タイミーの神山です。 11/14-15 で 福岡工業大学 で開催された YAPC::Fukuoka 2025  に参加してきました。 私は当日のボランティアスタッフとして参加しつつ、いくつかのセッションを拝見したので、印象に残ったものをレポートという形でまとめます。 なお、タイミーには世界中で開催されている全ての技術カンファレンスに無制限で参加できる「 Kaigi Pass 」という制度があり、私はこれを使って参加しました。詳しくはリンクをご覧ください。 スタッフ業の様子 スタッフノベルティ 誘導している自分 クロージング! スタッフのみなさんありがとう! #yapcjapan_memorial pic.twitter.com/ijwyjMN6Nc — 坂井 恵(SAKAI Kei) (@sakaik) 2025年11月14日 はっ!941さん!大吉祥寺PMで『当日スタッフやりなよ、YAPC募集してるよ」って言われてその場で申し込んで今やってて! という方がいて、なんかいいことした気分。ちなみにスタッフの感想は「楽しいし、スタッフの尊さを知りました」とのこと。いいね。 #yapcjapan pic.twitter.com/sio1TmuszK — 941 / kushii (@941) 2025年11月15日 今回 YAPC に参加しようと思ったのは、 大吉祥寺.pm に参加して皆さんが YAPC はいいぞと言っており、興味を持ったことがきっかけです。 また、 株式会社カケハシ の 941 さんに「YAPC の当日スタッフとして参加してみたら?」と言ってもらったので、その場で申し込んで行くことが決定しました。 カンファレンスのスタッフ業、大変ですね。当日幾つも想定外のことが起きつつも柔軟に対応される皆さんすごいなあと思いました。(僕も色々とお手伝いさせてもらいました)今ではカンファレンススタッフへのリスペクトと感謝に溢れています。 また、弊社 DRE の chanyou さんがまさかのスタッフとして参加されており、初対面するというイベントもありました。色々ありましたが、初のスタッフ業楽しかったし気付きが多かったですね。参加して良かったです。 セッションの内容 なぜインフラコードのモジュール化は難しいのか - アプリケーションコードとの本質的な違いから考える fortee.jp speakerdeck.com インフラコードのモジュール化はなぜ難しいのか、インフラコードにおける問題点とアプリケーションコードとの対比を交えたセッションでした。 インフラコードとアプリケーションコードの性質は異なります。インフラコードだと”状態を記述”するので、個々の状態、つまり実装の詳細が関心事になる。 アプリケーションコードだと”処理を記述”するので、処理が何をもたらすか、つまり結果としての振る舞いが関心事になる。 従って、アプリケーションコードは処理自体のロジックをカプセル化して、抽象化が可能。 一方、インフラコードだと内部構造の可視性が必要なので、無理にカプセル化すると、理解が困難になってしまう。階層が多段 x モジュール化が問題をさらに困難なものにするという話もありましたね。 そもそも、モジュール化せずに書き下したり、論理的凝集ではなく機能的凝集を意識して、ディレクトリ・ファイル分割、モジュール機能を利用しようという結論でした。 自分はアプリケーションを書くことがほとんどなので、インフラコードとアプリケーションコードの違いに関して強く意識をしたことはありませんでした。アプリケーションコードなら当たり前に考える抽象化という考え方が、インフラコードだと一筋縄ではいかないという話を聞いて、新しい観点を得られた気がします。 「“状態の記述”と”処理の記述”はメンタルモデルが違うので、前者はホワイトボックス的、後者はブラックボックス的になる」という話だと認識しましたが、アプリケーションを書いている身からすると、テストコードは”状態の記述”のメンタルモデルが近いと思います。 テストコードは過度に抽象化しないというプラクティスもまさにそうだなと。テストが失敗した時に抽象化されていると、書き下されている場合に比べて原因のトレースが難しいですよね。 セッションスライドに、 ソフトウェア設計の結合とバランス の書籍の紹介がありましたね。統合強度の観点でもお話をされており、興味深かったです。 新しい観点を得られた素晴らしいセッションでした。 Amazon ECSデプロイツールecspressoの開発を支える「正しい抽象化」の探求 fortee.jp speakerdeck.com 普段何気なく使っている ecspresso の成り立ちや設計思想を知れたセッションでした。 ECS を管理できる IaC ツールを比較しながら、設計の違いに焦点を当て、 https://github.com/kayac/ecspresso の設計思想を明らかにする構成でした。 https://github.com/kayac/ecspresso は、責任分界点を明確にして”どこで切るか”が設計思想になっている。ECS 以外の AWS マネージドらを相手にせず、ECS のみにフォーカス。操作自体は抽象化( ecspresso deploy など)し、構造は抽象化しない。 セッション内で、 https://github.com/aws/copilot-cli というツールに言及がされていました。Copilot CLI は処理だけでなく、構造まで抽象化して提供しているとのこと。つまり、インフラ構成の定義が DSL ということです。インフラ定義を簡単に記述できるようにした DSL は、現実のアプリケーションが必要とする複雑さをカバーしきれない。DSL を運用し続けるのは骨が折れそうですよね。特に AWS API の変更や追加があったときは、それを抽象化して提供しないといけないわけですから、抽象 I/F をその都度考えないといけないわけです。Copilot CLI は ECS の複数 LB 対応をしておらず、現在は事実上メンテナンスモードになっているとのこと。 Copilot CLI がサポートしていない機能は CloudFormation テンプレートで書き下すしかなく、結局具象を使わざるを得ない状況に。この現象を “現実世界のアプリケーションを相手にすると抽象が破れて具象が漏れてくる” と表現されていました。 このセッションは、「 なぜインフラコードのモジュール化が難しいのか 」のセッションとも通ずるところがありました。 インフラコードは“状態の記述”をするもので、実装の詳細が関心事になるという話でしたが、まさにその内容がそのまま具体例として言及されているようだなと。Copilot CLI は構造を抽象化して提供しているがあまり、内部的には変更容易性を担保しづらい状態だったのかもしれませんね。 “状態を記述”する類のコードは、安易に抽象化しない方が良い、抽象化する範囲は慎重に考えた方が良いなと。前のセッションの内容と合わせて2倍楽しめる内容でした。 なぜThrottleではなくDebounceだったのか? 700並列リクエストと戦うサーバーサイド実装のすべて fortee.jp speakerdeck.com CloudBees さんで700並列リクエストを捌くために Debounce の手法を使ってサーバーサイドで処理をする実装の紹介でした。 CloudBees さんでは、CI/CD プラットフォームを提供しており、テスト結果を集めて AI を使ってさまざまな処理(以下、Close 処理)をしているとのことです。その際に700並列の分散テスト実行結果が大量に送られてくるので、Close 処理をある程度まとめて実行したいが、実行が終わったかどうかを判定するのが難しく、いい感じにデータが溜まった後に Close 処理する必要があったとのこと。なので、Close 処理を最適なタイミングでまとめて実行する仕組みが必要だった。 処理回数を間引くには、n秒に1回処理の Throttle と、待ち時間内に次のイベントが発生するとタイマーがリセットされ、処理の実行が遅延する Debounce があります。Debounce の例で言うと、Google の入力サジェストがあります。 YAPC という文字を1文字ずつ入力するたびに、サジェスト結果を返すのではなく、1文字ずつ入力するごとに処理時間が Delay して、最終的に C が入力されて次の入力がなくなったタイミングでサジェスト結果を返すようにするような手法です。今回は、ほぼ同時に大量のリクエストが送られてくるが、タイミングには振り幅があるので、決まった間隔の Throttle 処理ではなく、Debounce が良さそうだったとのこと。 基本的なロジックとしては、リクエストがあった時点で UUID を生成して Redis に格納し、nmb秒待って UUID を取得して照合し、同じ値なら実行する。他のリクエストがあったら、UUID が書き換わるので最初のリクエストは UUID 照合時に弾かれて何も実行されないというものです。 Race Condition への対応含め興味深く聞かせてもらいました。Redis の SET コマンドに、 GET オプション なんてあるんですね、知らなかった。 前職では、フロントエンドでサジェスト機能を作った際に Debounce のロジックを使って実現したことがあり、Debounce 自体の存在は知ってはいたものの、サーバーサイドのリクエスト負荷軽減の文脈で理解していたので、サーバーサイドのロジックそのものに適用するという視点は目から鱗でした。 Agentに至る道 〜なぜLLMは自動でコードを書けるようになったのか〜 fortee.jp speakerdeck.com 普段何気なく使っている AI Agent の成り立ちがざっくりわかるセッションでした。 Transformer ⇒ GPT ⇒ InstructGPT の流れで一発勝負の生成ができるところから、 ReAct 論文 の登場。生成してダメだったら何がダメだったのか考えて再生成という、人間の営みに近い動きを実現。Reason と Act のループを回せるように ReAct。これが自律的に判断し行動する AI エージェントの基礎に。言論文から学ぶ生成 AI を積読しているのでこれを機に読もうかなと思いました。 Stay Hacker 〜九州で生まれ、Perlに出会い、コミュニティで育つ〜 speakerdeck.com pyamaさんのキーノート、とても良かったです。生成 AI 時代に変わっているのは手段であって、自分の手でよくしたいという目的は変わってない。進化しながら Hack し続けるという話が印象的でした。いい課題を見つける、研鑽、事例から学ぶ。これからの自分がどうありたいかについて深く考えさせられました。 コミュニティの話も良かったです。自分の手で世界、社会、個人、技術を少しだけよくしようとする人たちの「熱」を感じられる。そこには言語の壁や、役職、職種でもない、”Hacker” としてのあり方があると。個人的にはコミュニティの体験がより Hack を楽しく加速させると感じています。 自分も楽しく Hack し続けたいですね。とても奮い立たせられる内容でした。 まとめ 独楽蔵で一杯 博多一双のラーメン YAPC 初参加でしたが、楽しかったし学びも多いカンファレンスでした。勢いで参加しましたが、参加して良かったなと思います。 Perl を1行も書いたことないんですが、そんな私でも学びになるセッションがとても多く、有意義な時間を過ごせました。 コミュニティの方も皆さん温かく、YAPC の成り立ちなどを教えてくれました。 改めて、福岡という場所は最高だなと思いました。アクセス抜群、食べ物も美味しい。福岡といったら独楽蔵という日本酒がありまして、燗酒でいただきました。良かったですね。 来年は東京ビッグサイト開催です!来年もまた参加したいですね。
アバター
タイミーQA Enabling Gのkishikenです。 「 Tokyo Test Fest 2025 」が11月14日に大崎ブライトコアホールにて開催されました。 タイミーには世界中で開催されているすべての技術カンファレンスに無制限で参加できる「Kaigi Pass」という制度があります。詳しくは以下をご覧ください。 productpr.timee.co.jp 今回はこの制度を使って参加しました。 参加して 聴いたセッションのうち印象に残ったいくつかをピックアップしてご紹介します。 Zen of Testing Mesut Durukal 日本の「禅」の心。禅のような日本の文化がソフトウェア品質とどのようにつながるのか、ユーモアを交えて講演されていました。 例えば、ゲートQAという品質のチェックポイントは神社の「鳥居」であり、探索的テストは誰にも仕えず旅をする「浪人」のようであると言ったものです。いくつか「流石に厳しくないか……?」と感じたものもありましたが、日本の文化と品質活動を結びつけていこうという試みが非常に面白く、ユニークな講演でした。 個人的にはテストという活動を「折り紙」の繊細さや精緻さに例えたところが印象に残りました。テストは細やかな技術が必要でありつつ、一種のアートでもあるという考えは私も同意見です。 Make Testing a Team Sport: Bug Bash for Better Software Prashant Hegde バグバッシュを「チームスポーツ」として捉え、チーム全体で品質を高めていく手法についての講演でした。 冒頭で「Blind men and an elephant」の寓話が引用され、異なる視点からプロダクトを見ることの重要性が語られました。開発者・デザイナー・プロダクトオーナーさらには開発部以外の方など、異なる役割の人々が集まってテストすることで、一人では見つけられない問題を発見できるという考え方です。 バグバッシュを成功させるためには準備ミーティングが重要であり、また参加者のモチベーションを高めるために勝者の表彰といった工夫も効果的だと紹介されていました。特に準備についてフォーカスしており、ただ人を集めてプロダクトを触ればバグバッシュになるのではなく、役割分担やタイムボックスを決め、テストが専門でない人には事前にレクチャーをし、そして飲み物やおやつをしっかり用意しておくのが重要だと説明していました。 私自身はバグバッシュについて企画も参加もあまりやったことがないのですが、開発以外の部門の方との交流も含めて実施してみるのも面白そうだなと思いました。 Be a Better Test Friend Martijn Goossens 基調講演です。QAと開発に関わる他のロールとの関係性について、「友達」という視点から語られた講演でした。 ソフトウェア開発はQAだけでなく、プロダクトオーナー・UXデザイナー・開発者・運用担当者といった多くロールの人がチームを組んで行います。QAエンジニアは時に孤独な役割になりがちですが、品質を「シェア」し、チーム全体で取り組むマインドセットを持つことで、より良い協力関係を築けるという考え方について紹介されていました。 例えば、プロダクトオーナーはビジネスを技術に翻訳するエキスパートです。プロダクトオーナーからはドメイン知識やステークホルダーを教えてもらい、QAからは受け入れ基準やユーザーストーリーの書き方をサポートしていきます。このようにQAと他ロールとで協調をして、お互いに足りないものを補っていくことの重要性をお話ししていました。 特に、QAは批評家ではなく編集者であるべきだという指摘が心に残りました。批評家は問題を指摘するだけですが、編集者は一緒により良いものを作り上げていく存在です。「品質のアンバサダー」として、異なる役割の人々と「友達」として協力し、仕事に取り組んでいくことの大切さを学びました。 まとめ Tokyo Test Festは今回が2回目の開催です。私は昨年参加できなかったため、今年が初参加でした。このカンファレンスは都内での開催ですが、日本で働く海外のエンジニアの方々が主催しており、さながら国際カンファレンスのような雰囲気です。ほとんどの講演が英語で行われ、 聴く側としても貴重な機会になりました。 ピックアップには含めませんでしたが、品質保証においても生成AIに関わる多くのトピックが語られています。生成AIの登場によるソフトウェア開発の大きな変化は日本も海外も変わらず、皆悩みながら日々向き合っているというのが実感できました。 生成AIの活用からチーム協働まで、QAの未来について多くの示唆を与えてくれるカンファレンスでした。今回学んだ「品質をシェアする」という考え方を、タイミーのチームでも実践していきたいと思います。
アバター
こんにちは、タイミーでバックエンドのテックリードをしている新谷 ( @euglena1215 ) です。 タイミーのバックエンドでは、Feature Flagの管理に Flipper を使用しており、そのデータストアとしてRedisを利用しています。本記事では、Flipperの値をインメモリキャッシュする機構を導入することで、Redisへのアクセスを90%削減し、毎時発生していた突発的なスパイクを解消した事例を紹介します。 本記事が、Feature Flagシステムの負荷やパフォーマンスに課題を抱えているチームの参考になれば幸いです。 Redisで突発的なspikeとエラーが発生していた タイミーのモノリスRailsリポジトリでは、Feature Flagを活用して新機能の段階的なリリースやA/Bテストを実施しています。しかし、運用を続ける中で、いくつかの問題が顕在化してきました。 毎時0分台に発生する突発的なspike DatadogでRedisのメトリクスを監視していたところ、毎時0分台に突発的なspikeが発生していることが確認されました。このspikeでは、平常時の約5倍ものアクセスが集中していました。 Redisアクセスが毎時0分台に跳ねてました これはどうやら毎時0分に実行されるcronにより非同期ジョブが大量にenqueueされ、各ジョブの処理でFeature Flagが参照されることによる RedisへのN+1アクセスが発生しているようでした。 そもそもFeature Flagの値はほとんどのケースで変化せず、毎回データストアであるRedisにアクセスする必要はないのではないかと考えました。数秒くらいの期間であれば各プロセスでFlagをキャッシュしておいても、実用上は問題ないはずです。 そこで、プロセス内キャッシュとRedisの値を同期してくれる便利な仕組みを実現しようと考えました。 解決策:Flipper::Adapters::Syncの導入 Flipperには、remoteのデータとlocalのデータをいい感じに同期してくれる Flipper::Adapters::Sync が存在します。このアダプターを使うことで、指定した間隔(interval)でのみremoteからデータを取得し、それ以外はlocalのキャッシュから値を読み取る、という振る舞いを実現できます。 実装の詳細 実装の主なポイントは以下の通りです: 1. Sync Adapterの導入 # config/initializers/flipper.rb require ' connection_pool ' require ' flipper/adapters/sync ' Flipper .configure do |config| redis_pool = ConnectionPool .new( size : 5 , timeout : 5 ) do Redis .new( url : REDIS_URL ) end remote_adapter = Flipper :: Adapters :: Redis .new(redis_pool) local_adapter = Flipper :: Adapters :: Memory .new sync_adapter = Flipper :: Adapters :: Sync .new(local_adapter, remote_adapter, interval : 10 ) config.adapter do sync_adapter end end Sync Adapterは、localのMemory adapterとremoteのRedis adapterを組み合わせて使用します。 interval: 10 と指定することで、10秒間はlocalのキャッシュから値を読み取り、10秒経過後に再度Redisから最新の値を取得します。 2. Adapter初期化の最適化 重要なポイントとして、Sync Adapterのインスタンスを config.adapter do ~ end ブロックの 外で作成 しています。これには理由があります: ブロック内に記述すると、リクエスト毎にアダプターが初期化されてしまう 外で作成することで、プロセス内で単一のインスタンスを共有できる 結果として、プロセス内で同じローカルキャッシュが使われる 3. Redis Connection Poolの導入 gem ' connection_pool ' これまでは毎リクエストでRedis接続を確立していましたが、Sync Adapterを導入することで接続が長期間保持されるようになります。そのため、接続が切れた場合の耐障害性を高めるため、Flipper 1.3.3から導入された Redis Connection Pools の機能を使ってconnection poolを持つようにしました。 4. リクエスト毎のメモ化を無効化 Rails .application.configure do # Sync adapter の @local (Memory) がプロセス内キャッシュとして機能するため、 # リクエストごとのメモ化は不要 config.flipper.memoize = false end Flipperではデフォルトでリクエスト毎に同一flagを参照した際にメモ化する最適化が有効になっていますが、これを無効にしました。メモ化が有効になっていると、リクエスト毎にSync adapterのlocalデータが揮発してしまう問題があったためです。Sync adapterが導入されたことで、そもそもメモ化は不要になりました。 導入前後の効果 検証環境での検証結果 本番導入前に、demo環境で動作確認を行いました。社内管理画面に全てのfeature flagに対して Flipper.enabled? を呼び出すテストボタンを用意し、導入前後の挙動を比較しました。 Before:全リクエストでFlag数回のRedisアクセス 全てのリクエストでFlag数回(21回)のRedisアクセスが発生している After:10秒中の最初のリクエストのみRedisアクセス 同一プロセスにおける10秒中の最初のリクエストを除き、Redisアクセスが発生しなくなった 元の実装では、全てのリクエストでFlag数回(21回)のRedisアクセスが発生していましたが、変更後は同一プロセスにおける10秒中の最初のリクエストを除き、Redisアクセスが発生しなくなったことを確認しました。 本番環境での効果 本番環境に導入した結果、以下の効果が確認できました: 平常時のRedisアクセスが90%削減 リクエスト毎のRedisアクセスがほぼ不要になり、大幅なアクセス削減を実現 毎時0分台のspikeが完全に解消 平常時の5倍のアクセスが発生していたspikeが消滅 Redisの負荷が安定し、インスタンスサイズダウンの可能性も見えてきた まとめ Flipperに Flipper::Adapters::Sync を導入することで、Feature FlagシステムのRedis負荷を90%削減し、毎時発生していた突発的なspikeを解消することができました。 この改善により、以下のメリットが得られました: 安定性の向上 :timeout errorの減少、spike解消による安定した動作 パフォーマンスの向上 :Redis I/Oの削減による応答速度の改善 コスト削減の可能性 :Redis負荷の削減によるインスタンスサイズダウンの検討 Feature Flagの値が頻繁に変更されない特性を活かし、適切にキャッシュすることで、大きな改善効果を得ることができました。同様の課題を抱えているチームの参考になれば幸いです。 参考資料 Flipper GitHub Repository Flipper 1.3.3 Release Notes - Redis Connection Pools Flipper::Adapters::Sync Documentation
アバター
こんにちは、タイミーでAndroidエンジニアをしている ふなち(@_hunachi) です 😆 タイミーの Kaigi Pass という制度を使い、 Kotlin Fest 2025 に参加してきたので、その感想などを紹介します! ちなみに、弊社からは @tick_taku と私の2名で参加しました。 聞いたセッション紹介 @tick_taku と 私、それぞれが特に印象に残ったセッションをご紹介します! tick_taku 「動く」サンプルでスムーズなコミュニケーションを 〜CMP時代のKotlinPlayground活用最前線〜 2025.kotlinfest.dev KotlinPlayground のプラットフォームに Compose Multiplatform が選択できるため、Web 上でサクッと画面を構築して URL を共有することで動くものベースにチームコミュニケーションを活性化しよう、というセッションです。 KotlinPlayground は Kotlin 勉強し始めのころに「このコードはどういう動きをするのか?」を確認するために利用したことはありましたが、まさか UI を共有することができるようになっているとは...明日から使ってみたい tips な内容でした! 自分は Android アプリエンジニアなのでさっそく Android 向けの UI を構築して共有したいなと思いました。ですが実際触ってみると activity package は import できそうになかったので、基本は Web 向けで Android アプリ的にはコンポーネント単位の挙動の確認などに利用できるのかなと感じました。 またWeb上の環境なのでプロダクトコードをそのまま載せるのはやめておいた方が無難かもしれません。 こちらのセッションで Firebase Studio なる存在を初めて聞ききました。AI による vibe coding をベースとしたノーコード開発向けのプラットフォームでしょうか。 UI 共有に利用するだけだとオーバースペック気味そうですが、テキストからモックを作らせてコミュニケーションを図るという目的であれば有用そうですね。 フルKotlinで作る!MCPサーバー、AIエージェント、UIまで一気通貫したAIエージェントシステム 2025.kotlinfest.dev もともと Kotlin で MCP サーバー作れないかな〜と思っていたところに Koog による AI エージェント作成までを紹介されていて、フルスタック Kotlin の可能性を見せていただき、とてもワクワクしました。やはり Kotlin は素晴らしい言語!! チャットのチェックポイントにおけるハマったポイントなど、経験者の少ないフレームワークの実体験に基づくナレッジの共有はとても貴重でありがたいです。 公式 SDK が Streamable HTTP をサポートしていないというのは MCP サーバーを作成する上でネックになりそうですね。 このセッションを聞いた後に上記の KotlinPlayground のセッションを聞いていて思ったのですが、LLM を利用して仕様書や要点から KotlinPlayground 使ってモックを作らせて短縮リンク発行するエージェント作れないかな〜🤔 アイデアを話してまとめているだけで動作が見られる簡単なモックができ、よりプロダクトのコミュニケーションを円滑にできるのではと期待値が高まりました。 とは言え、 Firebase Studio が想像している通りであれば、用途が被りそうなので情報を集めていきたいです。 内部実装から理解するCoroutines ー Continuation・Structured Concurrency・Dispatcher 2025.kotlinfest.dev 普段何気なく利用している Coroutines の詳細な内部処理をコードと図解を用いて詳細に解説していてとても勉強になりました。 実際この辺りの内部仕様を把握していなくてもなんとなくで使えてしまい非常に便利ですが、ディテールを理解することでより深く・効率的に使いこなせるようになるのだと実感しました。 少しハードルの高いデコンパイルやライブラリのソースコードを読んでみることに挑戦しようと思える素晴らしいセッションだと思います。 余談ですが解説記事が事前に公開されており、進行に合わせて読み進めながらセッションに集中できたこともとても良い体験でした。ありがとうございます! ふなち Kotlinを支える技術:言語設計と縁の下の力持ち 2025.kotlinfest.dev このセッションでは、Kotlinの言語設計の裏側と、この一年でKotlinがどのように進化してきたのかについて深く知ることができました。 言語設計に関しては Companion object の話を聞くことができました。 Kotlinの Companion object は、その使用の95%がJavaでいう static を宣言する場として使われています。しかし、実際には Class.Companion という 型 を生成しており柔軟な使い方ができる点や、クラスに一つしか作れない点などの問題があるそうです。 この問題を解決するため、過去には namespace や static などさまざまなプロポーザルが検討されましたが、 Companion object との併用時の課題などから、全てリジェクトされてしまったそうです。しかし、現在は companion 構文を使えるようにすることで、これらの課題を解決しようとしているという話でした。 また、この話の中で「一度リリースしてしまうと、それは本や動画、ネット上の記事として末長く残ってしまう。だから、簡単にDeprecatedにはできない。新しい機能を作る際には、とても慎重にならざるを得ない。」とおっしゃっていたのが特に印象に残りました。 他にもセッション内では、Kotlin 2.2や2.3、そしてAndroid Studioのアップデートについても触れられており、Kotlinは更に年々便利になっているなと改めて実感しました。 また、 Kotlinのロードマップ が年に2回ほど更新されているとのことだったため、Kotlin Fest の数日後にAndroidのメンバー全員で、最新のロードマップを確認する時間を作りました。Kotlin Notebookの開発が進んでいることなどを知れて、とても有意義な時間となりました。半年後にも、またメンバー全員で読もうと思います! せめて、ネイティブらしく - マルチプラットフォームと撤退戦略 2025.kotlinfest.dev KMP(Kotlin Multiplatform)をあまり触ったことがない自分としては、「KMPを導入した場合の欠点」や「万が一、撤退する場合にどうすべきか?」という点が気になり、このセッションを聞きに行きました。 KMPをほぼ触ったことがないのですが、このセッションは主にiOSアプリ開発を行う際の内容だったため、FlutterやAndroidを触っている自分にとって、比較的分かりやすい内容でした。 KMP iOSのStableリリースもあり、KMPでのiOSアプリ開発はどんどんしやすくなっていることや、KMPでは実現が難しいこともKotlin/Nativeで頑張ればほとんど実現できると知れ、KMP導入のハードルはかなり下がっているんだなと感じました。 「撤退戦略」に関しても、実装時にレイヤーを分けるといった具体的なTipsが紹介されており、とても勉強になりました。 Rewind & Replay: Kotlin 2.2 が変えるCoroutine デバッグ最前線 2025.kotlinfest.dev 最近はCoroutinesの深いデバッグをする機会が減っていましたが、このセッションでCoroutinesのデバッグがこんなにも進化していることを知ることができました。 ただ、新しいデバッグ機能をAndroid Studioではまだ使えないそうなので、早くAndroid Studioでも対応してほしいなと思いました😢 一緒に紹介されていた stacktrace-decoroutinator というライブラリ は、Android Studioでも使用可能とのことだったので、いざという時に頼らせてもらおうと思います! KoogではじめるAIエージェント開発 2025.kotlinfest.dev 現在のAI Agent開発の状況や、 Koog がいかに便利かを知ることができました。 カレンダーに予定を追加してくれるようなAgentも作れるという実例を見て、これは社内ツールや外部サービスとの連携など、色々な用途で使えそうだと感じました。 細かな実装のコツも色々紹介されており、Koogを使って、社内・社外問わず、AIエージェント開発にチャレンジしてみたいというモチベーションが高まりました! 全体を通して 全体的な雰囲気は、セッションもブースもたくさんあり、賑わっていて楽しかったです。 お弁当やPCを持ってきていた関係で荷物が多かったため、クロークがあったのも本当に助かりました! 懇談会では、サーバーサイドでKotlinを使っている企業の方ともお話ができたり、Kotlin Fest ならではの交流ができていい刺激になりました!弊社のサーバーサイドもKotlinになればいいのに…(小声)🤫 Kotlin Fest で学んだことや得られたモチベーションを活かして、明日からの開発に繋げていきたいと思います! 推しのあすけんのお姉さんとのツーショに喜ぶ tick_taku
アバター
こんにちは。タイミーでデータサイエンティストをしている吉川です。 最近、生成AIを使ったプロジェクト管理の仕組みを試しているのですが、想像以上に働き方が変わったので、その体験を共有したいと思います。 TL;DR 「進捗を更新して」の一言で、5つの管理ファイルが自動更新される仕組みを作った 成果物作成時間が約50%削減(6.5時間→3.25時間)※体感ベース・タスクにより削減率は異なる LLMが「外部ツール」から「チームメンバー」に変わる感覚 秘訣は、Git管理されたMarkdownファイルにドキュメントを集約し、自動化ルールを作成すること はじめに:もっと本質的な仕事に時間を使いたかった プロジェクト管理をしていて、こんなふうに感じたことはありませんか? デイリースクラム準備 - タスク管理ツールを開く - 「昨日のタスク、進捗率どうなった?」 - 完了タスクを目視でカウント(1, 2, 3...) - 電卓で進捗計算「40/45...88.9%か」 - 手動で更新 今日のタスクを決める - 「タスクAとB、どっちを先にやるべき?」 - 依存関係を確認しようとする - 複数のツールを参照して判断(15分) タスク決定、作業開始 - 「前提となる仕様、どこだっけ?」 - 「前に決めた内容、何だっけ?」 - 複数のツールやドキュメントを探索(30分) 文書を書き始める - 構造を考え、内容を練り上げる(4時間) 週次報告の準備(金曜日) - 完了タスクを手作業でカウント - Sprint切り替え準備:未完了タスクを整理 - 次週の準備(30分) 日常的な進捗管理や情報収集に時間を使うより、 もっと創造的で本質的な仕事に時間を使いたい 。そう考えるようになりました。 やったこと:Git + Markdown + LLM自動化ルールで進捗管理を自動化 そこで、以下のような仕組みを作りました。 1. すべてのドキュメントをGit管理されたMarkdownファイルに集約 プロジェクトに関するドキュメントをすべてMarkdown形式でGitリポジトリに格納しました。 requirements/ ├── A_現状分析/(5ファイル) │ ├── p01_a_01_high_level_kpi_analysis.md │ └── ... ├── B_詳細分析/(5ファイル) ├── C_ビジネス要件/(5ファイル) ├── D_データ要件/(5ファイル) ├── F_機能要件/(4ファイル) ├── S_詳細設計/(7ファイル) └── execution_management/ ├── sprint_plans/sprint6.md ├── phase1_issues.md ├── project_epics.md ├── project_dashboard.md └── project_roadmap.md なぜMarkdownなのか? ファイルベースなので、LLMがコンテキストを取得しやすい 相対リンクで、ファイル間の関連性をLLMが自動理解できる Git管理により、変更履歴を追跡できる grep による高速全文検索が可能 2. 「進捗を更新して」というトリガーワードで自動処理 次に、Cursor(AIエディタ)のルール機能を使って、自動化フローを定義しました。 なぜLLMなのか? Git管理のMarkdownに集約された全体の文脈を横断的に理解できる 関連ドキュメントを自動収集し、依存関係を把握して不足情報だけ質問できる 叩き台の生成と整合性検証を自動化し、人はLLMと壁打ちしながら仕上げに集中できる 「進捗を更新して」と入力すると、以下の処理が自動実行されます: 実際の動作例 ※以下は実際の出力を読みやすく簡略化したものです。 私: 進捗を更新して Claude: ╔══════════════════════════════════════════════════════╗ ║ 📊 進捗更新の自動処理を開始 ╚══════════════════════════════════════════════════════╝ ┌──────────────────────────────────────────────────┐ │ 🗂️ ステップ1: エビデンス確認結果 ├──────────────────────────────────────────────────┤ │ A.現状分析(5個全完了) │ ├─ p01_a_01_high_level_kpi_analysis.md ✅ │ ├─ p01_a_02_stakeholder_hearing_summary.md ✅ │ ... │ │ D.データ要件(1個) │ └─ p01_d_01_data_sources_list.md ✅ └──────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────┐ │ 📈 ステップ2: 進捗率算出 ├──────────────────────────────────────────────────┤ │ 完了Sub-issue数: 17 → 18(+1) │ 全Sub-issue数: 54 │ │ 進捗率 = (18 / 54) × 100 = 33.3% │ 進捗バー: ███░░░░░░░ 33.3% └──────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────┐ │ 📝 ステップ3: 5ファイル同時更新 ├──────────────────────────────────────────────────┤ │ 1️⃣ sprint6.md ✅ 更新完了 │ 2️⃣ phase1_issues.md ✅ 更新完了 │ 3️⃣ project_epics.md ✅ 更新完了 │ 4️⃣ project_dashboard.md ✅ 更新完了 │ 5️⃣ project_roadmap.md ✅ 更新完了 └──────────────────────────────────────────────────┘ ✅ 進捗更新処理が完了しました 3. エビデンスベース管理 進捗の判定は「実ファイルが真実」という哲学に基づいています。 そのため、あらかじめ生成される成果物のファイル名や置き場所などを決めておく必要があります。 タスク一覧: - A-1: API設計書 → ファイルあり、ただし [WIP] タグ付き - A-2: DB設計書 → ファイルあり ✓ - A-3: 画面設計書 → ファイルあり ✓ - A-4: テスト計画書 → ファイルあり ✓ - A-5: 運用手順書 → ファイルなし(未着手) LLMの判定:「WIPは除外、未着手1個、実際は60%完了」 → 進捗率 = 完了数(3) ÷ 全体数(5) × 100 = 60% → 成果物ベースの進捗把握で進捗率を明確化 推測や自己申告を排除し、実成果物ファイルの存在で作業完了を判定します。 効果:体感で成果物作成時間が50%削減、そして仕事の質が変わった Before(従来の方法) 10:45 要件定義を開始 - 「前提となる仕様、どこだっけ?」 - 「前に決めた内容、何だっけ?」 - 複数のツールやドキュメントを確認(30分) 11:15 材料が揃って、ゼロから文書を書き始める - 構造を考えながら、内容を練り上げる(4時間) 15:15 レビュー依頼 - レビュー指摘を受けて、修正対応 17:15 初稿完成(合計6.5時間) After(LLMとの協働) 11:00 「タスクCに取り組んで」と指示 11:05 LLMがタスクの本質を理解・必要情報を一覧化(5分) - 「このタスクの目的は〇〇ですね」 - 「必要な情報:A, B, C。不足:Dを教えてください」 11:10 不足情報を提供(20分) 11:30 LLMが叩き台作成(5分) - 関連ドキュメント自動収集済み - 7割完成の初稿を生成 11:35 LLMと壁打ちしながらじっくり詰めていく(2時間30分) - 私:「UIの表示項目これで全部?」 - LLM:「〇〇の観点が抜けています。追加しましょうか?」 - 私:「エラーハンドリングのパターンは?」 - LLM:「A, B, Cの3パターン。それぞれメリット・デメリットは...」 14:05 最終調整(10分) 14:15 高品質な成果物が完成! Before: 6.5時間 After: 3.25時間 - 50%削減、しかも品質向上 ! 体感的に変わったこと 指標 Before After 変化 進捗更新作業 手動で5分~30分 LLM作業10分~30分+人の確認1分~5分 50%削減 成果物作成 人10割 LLM叩き台5~7割+人仕上げ3~5割 50%削減 視点の網羅性 人の経験に依存 多角的な視点で抜け漏れ減 抜け漏れ減 ※ 上記の50%削減は体感ベースであり、タスクの性質によって削減率は変動します。 削減率が高かったタスク(60-80%削減) : 一般的な知識で構成されるタスク テンプレート化・パターン化しやすい LLMの事前学習データに豊富な事例がある 例:技術調査、API設計書作成、一般的なテスト計画書 削減率が低かったタスク(20-40%削減) : 組織固有の知識が必要なタスク LLMに与えるコンテキストが膨大 人間の判断・確認が必要 例:既存システムの設計意図の理解、複雑なビジネスロジックの要件定義 その他の副次効果 Markdown集約による「組織の記憶」は、進捗管理と成果物作成以外にも、様々な場面で価値を発揮します。 1. コンテキストを渡さずに、即座に壁打ちができる ※以下は実際の出力を読みやすく簡略化したものです。 私: 機能Xのユーザー認証、どう実装すればいい? Claude: S-1で決定した認証方式を使用してください 【認証方式】 - JWT + OAuth 2.0(Google認証) - トークン有効期限:24時間 【実装制約】 - 共通ライブラリ:@/lib/auth.ts を使用 - エラーハンドリング:共通形式(S-7:ログ・エラー設計) 【依存関係】 - ログイン機能:完了済み - API仕様:S-3(API設計)に記載 LLMが過去の意思決定(S-1で決定した認証方式)を記憶しているので、すぐに実装方針がわかります。 2. 属人化の排除 「組織の記憶」があることで、特定メンバーへの依存が最小になります。 ※以下は実際の出力を読みやすく簡略化したものです。 私: 担当メンバーが突然退職することになった。 引き継ぐため機能Xの実装状況を把握したい。 Claude: 機能X(ユーザー認証)の進捗状況 【完了したIssue】 ✅ N-1: セキュリティ基盤設計(100%完了) - Cloud IAP + NextAuth.js設計 - 決定日: 2025-09-15(CTO承認済み) 【次のIssue】 🟡 N-2: ユーザー認証機能実装(進捗 60%) ✅ 完了: LoginForm基本実装 ⏳ 残タスク: エラーハンドリング実装 従来では、担当者のアサイン変更時に情報の引き継ぎに時間がかかっていました。 LLMによる組織の記憶があれば、即座に状況を把握し、スムーズに作業を引き継げます。 3. 思考の深まり(LLMとの壁打ち) 下準備(情報収集・構造化)をLLMに任せることで、本質的な思考に集中できます LLMと壁打ちすることで多角的な視点を獲得できます(要件の漏れ、エラーハンドリング、セキュリティ等) 一人では気づかなかった観点をLLMが補完してくれます 結果:最大の価値は時間削減ではなく品質向上にあります 運用してわかったこと 始めるのは簡単だが、期待通りに動かすには それなりの試行錯誤が必要でした プロンプトの調整、ルールの修正を何度も繰り返す必要がある しかし、一度作れば継続的に価値を生み出し続ける 長期的な投資対効果が初期の手間を上回る まとめ この記事では、「進捗を更新して」で進捗管理が自動化され、LLMとの壁打ちで成果物作成が効率化される仕組みを紹介しました。 実現の基盤となる3つのポイント 1. Git管理のMarkdownに集約 LLMが「組織の記憶」として機能 関連ドキュメントを自動収集・整理 進捗管理と成果物作成の両方を支える基盤 2. 自動化ルール(トリガーワード) 「進捗を更新して」で5ファイル同時更新 Sprint期限超過の自動検出 3. エビデンスベース管理 実成果物ファイルの存在で進捗を判定 推測や自己申告を排除 得られた効果 事務作業の負荷が半減し、品質は安定化 創造的な仕事に時間を使え、アウトプットの質が向上 LLMは「外部のツール」から「内部のチームメンバー」に変化 最後までお読みいただき、ありがとうございました!
アバター
MLOpsエンジニアの tomoppi です。 データエンジニアリング部 データサイエンスグループ(以下、DS G)に所属し、ML/LLM基盤の構築・改善に取り組んでいます。 概要 2025年4月、タイミーPlatform EngineerのMoneyForestさんが、 「タイミーにおけるProduction Readiness Checkの取り組み」 という記事を公開しました。サブシステムや新規サービスのリリースに際しての評価基準を定めたもので、MLOpsとしても参考になる部分が非常に多くありました。 その記事に触発され、DS Gとしても再現性高くLLM Applicationを構築・運用するための評価基準を定めたいと思うようになり、本記事で言及する Production Readiness Checklist for LLM Applicationの策定に至りました。 背景 2024年以降、DS Gでは複数のLLMを活用したプロダクト開発に取り組んできました。しかし、前述のチェックリストをDS Gが構築・運用する LLM Applicationに適用しようとすると、以下のような課題に直面しました。 AWS前提で書かれているため、DS Gで利用しているGoogle Cloud用に読み替える必要がある LLM特有の非決定性への考慮が必要 継続的評価やGenerative AI ライフサイクルの観点が不足している チェックリスト策定の方針 技術に依存しない普遍的な原則と、タイミーDS Gでの具体的な実装に基づいたガイドの2部構成としました。 理由としては、現在はCloud Runをベースとした運用となっていますが、将来的にGKEの採用やECSへの移行などが十分に可能性としてあるためです。 また、RAG、Agentic Workflow、tool useなどのより高度なLLM利用は対象外としました。 それらを利用するアプリケーション設計・構築の際は、本記事で紹介するチェックリストを土台とし、新たにより専門性の高いチェックリストやそれに準ずる評価基準を設ける想定です。 ビジネス目標に関してはスコープアウトすることも考えましたが、ROIは大事にしていきたいというDS Gの意思として入れ込むことにしました。 チェックリスト 0. 戦略的目標とビジネスアライメント LLM導入の目的と期待される成果を明確にします。 0-1. ビジネス目標の明確化 [ ] 測定可能なビジネスゴール(コスト削減率、顧客満足度向上、処理時間短縮等)が設定されている [ ] ユースケースに対する期待される精度・応答速度が定義されている 0-2. 成功指標(KPI)の設定 [ ] ユースケース固有のKPIが定義されている [ ] KPIの測定方法とダッシュボードが整備されている 1. 安定性・信頼性 システムを再現可能かつ自動化された方法でデプロイ・運用できるようにします。 1-1. 構成管理 [ ] すべてのコードがバージョン管理システムで管理されている [ ] プロンプト(システムプロンプト、テンプレート等)がバージョン管理されており、変更履歴が追跡可能(監査可能)な状態になっている 1-2. CI/CD [ ] Lint、単体テスト、セキュリティスキャン(静的解析)がCIパイプラインで自動化されている [ ] 開発(dev)、ステージング(stg)、本番(prod)環境へのデプロイがCDパイプラインで自動化されている 1-3. リリースプロセス [ ] プロンプトやモデルの変更が、ステージング環境で品質評価を実行した後に本番にデプロイされるプロセスになっている [ ] カナリアリリースや段階的ロールアウトで、主要メトリクス(エラーレート、精度、コスト)を監視しながらリリースされる仕組みが確立している 1-4. LLM選定と継続性 [ ] 利用モデルのSLA/SLOを踏まえたフォールバック戦略が定義されている プライマリLLM APIが利用不可時のフォールバック先(別モデル/静的レスポンス/エラー表示) リトライポリシー(回数、待機時間、指数バックオフ等) タイムアウト設定 [ ] モデル非推奨・廃止のスケジュールを把握しており、利用不可能になる前にモデルを更新するプロセスが確立している 2. スケーラビリティとパフォーマンス トラフィックの増減に対応し、効率的なリソース利用を目指します。 2-1. キャパシティプランニング [ ] コンテナ実行環境でオートスケーリングが設定されている [ ] 利用するLLM APIプロバイダーのレート制限(TPM: Tokens Per Minute / RPM: Requests Per Minute)を把握し、自システムのキャパシティプランに反映されている プロバイダー側の制限値を文書化 制限値を超えないための流量制御(レートリミッター)の実装 2-2. トラフィック・トークン管理 [ ] ビジネス要求の変化に伴うトラフィック増加とその影響について、ステークホルダーと認識を揃えている [ ] リリース時点で必要なスループットと、システムの最大許容スループットを算出している [ ] 1セッションあたりの消費トークン数の見積もりを算出している 入力トークン数(プロンプト + ユーザー入力 + コンテキスト) 出力トークン数(予想される生成テキスト長) 3. 耐障害性と大惨事対応 システムの一部が故障しても、サービス全体が停止しないようにします。 3-1. 障害シナリオ [ ] 想定される障害シナリオが洗い出され、文書化されている LLM API障害(タイムアウト、5xx エラー、レート制限超過) 依存サービス障害(データベース、外部APIなど) ネットワーク障害 リソース枯渇(メモリ、CPU、ディスク) [ ] 各障害シナリオに対応したRunbook(対応手順書)が作成されている [ ] 障害発生時の影響(ビジネス損失、ユーザー影響等)を見積もっている [ ] LLM API起因による長時間の回復困難な障害が発生した場合の対応方針についてステークホルダーと合意されている 3-2. LLM APIの可用性 [ ] システム要求(即時性、UX等)とLLM APIの可用性を踏まえたLLM処理コンポーネントの呼び出し方式(同期/非同期)が選択され、システムアーキテクチャに反映されている。 [ ] LLM API障害時のフォールバック・リトライ戦略が実装されている エラー種別ごとのリトライ可否判定(429はリトライ、4xxの多くはリトライ不可等) Exponential Backoffによるリトライ間隔の調整 Circuit Breakerパターンの導入(連続失敗時に一時的にリクエストを停止) [ ] LLM APIの冗長化が検討され、必要に応じて実装されている 複数のLLMプロバイダーまたは複数のモデルを利用可能にする フェイルオーバーの仕組みを実装 3-3. 回復性テスト [ ] 負荷テストを実施し、想定トラフィック下での挙動を確認している [ ] 主要な障害シナリオを再現させた際、その障害が検知・アラートされることを確認している(カオステスト) 4. 安全性 LLMが生成する有害なコンテンツや不適切な振る舞いを防ぎます。 4-1. リスク分類とインシデント対応 [ ] ユースケースに合わせた安全性リスク分類が定義されている 例: 誤情報、バイアス、プライバシー侵害、非倫理的なアドバイス、有害コンテンツ(ヘイトスピーチ、暴力的表現等) [ ] 安全性に関するインシデント発生時の対応プロセス(Runbook)が定義されている インシデントの検知方法 エスカレーションフロー 緊急時の対応(プロンプトの切り戻し、機能の一時停止等) 4-2. ガードレール [ ] 安全性リスク分類がガードレールのルールに反映されている [ ] 入力ガードレールが実装されており、以下を検知・ブロックできる プロンプトインジェクション攻撃 不適切なトピック(暴力、ヘイト、性的コンテンツ等) 機密情報(PII等)を含む入力 [ ] 出力ガードレールが実装されており、モデルが生成した以下のコンテンツをユーザーに届ける前に検知・フィルタリングできる 有害コンテンツ(ヘイトスピーチ、暴力的表現等) 機密情報(PII、APIキー等) 明らかなハルシネーション(事実と異なる情報) [ ] ガードレールで検知された違反はすべて記録されている 4-3. Red Teaming [ ] 定期的なレッドチーミング(敵対的テスト)が計画・実行されており、新たな脆弱性を継続的に探索するプロセスが確立している ジェイルブレイク(システム制約の回避) 巧妙なプロンプトインジェクション 複数ターンにわたる対話での安全性回避 5. 監視 システムの状態を可視化し、問題に迅速に対応できるようにします。 5-1. アプリケーションロギング・トレーシング [ ] アプリケーションログが構造化ログ(JSON等)で出力されている [ ] リクエストごとに一意のIDが生成され、ログとトレースに出力されている 分散トレーシング対応(トレースID、スパンIDの伝播) 上流サービスから受け取ったIDを継承 [ ] APM(Application Performance Monitoring)ツールでエンドツーエンドのトレースが可能になっている 5-2. 可視化とアラート [ ] 主要メトリクスを一覧できるダッシュボードが整備されている システムメトリクス: CPU、メモリ、リクエスト数、レイテンシ ビジネスメトリクス: 利用回数、成功率、ユーザー満足度 LLM固有メトリクス: トークン消費量、API利用料金、エラーレート [ ] サービスレベル指標(SLI)と目標(SLO)が定義されている 例: 可用性 99.9%、P95 レイテンシ < 3 秒、エラーレート < 1% [ ] SLO違反やシステム異常(エラーレート急増やリソース逼迫)時にアラートが発報される [ ] LLMガードレール違反検知数や検知率をダッシュボードで確認できる [ ] LLMガードレールにより検知された違反を重大度に応じてアラート通知する仕組みが確立している 5-3. LLM Observability [ ] LLM APIへのリクエストが分散トレーシングシステムと統合されており、同一リクエスト内のLLM呼び出しがスパンとして関連づけられている [ ] LLM APIへのリクエスト単位で以下の情報がトレース可能になっている 入力プロンプト(機密情報をマスキング済み) 出力レスポンス(機密情報をマスキング済み) 利用モデルとパラメータ(temperature、max_tokens等) レイテンシ(TTFB: Time To First Byte、全体レスポンス時間) 消費トークン数(入力/出力) 概算コスト [ ] トークン消費量とコストの急増を検知するアラートが設定されている 6. 品質保証(QA)と評価プロセス LLMの出力品質と安定性を客観的に担保します。 6-1. オフライン評価セット [ ] LLM出力を評価するためのデータセット(ゴールデンセット)が定義・管理されている 代表的なユースケースをカバーする入力と期待される出力のペア データセットの保存場所と管理方法が明確 [ ] エッジケース、既知の失敗パターン、敵対的入力を評価するデータセットが定義・管理されている 6-2. オフライン評価指標 [ ] タスクの目的に沿った具体的な評価指標が定義されている 分類タスク: Accuracy、Precision、Recall、F1-score 生成タスク: BLEU、ROUGE、人手評価(流暢性、正確性、関連性) 安全性: 有害コンテンツ検出率、PII漏洩率 6-3. オンライン評価 [ ] 本番環境での評価項目について導入の必要性を検討し、適切に導入している Direct Feedback: ユーザーからの明示的なフィードバック(👍/👎等) Functional Correctness: 生成結果が機能的に正しいか User Acceptance: ユーザーが結果を採用したか(コピー、編集、実行等) Achieved Impact: ビジネスKPIへの影響 Incidental Metrics: レイテンシ、エラーレート、コスト等 6-4. 継続的改善 [ ] 評価データセットを継続的に更新するための仕組みが整っている 本番トラフィックから評価データセットへのサンプリング 失敗ケースの追加プロセス [ ] 評価データセットのバージョン管理がされている [ ] プロンプトバージョン・モデルバージョン・評価データセットバージョン・パラメータを1単位として評価結果を管理する仕組みが整っている 実験管理ツールで各バージョンの組み合わせと評価結果を記録 6-5. 自動テスト [ ] LLM API呼び出しを含む主要なE2Eテスト(正常系)が自動化されている [ ] プロンプトやモデル変更時に、品質評価セットを用いた回帰テストが自動化または半自動化されている [ ] 負荷テストが自動化または半自動化されており、リリース前に負荷テストを行う基準がチーム内で合意されている 7. セキュリティとコンプライアンス 不正アクセスやデータ漏洩、意図しないモデルの振る舞いを防ぎます。 7-1. データ保護 [ ] LLMへの入力データのうち、PII等のセンシティブ情報が分類され、マスキング対象が特定されている 分類基準(例: PII、機密情報、公開情報)が定義されている マスキング対象のデータ種別(例: 氏名、メールアドレス、電話番号、住所等)がリスト化されている [ ] 転送中および保存データが暗号化されている 転送中: TLS/HTTPS 保存時: ディスク暗号化、データベース暗号化 7-2. プロンプトインジェクション対策 [ ] プロンプトインジェクション対策として、多層防御の仕組みが実装されている 入力検証・サニタイゼーション プロンプトテンプレートの使用(ユーザー入力を直接連結しない) 出力の検証・パース [ ] ユーザー入力とシステム指示が明確に分離されている システムプロンプトとユーザー入力の役割分離(system role / user role) 7-3. アクセス制御 [ ] LLM APIの認証に、長期的なAPIキーやサービスアカウントキーを使用せず、一時クレデンシャルによる認証方式を利用している [ ] APIへのアクセスが適切に認証・認可されている ユーザー認証(必要に応じて) リクエスト元の検証 7-4. モデルのリスク管理 [ ] ユーザー入力がLLMの学習データとして利用されないよう設定されているか、リスクが評価・受容されている LLMプロバイダーのデータ利用ポリシーを確認 オプトアウト設定(必要に応じて) [ ] 社内のモデル利用ポリシーに沿って利用モデルを選定している [ ] Open‑weightモデルを利用する場合、Pickle形式ではなくsafetensors形式を利用している 8. ドキュメントと組織運営 チームメンバーがシステムを理解し、スムーズに運用できるようにします。 [ ] システム概要・システム構成図・依存関係図が最新の状態に保たれている [ ] 主要なアラートへの対応手順(Runbook)や障害対応プロセスがドキュメント化されている [ ] 障害発生時のインシデント対応プロセスが定義されており、インシデント発生後のポストモーテム(振り返り)を実施し、再発防止策を講じるプロセスが確立している タイミーでの実装ガイド 本ガイドは、上記チェックリストを踏まえ、DS Gの技術スタック(Google Cloud、Datadog等)を踏まえた具体の実装例に落とし込んだものです。 1. 安定性・信頼性 構成管理 コード管理 : Git(GitHub)でコード管理 プロンプト管理 : Gitでプロンプトファイルを管理、またはプロンプトバージョニング機能を持つツールを使用 CI/CD CI : GitHub ActionsでLint・テスト・SASTを実行 CD : GitHub ActionsからGoogle Cloud(Cloud Run)へ自動デプロイ リリースプロセス 段階的ロールアウト : Cloud Runのトラフィック分割機能、またはリリース管理ツールを使用 2. スケーラビリティとパフォーマンス キャパシティプランニング オートスケーリング : Cloud Runの「最大インスタンス数」「最小インスタンス数」「同時実行数(concurrency)」を設定 3. 耐障害性と大惨事対応 回復性テスト 負荷テスト : Locustを負荷テストツールとして使用 4. 安全性 ガードレール 違反記録 : 構造化ログまたはDatadogイベントとして記録 5. 監視 アプリケーションロギング・トレーシング APM : Datadog APM(Cloud Runのサイドカーに datadog/serverless-init を設定) 可視化とアラート ダッシュボード : Datadog Dashboard、Looker アラート : Datadog Monitor、Slack連携 LLM Observability LLM監視 : Datadog LLM Observability 6. 品質保証(QA)と評価プロセス オフライン評価セット データセット管理 : Datadog LLM ObservabilityのDatasets機能の利用、またはBigQuery・GCSをベースとしたデータセットのバージョン管理 継続的改善 実験管理 : MLflow、Datadog LLM Observability 7. セキュリティとコンプライアンス アクセス制御 認証 : Workload Identity Federation(GCP)等 モデルのリスク管理 タイミー社内の基準を満たすモデルを利用している 振り返りと今後の取り組み これまでDS Gでは、データサイエンティスト中心のCSチーム(Complicated Subsystem Team)とMLOps(Platform Team)がCollaborationモード(Embedded SREに近い形)の関わり方で、LLMアプリケーションの構築・運用に取り組んできました。これは初期の知見蓄積と開発のアジリティ確保の点で、不可欠で有効なものでした。 しかし、今後サービスが増加するにつれ、この密な連携体制が組織全体のスケールのボトルネックになる懸念がありました。そうした課題を乗り越えるため、MLOpsチームは、CSチームが高速に高品質なアプリケーションを構築できるようなゴールデンパスの整備を進めています。 今回のチェックリスト作成は、その一つの取り組みとして知見を整理し、また現状の課題を再認識するものになったと考えています。 今後DS Gでは、RAGやAgentic Workflowのような、より複雑性の高いアプリケーションの構築や、LLM Gatewayといった基盤導入が控えています。 そうした複雑な課題に対し、CSチームとMLOpsチームがそれぞれの強みを発揮し、協調しながらスケールしていける体制が不可欠だと考えています。 本チェックリストの活用・改善を含め、常により良い協業の形を模索し、再現性高く信頼性のあるML/LLMアプリケーションによる価値創出にチャレンジしていきたいと思っています。
アバター
こんにちは!株式会社タイミーでアナリティクスエンジニアをしているひろろと、データエンジニアをしているつざきです。 本記事は、二人の共同執筆という形式でお届けします! 現在、私たちはアメリカ・ラスベガスで開催されているdbtの祭典 「Coalesce 2025」 に現地参加しています! Coalesce 2025 は、年に一度世界中のdbtユーザーが集まり、機能アップデートや事例発表、ハンズオンなどが行われるdbtの技術カンファレンスです。 当社ではデータ基盤にdbtを採用しているため、最新技術のキャッチアップを積極的に行っています。Coalesceへの参加もその一環として決まりました。 ※アーキテクチャのついて詳しくは こちら また、今回はカンファレンス参加支援制度 「KaigiPass」 を利用して参加しております! 速報を現地の熱気とともにお届けします。ぜひご覧ください。 Day 1 初日には有料のトレーニング中心で、夕方からカンファレンスのキックオフとウェルカムパーティーが開催されます。 キックオフでは近くにいる人との自己紹介タイムがあり、最初は焦りましたが、みんな技術者同士。英語が苦手でも温かく受け入れてもらえる優しい雰囲気でした。 Coalesceのイベント内容や会場案内などを中心に紹介され、これから始まるセッションへの期待が高まる良い時間でした。 (決してモーニングやランチが出ることに喜んでいるだけではありません笑) キックオフの後は、ウェルカムパーティーの開催です!立食パーティーの形式で実施され、パートナーブースで話を聞いたり、他の来場者との会話を楽しんだりできます。 同じ日本人の参加者とも交流することができ、データに関する話題で盛り上がりました。日本開催のカンファレンスとは違い、日本人が少ない分、数少ない日本人同士で仲間意識が芽生え、より深い話ができたように感じます。 また、パートナーブースの方も熱気がすごく盛り上がっていました。日本で開催されるカンファレンスよりも、皆さんのテンションが高いです。実際にブースへ行ってみてデモをみせてもらうだけでもためになります。どのブースにもAIという文字が記載されているのが印象的でした! Day2 初日はKeynoteや数多くのセッションに参加しました。セッションの数がとても多いため、吟味するのがとても大変です。また、ランチの時は同じテーブルに座った方が気兼ねなく話しかけてくるので、そこでの会話も貴重な時間でした! Opening Keynote Keynoteはホテル内のシアター会場で行われました。前方席に座ったのですが、両サイドのスクリーンにリアルタイム字幕が表示されていたため、後方の方が見やすかったと後で気づきました。 Opening Keynoteでは「 Rewrite 」というキーワードが繰り返し登場し、印象的でした。dbt LabsのCEO、Tristan Handy氏をはじめとした登壇者たちが、AI時代におけるデータワークの再定義について語りました。 まず大きな話題として取り上げられたのが、Fivetranとの合併についてです!前日の10/13に発表されたばかりのため、この場で話を聞けることを楽しみにしていたのが、歓声から伝わってきます。 その流れで呼ばれたのが、FivetranのCo-Founder & CEOであるGeorge Fraser氏。Fivetranのコネクタバグ修正の話から始まり、dbtに対して初めは懐疑的だったが変わっていったこと、dbtをAIが今後重要となっていく中で将来性や相互運用性が合併の背景としてあること、dbtへの破壊的な変更はないことなどをトーク形式で語っていき、dbtユーザーを安心させ一緒にdbtを盛り上げていきたいという気持ちがとても伝わる内容でした。 次に、Metric Flowついて大きな発表がありました。 MetricFlowは、dbt Labsが提供するメトリクス管理の製品です。今回、Apache 2ライセンスでのオープンソース化が発表され、会場から大きな拍手が起こりました。 「ベンダーロックインが懸念で導入に踏み切れなかった」という声も多く、まさにその課題に応える発表でした。 参考URL: https://www.getdbt.com/blog/dbt-labs-affirms-commitment-to-open-semantic-interchange-by-open-sourcing-metricflow その次に語られたのが、 dbt Fusion エンジン になります。 dbt Fusionエンジンは「Rewrite Developer Experience」をテーマに、開発体験の抜本的な刷新を目指した機能群として紹介されました。 実際のデモでは、従来より最大30倍のコンパイル高速化や、CTEプレビュー・カラムレベルのエラー検知などが披露され、歓声が上がる場面もありました。 機能についての詳細は 公式ドキュメント をご覧ください。 SDF Labsの共同創業者で、dbt Fusionエンジンのプロダクトリーダーを務めているElias DeFaria氏によるライブデモでは、dbt Fusionエンジンがもたらす多くの開発支援機能が実際に披露され、会場からは何度も歓声が上がりました! 「Column rename propagation」など機能が発表される度にドヤ顔をしている担当者を近くで見られることは現地参加の醍醐味ですね! 特に、新たに発表された「Compare changes」は、変更を本番環境に適用する前にデータへの影響を確認することができます。本番にマージしてからデータがおかしくなってしまっていることに気がつく、といった肝が冷えるやらかしを削減できる素晴らしい機能だと思いました! 次に発表されたのは、State-aware orchestration です。 State-aware orchestrationは、Fusionの機能を活かしたインテリジェントな実行制御機能です。依存関係の入力データが変更されたモデルのみを再構築することで、無駄な再実行を防ぎます。 発表によると、この機能を有効化するだけで約10%のコンピュートコスト削減が見込まれるとのことです。(すごい!) また、State-aware orchestrationのさらなる最適化として、Tuned configurationが紹介されました。 Tuned configurationでは、ユーザーは「Freshness要件の宣言」をすることができ、年間コンピュートコストを少なくとも29%削減できる見込みが示されました。 dbt内部ではこれらの最適化を導入することで最終的に64%もコストを削減できたとのことです。すごすぎる! そして、最後に発表されたのがdbt Agentsです。 dbt Agentsでは4つの新しいサービスが発表されました。 サービス名  機能・コメント 状態 Developer Agent (開発者エージェント) 変更内容を記述するだけで、エージェントがそのタスクを実行し、SQLファイルの作成、テスト、ドキュメント化を自動で実行してくれるとのこと!アツイ! 近日公開 Observability Agent (可観測性エージェント) パイプラインの障害箇所を特定し、修正を支援してくれるエージェントです。これによってトラブルシューティングに費やす時間を削減しアラートの削減につながることが期待されます 近日公開 Discovery Agent (発見エージェント) カタログやドキュメントから承認済みのデータセットと定義を識別し、なぜそのデータが信頼できるのかを示します。ただの検索ではなく信頼性の高いデータを見つけるまでの時間が削減できるとのことです ベータ版 Analyst Agent (アナリストエージェント) 会話型分析(conversational analytics)の構築を支援し、複雑な質問に対してガバナンスされた回答を提供してくれるとのことです ベータ版 全てのAgentが、データ分析開発における「あったらいいな」を実現できる機能のように思えました。特に個人的には、Developer Agentは、テキストで指示するだけで自律的に実装を行ってくれる、おそらくDevinのような開発体験ができるのかなと感じていて期待が膨らみます。 まとめ 本記事では、Coalesce 2025の現地の様子とOpening Keynoteの内容を中心に紹介しました! dbt LabsとFivetranの合併、Metric FlowのOSS化、Fusionエンジンによる高速化、State-aware orchestrationによるコスト最適化など、「Rewrite(書き換え)」というテーマにふさわしい革新が詰まったセッションでした。 今後、残りのセッションで各機能の詳細や導入事例を調査し、続報をお届けする予定です。ぜひご期待ください!
アバター
はじめに こんにちは!今回は( @arus4869 、 @yoshi_engineer_ )の2人で執筆しました! この記事では、私達のチームが『スプリントゴールで価値を駆動しよう』 という書籍の輪読会をきっかけに、形骸化しがちだったスプリントゴールを 「チームの羅針盤」 として機能させ、スプリントの達成率を 体感値で50%から80%に向上させた具体的な実践記録をご紹介します。 この記事は、特に以下のような課題を感じている方に読んでいただきたいです。 スプリントゴールが「タスクリスト」になりがちで、なぜやるのか(Why)やどんな価値が生まれるのか(Outcome)が曖昧になっている。 スクラムを実践中で、スプリントゴールの質に悩んでいる。 ステークホルダーやチームメンバーと機能の話はできても、 「価値」についての会話 がしづらい。 なぜこの本を選んだのか 私達のチームも、まさに上記のような課題に直面していました。 スクラム開発を長く続けているものの、いつしか「フィーチャーファクトリー」のようになり、スプリントゴールが単なるタスクリストになっていました。チームとしてやりたいことが多すぎて常にバックログを積みすぎる傾向があり、「レビューで何を学んだか」をアウトカムで語ることが難しく、アウトプットの話に終始しがちでした。 この「なんとなくこなすだけ」の状態を脱却したい。そんな思いで、スプリントゴールを考え直すヒントを与えてくれそうな本書を、輪読会のテーマとして選びました。 そもそもスプリントゴールとは何か? スプリントゴールはなぜ設定されるのか知っていますか?私は説明することができませんでした。 スクラムガイドでは以下のように定義されています。 スプリントゴールはスプリントの唯⼀の⽬的である。スプリントゴールは開発者が確約するものだが、スプリントゴールを達成するために必要となる作業に対しては柔軟性をもたらす。スプリントゴールはまた、⼀貫性と集中を⽣み出し、スクラムチームに⼀致団結した作業を促すものでもある。 スプリントゴールは、スプリントプランニングで作成され、スプリントバックログに追加される。開発者がスプリントで作業するときには、スプリントゴールを念頭に置く。作業が予想と異なることが判明した場合は、スプリントゴールに影響を与えることがないように、プロダクトオーナーと交渉してスプリントバックログのスコープを調整する。 スプリントゴールはスプリントの 唯一の目的 。 スプリントにおいて、この目標を達成することこそが大本命なわけです。 では、その重要性を深ぼっていきましょう。 スプリントゴールがなぜ必要なのか? スプリントゴールの重要性、まず挙げられるのは「チームの思考をタスクベースの 「アウトプット」から、ユーザーに提供する「アウトカム」へと転換 させる」ことです。 チームは 「ユーザーへの価値の提供」を本来の目的として行っています。実際のチームを稼働していく際に、いつの間にかタスクリストのみを指針として作業を進んでしまう 可能性があります。 例えば、「ユーザー登録画面の実装」や「データベース登録処理」といったタスクの羅列だけでは、開発者はなぜその作業を行っているのか、その作業が最終的にどのようなユーザー価値に貢献するのかがチームメンバーが共通して理解しにくい場合があります。 スプリントゴールがあることで、ユーザーに届けたい価値,アウトカムがタスクと連結して、一つの具体的なインクリメントを生み出すという明確な意図が共有されます。 このようにチームは 「タスクの進捗」 だけでなく、 ゴールへの貢献度 を常に意識できるようになり、より良い意思決定と協調性を生み出す。 スプリントゴールの効能 スプリントゴールがチームに対してもたらす効能は他にもあります。それは 「集中」 と 「一貫性」です。 チームに明確なゴールがなければ、チームはプロダクトバックログアイテム(PBI)がスプリントバックログに含まれる場合、それぞれのPBIが独立した作業として捉えられ、チームがばらばらに作業するリスクがあります 。このような状況では、チームとしての協調性が生まれず、特定のPBIを担当する個人が孤立したり、作業の詰まりが発生したりする可能性があります 。 スプリントゴールは、これらのPBIを「なぜ我々はこれらを同時に進めるのか」という 共通の目的に集約します 。共通の目的を持つことで、チーム全員が同じ目標に向かって努力し、一体感を高めることができます。 これにより、チームメンバーは 「ゴール達成のために誰かを助ける」 という動機付けが生まれ、協力して問題を解決する姿勢が自然に育まれます。個々のタスクの完了ではなく、チーム全体のゴール達成に焦点を当てるという、スクラムの本質を体現します。 良いスプリントゴールを建てるために ここでは タスクベースのゴール設定 と 状態ベースのゴール設定 についてお話しします。タスクベースのゴールでは、チームメンバーが「自分のタスクが終わらなかった」という個人の反省に終始し、ネガティブな内省に陥りやすい可能性があります。 スプリントゴールを「モヤモヤが解消された状態」や「特定の価値が提供された状態」といった「状態」ベースで設定すると、チームはプレッシャーから解放され、より前向きな振り返りがしやすくなります。 また、ゴールが未達成であっても、チームは 「なぜその状態に至らなかったのか」という根本的な原因を客観的に分析する ことができ、これを次のスプリントでの改善点として捉えることができます 。 このようなプロセスは、チームメンバー間の信頼関係を深め、失敗を学習の機会と捉える健全な文化を醸成します。スプリントゴールの達成に向けて互いに助け合う習慣は、 チーム全体の協力体制を強化 し、 個々のモチベーションを維持する 上で重要な役割を果たします 。 スプリントゴールを駆動して成功の鍵として活用していくために スプリントゴールが単なるタスク管理ツールではなく、 チームの集中力、柔軟性、そして価値創出を最大化するための不可欠な要素 です スプリントゴールを 「タスクのリスト」 から 「成果を示す羅針盤」 へと進化させることで、 チームは「何をするか」から「何を成し遂げるか」へと意識を変えることができます。 何を実践したのか(輪読会での取り組み) 輪読会での学びを元に、私達は以下の3つの具体的なアクションをチームに導入しました。 1. スプリントゴールの「決め方」を合意形成のプロセスに変えた これまで曖昧だったスプリントゴールの決め方を、チームで合意を形成するための明確なプロセスに変更しました。具体的には、スプリントプランニングで以下のステップを踏んでいます。 全員でスプリントゴール案を1つ考える: まず、POだけでなくチーム全員でスプリントゴール案を出します。 自信度の可視化と投票: そのスプリントゴール案に対して、達成できる自信度を各自が表明し、参考情報として投票を行います(多数決でスプリントゴールを決めるわけではありません)。 対話と議論: なぜその自信度なのか、なぜその案が良いと思うのかを全員で対話し、納得解を探ります。 「FOCUS」で最終チェック: 最後に、そのスプリントゴール案が良いゴールの特性(※)を満たしているか、チームでチェックし、最終決定します。 ※FOCUSとは? 良いスプリントゴールの特性を示した頭字語。Fun(楽しい), Outcome-based(アウトカムベース), Collaborative(協調的), Ultimate(究極的), Singular(単一)の5つの観点。 このプロセスにより、スプリントゴールが「誰かが決めた目標」から「自分たちが合意した目標」に変わりました。 2. 「やること」を勇気をもって絞り込んだ 過去の私達は、スプリントゴールに集中できずに品質を低下させたり、ゴール自体を達成できない経験がありました。本から「同時に多くの作業に取り組むことは、協働を妨げ、価値提供を減少させる」 という原則を学び、輪読会中にチームの 合意として以下のルールを決めました。 「スプリントプランニングでスプリントバックログに入れるのは、スプリントゴール達成に直接貢献するPBIだけにする」 これにより、チームのエネルギーが分散することなく、全員がゴール達成に集中できるようになりました。 3. レビューで「データ」を語ることを始めた 価値(アウトカム)を意識するため、スプリントレビューで関連するプロダクトのダッシュボードを参加者で見ることから始めました。まだ道半ばですが、これにより会話が「何を作ったか(Output)」から「それによって何が変わりそうか(Outcome)」へと少しずつシフトするきっかけになっています。 やった結果どうだったか(輪読会の成果) これらの取り組みの結果、チームには明確な変化が生まれました。 スプリントゴール達成率が向上: あくまで体感値ですが、スプリントゴールの達成率は 約50%から80%へと向上 しました。 プランニングの質が向上: 「このPBIはスプリントゴール達成にどう繋がるんだっけ?」という問いかけが自然に生まれ、バックログ選択の迷いが減少しました。 デイリースクラムの会話が変わった: 単なる進捗報告ではなく、「スプリントゴールを達成するために、今日は〇〇をしようと思う」といった、ゴールを意識した会話が中心になりました。 参加者の声 輪読会の参加者の声を抜粋します。 総合的に輪読会に参加して良かったという感想になりました。 「不確実性への対処」における説明やそれに対するソリューションが大変学びになった。 一人で読むとインプットだけで終わりがちですが、チームですぐに実践できるのが輪読会の良いところだと感じました。 FOCUSは一つのHOWに過ぎないと思いつつ、輪読会から具体的なアクションが生まれたのはとても良いな〜と思いました。 価値提供を重視し、FOCUSの考え方でゴールを打ち立てることが本を通じて印象に残りました。 まとめ 今回の輪読会と実践を通して、私達は重要な学びと気づきを得ることができました。 経験からの学び: 良いスプリントゴールとは、優れた文章以上に、 チームで『何に集中し、何を顧客に届けるのか』を合意するプロセスそのもの でした。 気づき: チームが共通のゴールに集中することで初めて、 日々の行動が価値ある「得点」に繋がる のだと気づきました。 この記事で紹介した私達の「スプリントゴールの決め方プロセス」が、その一助となれば幸いです。 最後までお読みいただき、ありがとうございました。
アバター
こんにちは、iOSエンジニアの大塚、山崎、早川( @hykwtmyk )、前田( @naoya_maeda ) です。 2025年9月19-21日に有明セントラルタワーホール&カンファレンスで開催されたiOSDC Japan 2025に、タイミーもゴールドスポンサーとして協賛させていただきました。 イベントは以下のように、3日間連続で行われました。 9月19日(木):day0(前夜祭) 9月20日(金):day1(本編1日目) 9月21日(土):day2(本編2日目) 私たちもイベントに参加したので、メンバーそれぞれが気になったセッションや感想をご紹介します。 大塚 編 先日、iOSDC Japan 2025に参加しました。今年は会場が例年とは異なり、カンファレンス全体の雰囲気にいつもと違った新鮮な印象を受けました。私は毎年iOSDCに参加していますが、日々の業務や自己学習だけでは触れる機会の少ない、高度でニッチな領域の知識を集中的に得られる点が、本カンファレンスの最大の魅力だと感じています。本レポートでは、その中でも特に印象に残った「金融サービスの成長を支える "本人確認フロー" の改善と取り巻く環境の変化」のセッションについてご報告いたします。 概要 セッションは、1. 本人確認フローの改善と2. 法令改正など取り巻く環境の変化についての発表でした。 1. 本人確認フローの改善 前半は、JPKI方式を用いた本人確認の実装戦略についての解説でした。リードタイムを短縮するため、マイナンバーカードの読み取りおよび電子証明書の検証といった機能は、外部ベンダーが提供する SDK を用いて実装しているそうです。一方で、ユーザーが直接触れるUI部分はあえて自社で開発するという戦略を採っています。UIを自社で開発することで、離脱率を改善するための様々な実験的な施策をスピーディーに導入し、その最適化のメリットを享受されているそうです。 2. 法令改正など取り巻く環境の変化 後半は、金融サービスを取り巻く犯収法などの法令改正がいかにアプリ開発に影響するかについてです。特にJPKIの署名用電子証明書を用いる方式やカード代替電磁的記録を用いる方式など、外部環境の変化にエンジニアがどう対応すべきかに焦点を当て解説されていました。 感想 私が所属するチームで担当している本人確認フローに関する発表だったため、特に強い関心を持って拝見しました。他社ではどのようにJPKI方式を実装し、離脱率の改善という難しい課題に対し、どのような観点で技術やプロダクト設計の選択を行っているのかを具体的に知ることができ、大変勉強になりました。 山崎 編 iOSDCは今回初めて参加させていただきました! 初参加だからこそ感じた現地の熱気やセッションの感想などをレポートします。ちなみに、私はday0とday2の2日間参加しました。 day0:カンファレンスの熱気に触れる day0ではまず、プレオープニングセッションに参加し、iOSDCの歴史やコミュニティの成り立ちについて知ることができました。多くの開発者の情熱によってこの素晴らしい場が作られていることを知り、参加できることへの感謝の気持ちで胸がいっぱいになりました。 その後は、弊社の岐部さん( @beryu )の発表を応援しに行ったり、興味のあるセッションをいくつか拝見しました。どれもレベルが高く、知的好奇心を強く刺激される内容ばかりでした。 day2:ブースでの交流 day2では、弊社のブースに立ち、たくさんの参加者の方々とコミュニケーションを取らせていただきました。 私たちのブースでは、「最近うまくいったこと」をテーマに付箋を書いてもらう、という企画を実施していました。これをきっかけに会話が弾み、「最近、SwiftUI化がうまくいったんです」「AIをこう活用して業務改善しました」といった具体的なお話をたくさん伺うことができました。特にAI活用のトピックは多くの方の関心事のようで、他社さんのユニークな取り組みを聞けたのは大きな収穫でした。 心に残ったセッション 業務で直接関わる機会の少ない領域でのセッションを機会が多かったのですが、どれも新鮮で非常に面白かったです。 手話ジェスチャーの検知と翻訳~ハンドトラッキングの可能性と限界~ fortee.jp Apple Vision Proでの立体動画アプリの実装と40の工夫 fortee.jp プログラマのための作曲入門 fortee.jp 「iPhoneのマイナンバーカード」のすべて fortee.jp 特に、Apple Vision Pro関連のセッションは、従来のアプリ開発とは異なり、3D空間上でUIやUXをいかに自然に見せるかという点に多くの工夫が凝らされており、大変興味深かったです。オブジェクトを空間に違和感なく配置することの難しさを改めて感じました。 マイナンバーカードの話は、実際にどのようなAPIが使われているのかという技術的な解説が非常にためになりました。アプリ内、対面、ブラウザの3種類のVerifier APIの具体的な使い方やユースケース、さらにはmdocのデータ構造や日本のために作られたAPIの紹介など、普段は知ることのできない実装の詳細に触れることができ、とても貴重な体験でした。 SwiftコードバトルとLT大会の熱気 Swiftコードバトルは、制限時間内に要件を満たすプログラムを、Swiftでいかに短く書くかという競技で、緊張感のある中、参加者の方々のコーディングスピードと発想力に圧倒されました。 LT大会は、参加者がサイリウムを振って会場全体が一体となる、ライブのような賑やかな雰囲気でした!特に印象的だったのは、課金周りのLTが続いた時間帯です。各社それぞれの知見や工夫が凝縮されており、とても勉強になりました。 初めてのiOSDCは、技術的な学びはもちろん、何よりもコミュニティの温かさと熱量を肌で感じられる最高の体験でした。ブースでの交流、刺激的なセッション、そしてユニークな企画までとても熱狂感のある素晴らしいイベントでした! 早川 編 タイミーでiOSエンジニアをしている早川です。 一昨年ぶりにiOSDC Japan 2025にオフライン参加してきました!今年から会場が変わり、またいつもと違う雰囲気でしたね(個人的には、自宅から近くなったので嬉しいです)。 さて、いくつかのセッションを聴講した中で、特に学びが多かったのが、デスクスさんが発表された「 5000万ダウンロードを超える漫画サービスを支えるログ基盤の設計開発の全て 」です。 speakerdeck.com 大規模サービスにおけるログ基盤という、普段なかなか知ることのできない領域について、詳細なコードベースでの解説や実践的な知見が共有されており、多くの学びがありました。本記事では、発表内容の要点と特に印象に残った点についてまとめていきます。 発表の要点 1. 新ログ基盤「Tracker」の設計思想と特徴 パフォーマンスと柔軟性の向上を目的とし、モダンな技術スタックを全面的に採用。Swift 6の Sendable に準拠した設計をベースに、並行処理による高いパフォーマンス、gzip/deflateによるデータ圧縮、そしてモバイル環境の不安定なネットワークを考慮したExponential Backoffによる堅牢なリトライ戦略が主な特徴として挙げられていました。 2. Clean Architectureによる責務の分離 アーキテクチャにはClean Architectureを採用。Entity、Use Case、Data Layer、Public Interfaceを明確に分離することで、各コンポーネントが独立して機能し、テスト容易性や変更容易性が格段に向上したとのことでした。これにより、全体で80%以上という高いテストカバレッジも実現していました。 3. Firebase Remote Configを活用した安全な移行戦略 Firebase Remote Configをフラグとして利用し、旧基盤と新基盤を並行稼働させ、いつでも切り戻しが可能な状態で段階的に移行を進めるという、非常に現実的でリスク管理に優れたアプローチが紹介されました。 特に印象に残った点・感想 この発表では、ログ基盤というテーマに留まらず、大規模なモバイルアプリ開発全般に役立つ知見が随所に見られました。 ログの送信自体はユーザーの目に見えない部分ですが、メインスレッドをブロックしない、メモリを圧迫しないといった ユーザーファーストな視点 と、テスト容易性を高めるアーキテクチャ設計や高いテストカバレッジといった 開発者としてのこだわり が見事に両立されており、まさにユーザーと開発者の双方にとって「Win-Win」な内容だと感じました。 また、Firebase Remote Configの活用法も非常に印象的でした。特に「 将来的に全体公開する前提のConfigは、デフォルト値をtrueにしておくことで、移行完了後にConfigを削除できる 」という知見は、私自身も過去に逆の設計で失敗した経験があるからこそ、大変共感しました。 このように、自身の業務でもぜひ参考にさせていただきたい学びの多い、素晴らしい発表でした。 前田 編 僕は今年が二回目のiOSDCへの参加でした。今回も幅広いジャンルで興味深いトークがたくさんありました。その中で、僕が最も面白いと感じたセッションは、 かっくん さんの「独自UIで実現する外部ストレージデバイスの読み書き」でした。 fortee.jp 偶然なのですが、ちょうどこのトークの前日に、僕が個人的に調査を始めたトピックでした。 僕にとってはすごくホットなトピックだったこと、デモを交えた聴講者を惹きつけるトークが魅力的に感じたのでこちらのトークを選びました。 このトークは、iPhoneで撮影した写真や・ビデオデータを外部ストレージに直接保存する方法を紹介する内容です。まだ情報自体が少ないこともあり、Appleのエヴァンジェリストの方と一緒に調査を進めて、実装を実現したエピソードを交えたトークになります。 iPhoneのカメラで撮影した写真や・ビデオデータを保存する場所や方法は、大きく2パターンが用意されています。 一つ目の方法は、iPhoneのストレージに保存する方法ですね。こちらの方法は最もよく知られており、サンプルコードも豊富で難易度はそこまで高くありません。 PhotoKit フレームワークを使用することで、10行程度のコードで実現することができます。 zenn.dev 二つ目の方法は、iPhoneに接続している外部ストレージに保存する方法です。こちらの方法は、さらに2パターンに分かれています。 一つ目の方法は、 UIDocumentPickerViewController または、.fileExporter モディファイアを使用する方法です。これらのAPIを使用することで、データを外部ストレージに保存することができます。しかし、これらの方法では、ユーザーにデータの保存先を選んでもらう必要があります。ユーザーは、データの保存先を選択することができるメリットがある一方、ユーザーの操作数を増やしてしまうことになります。 そこで今回のトークで登場したAPIが、 AVExternalStorageDevice です。iOS 17から使用可能な比較的新しいAPIです。このAPIと関連APIを使用することで、iPhoneに接続している外部ストレージに、データを直接保存できます。実装コストも高くなく、数十行程度のコードで本機能を実現できることを知れて、僕にとっては学びが多いトークでした。 本トークでは、実際に会場で撮影した写真を、iPhoneに接続されている外部ストレージに直接保存し、保存したデータ一覧を表示するといったデモもあり、楽しくトークを聴くことができました。 最近では、高ストレージ端末を除いては、ProRes Logデータを撮影する時に外部ストレージとの接続が必須になってきています。今後、外部ストレージ接続が必須になる機能が増えるかもしれない状況になる可能性もあるので、さらに深掘りを進めてみたいと思います。 最後に この3日間を通して技術的な知見を深めたり、久しい友人に会って話をすることができ、すごく有意義な時間を過ごせました。この場を用意してくださったiOSDCスタッフの方々、参加者のみなさん本当にありがとうございました! 上記で紹介したセッション以外にも非常に興味深いセッションが多くありました。 記事にある内容や、その他の内容についても、もしタイミーのエンジニアと話したいという方がいらっしゃればぜひお気軽にお話ししましょう!
アバター
タイミーの江田、徳富、神山、亀井、桑原、志賀です。 KaigiPass という制度を利用して Kaigi on Rails 2025 に参加してきました。タイミーは昨年に引き続きスポンサーをさせていただき、今年はなんと幸いなことにブースも出させていただきました。今回はそんな Kaigi on Rails 2025 の参加レポートを KaigiPass を利用したメンバーで記述していこうと思います。 2分台で1500 examples 完走!爆速 CI を支える環境構築術 Hiroshi Tokudomi https://kaigionrails.org/2025/talks/falcon8823/#day1 「2分台で1500 examples 完走!爆速 CI を支える環境構築術」というタイトル通り、CI をどのように高速化していくかについての発表でした。 弊社でも現在 self-host runner を使って GitHub Actions 上で CI を回しており、2 万 2 千件のテストを平均 9〜10 分程度で実行しています。CI は短ければ短いほどいいものなので、このセッションタイトルを見たときに「これは絶対に見に行こう」と思ったのですが、まさに期待通り参考になる内容でした。 テストを並列に分割して実行する方法として Knapsack Pro が紹介されており、自分たちが使っている split-test と比較しながら聞くことができました。Knapsack Pro は実行時間の履歴をもとに分割を最適化できるとのことで、今後検討してみたいと思います。 また、EBS の I/O がボトルネックになることで CI が遅くなる話も印象的でした。弊社でも CI 実行が重なると I/O 使用率がギリギリまで上がることがあり、tmpfs を使うことで改善できる可能性があるのでは、とワクワクしながら聞いていました。 ちょうど最近 CI 基盤を self-host runner に移行したばかり( 参考 )ということもあり、「次はどこをチューニングできるか」と考えていたタイミングでのセッションだったので、個人的にはかなり刺さりました。CI をどう速くするかは日々の開発体験に直結するテーマなので、今回の学びを持ち帰って改善につなげていきたいです。 https://speakerdeck.com/falcon8823/2fen-tai-de1500exampleswan-zou-bao-su-ciwozhi-eruhuan-jing-gou-zhu-shu-kaigi-on-rails-2025 dynamic! Keisuke Kuwahara https://kaigionrails.org/2025/talks/moro/#day1 https://speakerdeck.com/moro/dynamic 1日目のキーノート「dynamic!」は、静的なアプローチとは対照的に、最初から完璧なゴールを目指すのではなく、継続的に変化していくプロセスを重視するテーマでした。 セッションでは、開発を細かく進めるための第一歩として、「ハッピーパス」の実装が重要だと強調されていました。ハッピーパスとは、ユーザーが使える「最小限かつ最もシンプルな機能」のことであり、これをまず実装することで、早い段階でフィードバックを得て、改善サイクルを回すことが可能になります。 特に印象的だったのは、開発者だけでなく、チーム全体で変化を起こしていくという考え方です。私自身、複雑な機能を実装する際、最初からすべてを網羅しようと時間をかけた結果、いざデモをしてみると多くのフィードバックをもらった、という経験があります。その時はできるだけ指摘をもらわないように実装しよう、という気持ちになっていましたが、今回の話を聞いて、動くものをいち早くチームメンバーやステークホルダーに確認してもらい、早くフィードバックを得ることの重要性を再認識しました。 またこれまでは、優秀なエンジニアほど、一発で完璧なプロダクトを作るものだ、と漠然と思っていましたが、実際は逆でした。経験豊富なエンジニアほど、最小限の開発から始め、段階的な改善を積み重ねていくのだと気づきました。 アジャイル開発は今のチームでは当たり前になっていますが、「動的に変化し続けること」の重要性を改めて深く感じました。完璧主義にとらわれて最初の実装に時間をかけるのではなく、変化を恐れずに、その時々で最もシンプルな方法を実践していきたいと思います。 「技術負債にならない・間違えない」 権限管理の設計と実装 Akitoshi Shiga https://kaigionrails.org/2025/talks/naro143/#day2 https://speakerdeck.com/naro143/ji-shu-fu-zhai-ninaranaijian-wei-enai-quan-xian-guan-li-noshe-ji-toshi-zhuang Webサービスにおいて頻出の課題である権限管理に対して、自社のプロダクトで行った取り組みを発表されていました。 権限管理のありがちなアンチパターンとして、権限の判定が必要な場所で操作者の役割を判定するような実装についてあげられていました。 例: あるControllerのcreateアクション内で if admin? 役割を判定する場合は、判定する箇所が役割ごとに何ができるかを把握する必要があります。 役割ごとの権限は、サービス内容や事業環境の変化が激しい状況においてはすぐに複雑化します。 その上、この実装では権限を変更するたびに、すべての判定箇所に手を加えなければいけません。 その結果としてすぐに保守が困難になります。 そこで、役割=何者であるかではなく権限=何ができるか に依存すること、その上で、権限管理に必要な要素である「対象」「操作」「役割」「条件」に対する記述を分離・明文化した設計を発表されていました。 具体的には、module分けした「対象」ごとに「役割」のクラスを作り、「操作」に紐づいたメソッド内で「条件」を判定する実装を行っていました。 これによって、対象ごとに役割が操作を行うための条件が非常にシンプルになりました。 https://speakerdeck.com/naro143/ji-shu-fu-zhai-ninaranaijian-wei-enai-quan-xian-guan-li-noshe-ji-toshi-zhuang?slide=64 その結果、カスタマーサポートやプロダクトマネージャーがGitHubでコードを読んで理解できるようになり、社内からのエンジニアへの権限に関する問い合わせが0になったそうです。 所感として、操作する対象が増えるたびにすべての役割のクラスを作成する必要があるものの、条件というコアなロジックは一箇所に集中されるので保守しやすそうだと感じました。 また、権限という複雑なビジネスロジックを非エンジニアが理解できるほどシンプルにコードで表現されていることに感銘を受けました。 複雑なビジネスロジックにちゃんと向き合ってシンプルな表現を目指すことの効果と大切さを今一度実感しました。 Railsによる人工的「設計」入門 Yutaka Kamei https://kaigionrails.org/2025/talks/nay/#day1 亀井です。万葉の大場さんのこちらのセッションが素敵だなと思ったので、このセクションを書かせていただいています。私なりにこの発表を解釈すると、設計がうまくなる方法について深掘りした内容だったのかなと思います。この AI 時代にコーディングを AI に任せることは増えてきた中で、人間が設計をできることの重要性がとても高まっています。そんな中で、どのようにしてもっとうまく設計ができるようになるのか?というのをご自身の偶然の経験から形式知にしてまとめてくださった内容でした。 完成したシステムを思い浮かべてそこから「逆算」して考えていくとよいのではないか?というのが重要そうでした。私の経験でも、ゴールを思い浮かべてそこに至るまでのマイルストーンを描きステップを重ねるというやり方がよいのではないか?ということを 思っていた ので非常に納得感があります。まさに全体から部分へ、という思考を行うことによってうまく設計ができる、という考え方ですね。たしかに、 Ruby on Rails が MVC をベースにしたフレームワークだからといって、モデル→コントローラー→ビューという順番に書く必要もないですし、むしろその順番で書くと全体が見えない状態で開発をすることになるので、次第につぎはぎだらけのコードが生成されてしまいます。その場しのぎのコードになってしまうと、やはりそれはそれで今後のメンテナンス性に影響してくるわけです。あるべき状態、あるいは本質的なゴールを描いてそこから「逆算」する、という考え方の言語化がとても素敵でした。 また、名付けの重要性について語っておられたのもよかったですね。 Ruby 界隈では「名前重要」というのはよく知られていると思いますが、設計の上でなぜ名付けが重要なのか?ということについての言語化もクリアでわかりやすかったです。たしか、コードのレベルではなく、抽象度の高いレベルで短い名前をつけながら考えを進めて「デザインパーツ」を取捨選択し「仮置き」することが重要というようなこともおっしゃっていたと思います。「仮置き」というワーディングがよいですね。たしかに、「決定」というと後戻りができない感じがしますが、「仮置き」というのは「変更の余地があること」を示唆する感じがして、日本人としてこういう日本語表現を利用できるのはとてもラッキーだなと思います。そういえば、「仮置き」で思い出しましたし、このセッションで参照されたキーノートの dynamic! にもあったように、オプショナリティーについて言及が最近増えましたね。「仮置き」という用語にもオプションというニュアンスを感じます。『Tidy First?』を読んで私なりにオプションについて理解をしているつもりでしたが、こうしていろいろな登壇を聞いて私なりの解釈もそこそこあってそうな予感がしてうれしさを感じています。みんな Kent Beck が好きですよね。私もです。 今改めてServiceクラスについて考える 〜あるRails開発者の10年〜 Yuki Eda (edy) https://kaigionrails.org/2025/talks/joker1007/#day1 Ruby on Railsでアプリケーション開発に従事しているエンジニアであれば一度は通るであろうServiceクラス。2025年のこのタイミングでjokerさんがこのタイミングで発表を行う理由も気になり視聴しました。 冒頭で歴史的な経緯を含めて日本国内で布教した背景の考察や、『パーフェクトRuby on Rails』にてServiceクラスを題材に執筆された当時の意図、執筆後数年経ってからの[Qiitaでのエントリ]( https://qiita.com/joker1007/items/25de535cd8bb2857a685 )などに触れられつつ、Serviceクラスに対しては出来る限り使わない方が良いと結論づけられていました。理由は「開発統制の困難さを上回るメリットが得られない」とのこと。 この点、私も思い当たる節はあり、例えばFat Modelを解消する手段としてServiceクラスを選択していた過去があります。適切な命名付けをしたとしても、アプリケーションの成長や数年間の運用の過程で特定のドメインだけの処理で留まらずに肥大化したトランザクションスクリプトになる傾向があると思います。これはjokerさんも言及されていた「基準の作りにくさ」から来る部分や、適切なドメインモデリングを怠った結果の2つが大きな原因だと、発表を聞きながら改めて考えさせられました。 結局のところ、ビジネスにおける関心事を顧客やチームメンバーを交えながらシステム内でうまい具合に表現できるするか、その丁寧な抽象化と具体化の繰り返し作業が大事で、Serviceクラスもそうですし対抗馬として挙げられることの多いFormクラスも含めて、デザイン方法は表現技法の一つに過ぎないということですね。 発表の末尾のスライドの「正解はない、一緒に考え続けましょう」「ソフトウェア設計は継続的な営み」が非常に印象的で心に留めておきたいと思いました。 履歴 on Rails : Bitemporal Data Modelで実現する履歴管理 Daichi Kamiyama - dak2 https://kaigionrails.org/2025/talks/hypermkt/#day2 https://speakerdeck.com/hypermkt/history-on-rails-with-bitemporal-data-model 直近で履歴を残すかどうかの設計に頭を悩ませたことがあり、Bitemporal Data Model での履歴管理の良し悪しが気になったので、拝見させてもらいました。 履歴テーブルのパターンとして参考になるのは、一休さんの https://user-first.ikyu.co.jp/entry/history-table このスライドが非常に参考になると思っています。普通に“履歴”という概念を表現するのであれば、このスライドの内容に沿って場合分けしていけば多くの場合は網羅できるんじゃないかと思っています。 ただ、SmartHR さんが扱うドメインとして “従業員を起点とした変化” に向き合っていく場合、「あとから分かった出来事も正しい日付で反映したい」というニーズがあるようです。確かに言われてみればそうですよね。 レコードとして有効な期間(ex. 2025/09/26 までに役職が部長だった)と、操作時間の2つの時間軸で履歴を管理するコンセプトが Bitemporal Data Model です。有効な期間だけだと、そのレコードがいつ登録/無効になったかの事実が抜け落ちるので、2つの時間軸で管理しようという話のようです。 パッと直感的に思ったのは2つの時間軸を考慮しないといけないので、考えることが掛け算で増えてしまう懸念を持ちました。SQL も複雑になりそうですしね。後から有効な期間を追加したい場合に、有効期間が重複してはいけないと思うので、そこの制約を Model のバリデーション、Database の制約ともに考慮する必要はあるんだろうなとか。 スライドでも想定外の履歴データが作られていることがあり、原因調査に丸一日かかったという言及がされていました。基本的に INSERT されるはずなので、そこから問題となっているデータを特定するのに時間がかかるというのはあるんだろうなと。activerecord-bitemporal gem では visualizer 機能があり、レコードの履歴を可視化してくれるそうです。 有効期間の日付管理が timestamp だと時分秒の管理があるから、date 型に変更して運用容易性を担保したという話も興味深かったです。この部分で意図しないレコードが生まれるケースはありそうだなと思っていたところ、 https://tech.smarthr.jp/entry/2025/09/12/115617 も拝見し、まさにその問題があったようですね。“履歴の一括出力後、Excelで適用日を編集するとExcelの仕様で時分秒の情報が「00:00:00」に丸められてしまう”、“例えば部署マスターの更新、並べ替え、削除、従業員部署の登録、更新などでは多数のモデルが同一トランザクションで更新されます。 このとき、適用日の時刻を固定して揃えないと、処理タイミングの僅かなズレによって適用日が秒未満の精度で異なった履歴が作成されてしまう” 、“同一適用日の更新において、社員番号と適用日の組み合わせでレコードを特定する際、ユーザーから秒未満を切り上げた日時を受け取り、秒未満を考慮して対象の履歴を探す” などなど、かなり負の側面を抱えていたようです。大変な移行だったようですが、とても意味のある移行だなと思いながら拝見しました。date 型だと unique 制約によって重複を弾きやすくなりますしね。 “履歴” を表現する1つの手段として Bitemporal Data Model を使ったリアルな声が公開されているのは、本当に嬉しいですね。とても参考になる話を聞けました。今後モデリングする際は、1つの手段として頭の片隅にインデックスを貼っておこうと思いました。 Kaigi on Rails 2025 に参加したタイミーのメンバー
アバター