TECH PLAY

MNTSQ

MNTSQ の技術ブログ

98

はじめに MinIOについて データ移行の要件 データ移行手順 帯域制御の方法 おわりに はじめに MNTSQでSREチームに所属している中岡です。 昨今ではコンテナ技術を使用してアプリケーションを稼働させることが一般的になっています。 コンテナが稼働する環境であれば、理論上は AWS などの クラウド 上でも、オンプレでも、自分の端末でも同じように動作するはずです。 そのため、開発者が自身の端末内に開発環境を構築し、そこで開発を進めた上で AWS 上の環境にデプロイするというケースも多いかと思います。 その際、アプリケーションで使用するデータ保持のため、 RDB やオブジェクトストレージが必要になります。 RDB であれば、 MySQL や PostgreSQL をコンテナで稼働させれば問題ありません。ではオブジェクトストレージはどうすれば良いでしょうか? AWS ではS3(Simple Storage Service)というオブジェクトストレージのマネージドサービスがあります。 ローカル上で AWS と同じ構成を模して開発するには、同じようにストレージを扱う必要があります。 MinIOについて 弊社では、MinIOというS3互換ストレージの OSS を使用しています。 MinIOは、 GNU Affero General Public License v3.0 に基づいてリリースされた高性能オブジェクト ストレージです。 Amazon S3 と API 互換性があります。 MinIO Object Storage for Container — MinIO Object Storage for Container 実際にMinIOをコンテナ環境で稼働させた印象として、安定しており、非常に使いやすいと感じています。 ※本稿では、MinIOの具体的な設定方法などには触れません データ移行の要件 さて、ここからが本題となります。 弊社では以下のような要件が発生しました ローカル環境やオンプレ環境で使用したデータを、そのまま AWS のS3上に移行したい 移行したデータに破損や漏れがないかをチェックしたい ローカルから AWS 上にデータを移行する時、ネットワークの帯域を使い切ってしまい、他の通信に悪影響を与えてしまう事を避けたい 検討の結果、MinIOのcommand line toolとして提供されている mc が優秀で、上記の問題は全てこのツールで対処できました。 MinIO Client — MinIO Object Storage for Linux なお、弊社にて動作実績のある環境は、CentOS7及びRHEL8.9となります。 データ移行手順 mc(minio client)のインストール 以下の手順でインストールします。 $ wget https://dl.min.io/client/mc/release/linux-amd64/mc -- https://dl.min.io/client/mc/release/linux-amd64/mc dl.min.io (dl.min.io) をDNSに問いあわせています... 138.68.11.125, 178.128.69.202 dl.min.io (dl.min.io)|138.68.11.125|:443 に接続しています... 接続しました。 HTTP による接続要求を送信しました、応答を待っています... 200 OK 長さ: 27496600 (26M) [application/octet-stream] `mc' に保存中 100%[==============================================================================================>] 27,496,600 4.83MB/s 時間 5.9s YYYY-MM-DD HH:MM:SS (4.47 MB/s) - `mc' へ保存完了 [27496600/27496600] $ sudo chmod +x mc $ sudo mv mc /usr/local/bin/mc $ mc --version mc version RELEASE.2024-10-08T09-37-26Z (commit-id=cf128de2cf42e763e7bd30c6df8b749fa94e0c10) Runtime: go1.22.8 linux/amd64 Copyright (c) 2015-2024 MinIO, Inc. License GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html> これでmcがインストールできました。 mcの基本的な使用方法は、データ操作をしたい対象に エイリアス の設定をすることです。 以下は初期状態です。 $ mc alias ls mc: Configuration written to `/home/mntsq/.mc/config.json`. Please update your access credentials. mc: Successfully created `/home/mntsq/.mc/share`. mc: Initialized share uploads `/home/mntsq/.mc/share/uploads.json` file. mc: Initialized share downloads `/home/mntsq/.mc/share/downloads.json` file. gcs URL : https://storage.googleapis.com AccessKey : YOUR-ACCESS-KEY-HERE SecretKey : YOUR-SECRET-KEY-HERE API : S3v2 Path : dns local URL : http://localhost:9000 AccessKey : SecretKey : API : Path : auto play URL : https://play.min.io AccessKey : Q3AM3UQ867SPQQA43P2F SecretKey : zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG API : S3v4 Path : auto s3 URL : https://s3.amazonaws.com AccessKey : YOUR-ACCESS-KEY-HERE SecretKey : YOUR-SECRET-KEY-HERE API : S3v4 Path : dns このサンプルを見て分かる通り、MinIOで管理しているストレージだけでなく、 AWS のS3や Google Cloud Storage(以下GCS)もalias設定の対象にすることができます。その際、AccessKeyおよびSecretKeyを使ってアクセスしますが、aliasコマンドでkeyが表示されてしまうため、移行作業に限定したkeyを発行し、対象のS3Bucketを限定するなど、 AWS 側のアクセスキー設定には十分留意してください。 そして、データ移行が終わったら、速やかにkeyを削除しましょう。 では実際に エイリアス の設定をしていきます。MinIOはローカル上にDockerコンテナで起動している前提です。 また、S3は東京リージョンを使用しているため、URLも東京リージョン指定にしています。 移行元:MinIO(ローカルストレージ) 移行先: AWS のS3 mc mirror — MinIO Object Storage for Linux # エイリアスの登録 $ mc alias set mntsq-test-minio http://127.0.0.1:7100 <MINIO_ROOT_USER> <MINIO_ROOT_PASSWORD> Added `mntsq-test-minio` successfully. $ mc alias set mntsq-aws https://<S3BUCKET NAME>.s3.ap-northeast-1.amazonaws.com/ <ACCESS KEY> <SECRET KEY> Added `mntsq-aws` successfully. # エイリアスが登録されているかを確認 $ mc alias ls mntsq-aws URL : https://<S3BUCKET NAME>.s3.ap-northeast-1.amazonaws.com AccessKey : **************************** SecretKey : **************************** API : s3v4 Path : auto Src : /home/mntsq/.mc/config.json mntsq-test-minio URL : http://127.0.0.1:7100 AccessKey : ******** SecretKey : ******** API : s3v4 Path : auto Src : /home/mntsq/.mc/config.json これでデータ移行の準備ができました。 MinIOからS3にデータ移行をするには、mcの ミラーリング 機能を使います。 この例では、test配下のデータを ミラーリング します。 # ミラーコマンド実行 # 送信元、送信先の順番でエイリアス設定したバケットを指定 $ mc mirror mntsq-minio/test mntsq-aws/test 0 B / ? xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax ...xxxxxxxx.pdf: 1.02 GiB / 1.02 GiB xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax 10.31 MiB/s 1m41s $ ミラーリング が完了し、プロンプトが帰ってきたら完了です。 もし何かエラーが発生した場合は、その内容が標準出力されます。 また、途中で ミラーリング を停止した場合は、再度同じコマンドを実行すれば問題ありません。 最後に、データの移行がもれなく完了したかを確認します。 $ mc diff mntsq-minio/test mntsq-aws/test # 差分がない場合は、何も出力されずにプロンプトが返ってくる # diffの実行には時間がかかるため、sshでサーバに接続している場合などは途中でセッションが切れてしまう場合がある # データ量が多い場合は、以下のようにバックグランドで実行することを推奨 $ nohup mc diff mntsq-minio/test mntsq-aws/test > YYYYMMDD_mc_diff_log.txt 2>&1 & 帯域制御の方法 ここまででデータ移行は完了です。 ただし、この手順ではデータ移行でインターネット回線の帯域を占有し、他の通信に影響を及ぼす可能性があります。 mcにはアップロード、ダウンロードの帯域を制限するオプションもあり、これを併用することでその心配もなくなります。 mc mirror — MinIO Object Storage for Linux # ミラーコマンドにアップロードの帯域制限を指定 # この場合、30Mbpsの帯域制限をしてアップロードする $ mc mirror --limit-upload 3.57MiB mntsq-minio/test mntsq-aws/test ローカルから AWS へのアップロードになるため、上記のオプションとなります。方向が逆の場合は"--limit-download"を指定します。 おわりに ローカルでテストしたデータを使いたい、オンプレ環境のデータを クラウド 上に単純移行したいなど、オブジェクトストレージを扱う色々なケースがあるかと思います。そうした時に本稿の内容が参考になれば幸いです。
はじめに 要件の整理 構成 実装例 Stop Env (GitHub Actions) Control ECS (GitHub Actions) Update ECS Clusters (Lambda) おわりに はじめに 弊社MNTSQでは AWS 上にMNTSQ CLMをはじめとする複数のサービスを展開していますが、サービス運用が軌道に乗るにつれて、社内利用の環境( AWS アカウント)が開発環境、QA環境、ステージング環境と用途によって増えていき、コストの増加が無視できない問題となってきました。そこで、 GitHub Actionsを使用して、ECSサービスを夜間停止する仕組みを導入することにより、コストの削減を行いました。workflow_callを使用することにより、良い感じに再利用性のある仕組みが作れたと思うので、記事にしていきたいと思います。 ※ 以降 GitHub Actionsは単にワークフローと表記しています 要件の整理 弊社では、社内環境の停止を行うにあたって、以下のような要件を満たす必要がありました。 開発環境、QA環境、ステージング環境の3環境を対象に夜間停止を行う必要がある 平日は0時 ~ 8時, 休日は終日対象の環境を停止する 残業や休日稼働をする人がいるかもしれないので、手動での起動・停止ができる必要がある 構成 フローの全体図 この構成のポイントは以下です 上位の コンポーネント から「 AWS 環境」->「管理対象のECS クラスタ 」 -> 「ECS クラスタ に所属するECSサービス」と操作対象をスコープダウンしている フロー全体の基点となる"Start Env", "Stop Env"のワークフローを、スケジュール(自動)とworkflow_dispatch(手動)の両方でキックできる このような構成にすることにより各 コンポーネント を再利用性が高い形で作成することができ、管理しなければならないワークフローの数も少なくすることができました。また、以下のような形に拡張することにより、RDSやEC2などの他のコンピューティングリソースを停止対象に追加することも容易です。 対象のコンピューティングリソースを拡張した例 実装例 Stop Env ( GitHub Actions) フロー全体の基点となるワークフローです。スケジュール停止を行いたい場合は開発環境、QA環境、ステージング環境の3つの AWS 環境に対して操作を行いたいですが、手動で起動を行う場合は特定の AWS 環境のみを対象にしたいです。そのロジックを set-target のjobに内包し、このjobのoutputについて、後続の stop-ecs-clusters のjobをmatrixで起動することによって、ワークフローのトリガの種類による対象環境の差分を吸収しています。"Start Env"と"Stop Env"を分けているのは、 schedule をワークフローに記述する必要があったからです。workflow_dispatchで起動する際は環境を選択するだけなので、シンプルな使い心地になっていると思います。 Start Envはこのコードをコピぺして、操作を反転させればOKです。 # .github/workflows/control_stop_env.yml # 指定した環境を停止する # スケジュールで実行される場合は、development~stagingの環境を停止する # 手動で実行される場合は、inputで指定した環境を停止する name : "Stop Env" permissions : # wf_callを実行するためにはこの権限が必要 id-token : write contents : write on : schedule : - cron : '0 15 * * *' # JST 0:00 に自動実行 workflow_dispatch : inputs : environment : type : choice description : 'Environment to Stop' required : true default : 'development' options : - development - qa - staging jobs : # schedule起動の時は"development", "qa", "staging"を対象にする # workflow_dispatch起動の時は、inputで指定されたAWS環境を対象にする set-target : runs-on : ubuntu-latest outputs : target : ${{ steps.set.outputs.target }} steps : - name : Set Target Environment id : set run : | if [ "$GITHUB_EVENT_NAME" = "schedule" ] ; then target='["development", "qa" , "staging" ] ' else target=' [ "${{ inputs.environment }}" ] ' fi echo "target=$target" >> $GITHUB_OUTPUT # 管理対象のECSクラスタを一括管理するWFをCallする stop-ecs-clusters : name : "Stop ECS Clusters" uses : ./.github/workflows/control_ecs_clusters.yml needs : set-target strategy : # 操作対象のAWS環境ごとに並列で起動する matrix : target : ${{fromJson(needs.set-target.outputs.target)}} with : environment : ${{ matrix.target }} action : STOP # stop-ecs-clustersと並列に、別のリソースを管理するWFをcallするjobを追加して拡張できる # stop-rds-clusters: # name: "Stop RDS Clusters" # uses: ./.github/workflows/control_rds_clusters.yml # needs: set-target # strategy: # matrix: # target: ${{fromJson(needs.set-target.outputs.target)}} # with: # environment: ${{ matrix.target }} # action: STOP Control ECS ( GitHub Actions) 管理対象のECS クラスタ を一括停止するワークフローです。ただし、"管理対象のECS クラスタ "の部分の実態は、運用により大きく異なるかと思います。対象の判定ロジックをワークフロー, Lambdaのどちらに置くのが適切かは、ケースバイケースになるはずです。今回はシンプルに、ワークフロー内に管理対象のECS クラスタ を列挙する形の実装例を置いておきます。 ※ 弊社環境は開発環境内に複数の開発面を持っている都合上、管理すべきECS クラスタ の数が多く、対象判定ロジックをワークフローとLambdaで分割して持つ、もう少し複雑な実装になっております。このサンプルコードは弊社のコードから余分な処理を削ぎ落としたものであり、実際の動作を確認したわけではないので、参考程度にご覧ください。 inputには environment (対象AWS環境)と action を要求します。 action は STOP or START を渡し、後続のLambdaでECSサービスを停止するのか起動するのかを制御する変数です。別のワークフローから呼ばれるので、 workflow_call のブロックも記述しています。 # .github/workflows/control_ecs_clusters.yml # 指定した環境のECSクラスターを停止する name : "Control ECS Clusters" env : ASSUME_ROLE_ARN : "arn:aws:iam::%AWS_ACCOUNT_ID:role/oidc-gha-role" LAMBDA_UPDATE_ECS_CLUSTER_NAME : "update-ecs-cluster" permissions : id-token : write contents : write on : workflow_dispatch : inputs : environment : type : choice description : 'Environment to apply' required : true default : 'development' options : - development - qa - staging action : type : choice description : 'Action to apply' required : true default : 'STOP' options : - START - STOP workflow_call : inputs : environment : type : string required : true action : type : string required : true jobs : # 必要な変数を組み立てる # AWSアカウント毎の差分が.github/configs/aws/variables.ymlというファイルに記述されていることを前提としている setup-env : runs-on : ubuntu-latest outputs : ASSUME_ROLE_ARN : ${{ steps.setup-env.outputs.ASSUME_ROLE_ARN }} steps : - name : Checkout uses : actions/checkout@v4 - name : Setup ENV id : setup-env run : | ENV=${{ inputs.environment }} # 設定ファイルの<env>.AWS_ACCOUNT_IDというフィールドから、アカウントIDを取得している AWS_ACCOUNT_ID=$(yq -r ".[ \" $ENV \" ].AWS_ACCOUNT_ID" .github/configs/aws/variables.yml) ASSUME_ROLE_ARN=$(echo $ASSUME_ROLE_ARN | sed -e "s/%AWS_ACCOUNT_ID/$AWS_ACCOUNT_ID/g" -e "s/%ENV/$ENV/g" ) echo "ASSUME_ROLE_ARN=$ASSUME_ROLE_ARN" >> $GITHUB_OUTPUT # 対象のECSクラスタ毎に、所属するECSサービスに対して一括操作を行うLambdaを呼び出す control-ecs-clusters : needs : setup-env runs-on : ubuntu-latest strategy : matrix : # WF冒頭のenvブロックに記載するとfromJsonで展開できないので、ここに対象ECSクラスタを書く cluster : ${{ fromJson('["service1-cluster","service2-cluster"]') }} steps : - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v4 with : aws-region : ap-northeast-1 role-to-assume : ${{ needs.setup-env.outputs.ASSUME_ROLE_ARN }} - name : Invoke Lambda UpdateEcsCluster run : | echo '{ "clusterName": "${{ matrix.cluster }}", "action": "${{ inputs.action }}" }' | jq -c > payload.json aws lambda invoke \ --function-name $LAMBDA_UPDATE_ECS_CLUSTER_NAME \ --payload file://payload.json \ --cli-binary-format raw-in-base64-out \ --invocation-type RequestResponse \ response.json Update ECS Clusters (Lambda) "clusterName"と"action"をinputとし、指定されたECS クラスタ の全サービスのタスク数を更新するLambdaのサンプルコードを置いておきます。こちらはnode.22.xで動作を確認しているものになります。 import { ECSClient , ListServicesCommand , UpdateServiceCommand } from "@aws-sdk/client-ecs" ; const ecsClient = new ECSClient ({ region : "ap-northeast-1" }) ; export const handler = async ( event ) => { try { const clusterName = event . clusterName ; const action = event . action ; const desiredCount = action === "STOP" ? 0 : 1 ; if ( ! clusterName ) { throw new Error ( "clusterName is required" ) ; } if ( action ! == "START" && action ! == "STOP" ) { throw new Error ( "action must be either START or STOP" ) ; } console . log ( `Processing cluster: ${ clusterName } ` ) ; // クラスタ内の全サービスを取得 let nextToken ; let serviceArns = [] ; do { const response = await ecsClient . send ( new ListServicesCommand ({ cluster : clusterName , nextToken })) ; serviceArns = serviceArns . concat ( response . serviceArns ) ; nextToken = response . nextToken ; } while ( nextToken ) ; if ( serviceArns . length === 0 ) { console . log ( `No services found in cluster: ${ clusterName } ` ) ; return { message : "No services found" , clusterName } ; } // 各サービスのタスク数を更新 await Promise . all ( serviceArns . map ( async ( serviceArn ) => { await ecsClient . send ( new UpdateServiceCommand ({ cluster : clusterName , service : serviceArn , desiredCount : desiredCount })) ; console . log ( `Updated service ${ serviceArn } to desiredCount: 0` ) ; })) ; return { message : "Successfully updated services" , clusterName , servicesUpdated : serviceArns . length } ; } catch ( error ) { console . error ( "Error updating services:" , error ) ; return { error : error . message } ; } } ; おわりに GitHub Actions を綺麗に実装するのはなかなか難しいですが、今回はシンプルで再利用性の高い形にできたと思うので紹介させていただきました。特に「操作対象をスコープダウンしながら設計する」という部分は、他のワークフローを作成する際にも役立つ考え方になるはずです。 SREのように内部改善やプラットフォーム維持を担うエンジニアは、直接的に売上を上げる機会が少ないからこそ、「コスト」に敏感である必要があります。ただし、コスト削減はそう単純ではなく、例えばテクニカルサポートや営業など、サービスを扱うすべての人が、将来に渡ってスムーズに業務を進められるかどうかも、見落としてはいけない「コスト」です。 業務全体を見渡してみると、もっと幅広い場面で GitHub Actions を活用できるはずです。そうした「小さな自動化の積み重ね」が、より良い運用環境を作っていくのだと思います。とりわけ、「夜間も環境が動いているのはもったいないよね」といったシンプルな課題は、落ちているチリ紙を拾うような気持ちでサクッと解決したいですね。 MNTSQ株式会社 SRE 西室
こんにちは!! SREチームマネージャーの藤原です。 2024年6月末から2025年2月頭にかけて、 入門 継続的デリバリー の読書会を実施し、完走したのでその報告エントリです。 www.oreilly.co.jp 勉強会の進め方 基本的な進め方としては、 過去エントリ にて解説した通りの進め方に則る形としました。 tech.mntsq.co.jp つまり、 事前に対象とする章を定める 参加者は対象の章を読む 参加者は気になった部分などを引用しながら所感をなどを Google Docs に記載する 当日はそれぞれ読んだ内容についてDocs記載内容について説明しながらディスカッションする の形で進めました。 勉強会のログサンプル 書籍の内容について 書籍の内容としては、架空のシステムを対象にストーリー仕立てでCI(Continuous Integration; 継続的インテグレーション )やCD(Continuous Delivery; 継続的デリバリー)においてよくある問題とその対処方針をまとめています。 流れとしては、事例を挙げた上で個々の状況においてどのような点に問題があるのかを解説しています。問題を抱えた現状を改善してより良い状況に持っていくにはどう考えるか?どう対処するか?を基本となる考え方を示しつつ、アクションを定めていくような形をとっています。 なぜバージョン管理が重要なのか?といった基本的なことについても当然のこととして切り捨てるのではなく改めて丁寧に確認するような形となっています。また、テストそのものやCI/CDパイプラインから得られるシグナルをどうとらえるか、シグナルに比してノイズが多すぎる状況はどのような問題を開発組織にもたらすか?などさまざまな観点からプロダクトの 開発プロセス におけるデリバリーに関わるプロセス上の課題について議論しています。 さらに、テストやビルドの技術的な問題を解決していく中で、DORAメトリクスなどを使ってデプロイに関連した組織パフォーマンスを測定することなども記述されています。 終盤では、CI/CDのパイプラインを構築運用していく上での考え方が述べられています。問題が発生した際の トラブルシューティング に必要なシグナルや、そもそもCI/CDのパイプラインを構成している スクリプト もコードであり、各種ソフトウェアエンジニアリングにおけるプ ラク ティスが適用できることなどが述べられています。 勉強会を通じての感想 事前に読んで気になった点をピックアップしたり、勉強会の中で気になった点などをコメントしたり、個々人が勉強会の中で発言した内容をコメントと残す中で、最終的に Google Docs 上では、A4で37ページ、コメント数は120-130程度の大作になりました。 個々の回では対象となっている章のどの部分に勉強会参加者が興味があるのか?が引用した部分やコメントから浮き彫りになりました。 書籍の内容としては、(藤原個人からみると)特別な内容はなく、よくある課題とそれらへの対応方針を丁寧に 言語化 してくれています。 議論の端緒として非常に有用な書籍でした。 コードを提示した上でどう直すか?といったことはほぼないので、写経して学ぶといったスタイルの書籍ではありません。 それよりは直面している問題をどう捉え、どう対処するかについての指針が多く述べられています。 このようなことから、 読み進めるに際しては、”書籍中で述べられているこの問題は自分たちの抱えているプロダクトにおいてどの部分に対応するだろう?”といった観点から、書籍で述べられている内容をベースに具体的なアクションを議論することでより得られる学びは多くなる と思いました。
はじめに DMSを使ってMySQLの移行をする際に気をつけたいこと7選! その1. DMSのログを出力するには決まった名前のIAMロールが必要である その2. CDCを有効にするにはソースDBでバイナリログを出す必要がある その3. GENERATEDカラムは移行対象から除外せよ その4. LOB型のカラムがある場合はターゲットDBでNOT NULL制約を一時解除せよ その5. 完全LOBモードの設定が必要か確認せよ その6. AUTO_INCREMENTは手動で移行する必要がある その7. 移行後の検証の設計は慎重に おわりに はじめに データベース移行というのは非常にセンシティブな作業であり、この使命を背負ってしまったエンジニアの皆様におかれましては、さぞ胃に優しくない日々を送っていることかと存じます。そんな私たちの心強い味方が AWS DMSです。 AWS Database Migration Service (以下DMS) は、 AWS が提供する フルマネージドのデータベース移行サービス であり、オンプレミスや クラウド 環境間のデータ移行を可能にします。 MySQL 、 PostgreSQL 、 Oracle など多様なデータベースをサポートし、移行元と移行先の異なるエンジン間の変換も自動化。フルロード、CDC (変更データキャプチャ) による継続的 レプリケーション も可能で、 最小限のダウンタイムでデータベースをスムーズに移行できる のが大きな魅力です! そんな便利なDMSですが、当然使用する際に気をつけなければいけないことはあります。 本記事では、DMSを使用して60回以上のデータベース移行を行なったMNTSQ SREチームから、「 MySQL の移行をする際に気をつけたいこと7選!」をお届けしたいと思います。 ※ なお、本記事はDMS自体の説明や利用方法の解説記事ではございません DMSを使って MySQL の移行をする際に気をつけたいこと7選! その1. DMSのログを出力するには決まった名前のIAMロールが必要である まずはじめにDMSの移行タスクなどを作成すると思いますが、ここで Terraformの公式サンプルコード を見てみましょう。IAMロールの定義として、このような記述があるかと思います。 resource "aws_iam_role" "dms-cloudwatch-logs-role" { assume_role_policy = data.aws_iam_policy_document.dms_assume_role.json name = "dms-cloudwatch-logs-role" } 他のリソースと 命名規則 を揃えたかったとしても、 このロールのnameは変更してはいけません。 aws_dms_replication_instance のリソースの記述を見るとわかりますが、DMS インスタンス にこのロールをアタッチするわけではないのです。DMS インスタンス は暗黙的に特定の 命名 のロールを使用します。何故なのかは知りません。とにかく、他のリソースのような感覚で名前を変更してしまうと、後々動作確認の際に、エラーになっても原因調査が進まないといったことになります。(一応サンプルコードの コメントアウト に注意書きがありますが......) ちなみに dms-access-for-endpoint , dms-vpc-role も同様の理由でnameを変更してはいけません。 その2. CDCを有効にするにはソースDBでバイナリログを出す必要がある DMSの強力な機能であるCDC (変更データキャプチャ) は、 レプリケーション 開始後にINSERTされたレコードも移行先DBに反映させることができる機能です。これがあるため、稼働中の環境でも無停止で レプリケーション を進め、最小のダウンタイムで新データベースに移行することができます。ただし、CDCはソースDBでバイナリログを有効化していないと利用できません。 Aurora MySQL の場合は、パラメータグループで binlog_format=ROW , binlog_row_image=full に設定しておけばOKです。ただし適用にはソースDBの再起動が必要です。 その3. GENERATEDカラムは移行対象から除外せよ GENERATEDカラムは、他のカラムの値を基に自動計算されるカラムです。便利な機能ですが、DMSはこのカラムにも律儀に値をINSERTしようとしてしまい、そのまま移行を実行するとエラーとなってしまいます。 ソースDBにGENERATEDカラムが存在するときは、 aws_dms_replication_task の table_mappings に、以下のようなルールを記述して、移行対象から除外しましょう。除外しても、ターゲットDBにも適切にGENERATEDカラムの制約が設定されていれば、自動で計算された値が再び入るはずです。カラムが複数ある場合は、除外ルールも複数書きます。 resource "aws_dms_replication_task" "mysql" { replication_task_id = "replication-mysql" migration_type = "full-load-and-cdc" ~~ 省略 ~~ table_mappings = jsonencode(local.table_mappings_mysql) } locals { table_mappings_mysql = { rules = [ # GENERATEDカラムを移行対象から除外するルール { rule-type = "transformation", rule-id = "1", rule-name = "skip_generated_column", rule-target = "column", object-locator = { schema-name = <schema_name>, table-name = <table_name>, column-name = <column_name> # GENERATED制約がついているカラム名 }, rule-action = "remove-column" } ] } } その4. LOB型のカラムがある場合はターゲットDBでNOT NULL制約を一時解除せよ DMSはLOB型のカラムを含む行を移行する際、以下の2つのステップで処理を行います。 LOB列をNULLにしたまま行を作成 LOB列をUPDATEしてデータを挿入 このため、LOB型のカラムにNOT NULL制約がついている場合、1の処理でエラーとなってしまうようです。NOT NULL制約がついたLOB型のカラムを持つデータベースの移行を行う際には、DMSの移行タスクを実行する前にターゲットDB側の対象カラムから制約を解除し、移行後に元に戻しましょう。 MySQL のLOB型には TINYBLOB , BLOB , MEDIUMBLOB , LONGBLOB , TINYTEXT , TEXT , MEDIUMTEXT , LONGTEXT , JSON などがあります。 ※ 参考: DMSのAWS公式ドキュメント その5. 完全LOBモードの設定が必要か確認せよ DMSによるLOB型カラムの移行オプションには、次の2つのモードがあります。 制限付きLOB モード すべての LOB 値をユーザー指定のサイズ制限 (デフォルトは 32 KB) で移行します。サイズを制限を超えるLOBは移行されず、手動で移行する必要があります。 完全LOB モード サイズに関係なくテーブル内のすべての LOB データを移行します。 DMSのデフォルトは"制限付きLOB モード" なので、データベースの完全な移行を行いたい場合は明示的に"完全LOBモード"を設定する必要があります。一応 AWS 的には、まず"制限付きLOB モード"を試し、必要なら"完全LOBモード"に切り替えるという戦略を推奨しているみたいです。その主な理由は、"完全LOBモード"だとパフォーマンスが極端に落ちるためのようです。本来LOBはS3などを使用して管理するのが AWS 的なベストプ ラク ティスであり、そのような方法を検討して欲しいのだと思いますが、既に移行の計画に入ってからの変更は厳しい箇所かなと思います。ですので、 結局ほとんどのケースで"完全LOB モード"を使用することになるのではないかと思っています。 LOBモードの設定は、terraformの場合だと aws_dms_replication_task リソースの replication_task_settings.TargetMetadata.FullLobMode にbooleanで定義されています。"完全LOB モード"を使用する場合には FullLobMode=true に設定しましょう。 AWS コンソールから移行タスクを編集して設定することも可能です。 その6. AUTO_INCREMENTは手動で移行する必要がある DMSがサポートするのはあくまでアプリケーションデータであり、 INFORMATION_SCHEMA や performance_schema などの移行は行えません。プライマリキーなどに AUTO_INCREMENT を使用している場合、DMSでの移行後に値がリセットされ、新たなレコードが挿入できなくなるなどのサービス障害の原因となってしまいます。 これを防ぐにはAUTO_INCREMENTの値を手動で移行する必要があります。以下の記事などを参考にし、弊社では移行用 SQL を作成する スクリプト などを用意して、移行手順に組み込みました。注意点として、AUTO_INCREMANTの値を取得する時は SHOW CREATE TABLE <table_name>; などを使用しましょう。 INFORMATION_SCHEMA へのクエリでは、最新の値が取れないことがありますし、物理削除が行われるテーブルではAUTO_INREMENTと最新レコードのidにはズレが生じます。 blog.tocyuki.com その7. 移行後の検証の設計は慎重に データベース移行後には必ず、移行前後で差分が出ていないかの検証を行うかと思います。当然、弊社でも検証を行なっていましたが、移行作業初期にはやはりトラブルに見舞われることはありました。原因は様々でしたが、 移行後検証の完全性が保証されていれば、全てメンテナンスウインドウ中に検知できた ものであり、サービスのインシデントにつながることな無かったはずのものばかりでした。最終的に弊社では、以下のチェックを行う スクリプト を導入した結果、データベース移行に関するトラブルは起きなくなりました。 全テーブルの SELECT COUNT(*) FROM <table>; の結果を移行前後で突き合わせる スクリプト 全テーブルの SHOW CREATE TABLE <table_name>; の結果を移行前後で突き合わせる スクリプト これにより、 レコード数に差分がないこと 、 テーブル構造に差分がないこと 、 AUTO_INCREMENTの差分がないこと が保証できました。 なお、これは弊社の事例に基づく例であり、いかなるケースにおいても上記の確認が移行の完全性を保証するものではございません! 弊社の場合は事前にCDCで レプリケーション を行い、移行日にはサービスのメンテナンス時間をとってこれらを確認しましたが、無停止での切り替えなどを計画している場合は、上記の項目の確認は難しくなります。また、データベースの設計によっては、確認項目が不足しているケースもあるかもしれません。加えて、制限付きLOBモードを使用していた場合、移行がスキップされたLOBがあったとしても、この方法では気づけません。あくまで「最低限ここは確認したほうが良い」程度にお受け取りください。 いずれにせよ、 移行後の検証は、移行作業そのもの以上に慎重に設計すべきです。 おわりに 弊社MNTSQでは、ここ2年間ほどかけて大規模な アーキテクチャ 変更を行い、その一環として計67回の顧客環境のデータ移行を行いました。最初の1回目の作業を行った時は「え、この作業あと60回以上するの?」と、その "永遠" に絶望したものですが、この度、ついにその作業も完了したので、区切りとして記事を書かせていただきました。 本記事では、移行作業を設計・検証する際に、実際に私がハマった箇所を「気をつけたいこと」という形で紹介させていただきました。「ここだけ気をつけていればあらゆるトラブルを回避できる」というものではありませんが、これからDMSを使用してデータベース移行を行うという方の助けになれば幸いです。 MNTSQ株式会社 SRE 西室
......のですが、かなり苦戦しました。この記事に辿り着いた人はすでにハマっている、もしくはこれからハマる運命(さだめ)にある人も多いと思うので、そのような人の助けになればと思い、記事にして残しておきます。 結論からお伝えすると、Lambdaを使わずに通知を行うことは可能ですが、設定は少し複雑かなという印象でした。 しかし、一度設定出来てしまえば、同じようなことをしたい時の実装コストをグッと抑えられる、とても良い仕組みだと思います。 構成について この構成のメリット この構成のデメリット AWS Chatbotの認証を行う terraformでデプロイしてみる SNS -> Chatbot -> Slackの部分 DynamoDB Stream -> EventBridgePipes -> EventBridgeCustomBusの部分 EventBridgeRule -> SNS の部分 おわりに 構成について Lambdaではなく、 AWS Chatbotを使用してSlackへの通知を行う構成です。 この構成のメリット Lambdaのランタイムやコードの管理から解放される 「 SNS -> Chatbot -> Slack」,「EventBridgeRule -> SNS 」 の部分の汎用性が高く、使いまわせる この構成のデメリット メッセージのカスタマイズ性に少しだけ欠ける 初めて設定する際はハマりどころが多い AWS Chatbotの認証を行う まずは、 AWS ChatbotからSlack ワークスペース に対しての認証を行います。 あらかじめSlack ワークスペース にChatbotのアプリをインストールしておく必要があります。 mntsq.slack.com ワークスペース にアプリをインストールしたら、Chatbotが通知を行いたいチャンネルにアプリを招待します。チャンネル詳細の「インテグレーション」タブから、「 AWS Chatbot」のアプリを招待します。 次に、 AWS コンソールからChatbotのページへ行き、Slackクライアントの設定を行います。 ブラウザでSlackログインしていた場合、Slackの認証ページにリダイレクトされるので、「承認」します。 その後「Slack チャネルを設定」という編集画面に遷移することがありますが、今回はterraformでデプロイを行うので、この画面は閉じてしまって大丈夫です。 terraformでデプロイしてみる 今回はこのような仕組みを作るものとします。 ファイルアップロード時にウイルススキャンを行い、感染ファイルが見つかった場合、DynamoDBのInfectedScanResultsというテーブルに書き込みを行う InfectedScanResultsに書き込みがあった場合、その内容をSlackのエラーチャンネルに通知する SNS -> Chatbot -> Slackの部分 まずは通知の起点となる SNS トピックとChatbotのチャンネル設定を作成します。 SNS # 後段のChatBotからSlackへの通知を行うためのSNSトピック resource "aws_sns_topic" "slack_notify_error" { name = "slack-notify-error" } # SNSにCloudWatchからのPublishを許可するポリシー data "aws_iam_policy_document" "allow_cloudwatch_to_publish_sns" { statement { actions = [ "sns:Publish" , ] effect = "Allow" resources = [ aws_sns_topic.slack_notify_error.arn, ] principals { type = "Service" identifiers = [ "cloudwatch.amazonaws.com" , ] } } } resource "aws_sns_topic_policy" "allow_cloudwatch_to_publish_sns" { arn = aws_sns_topic.slack_notify_error.arn policy = data.aws_iam_policy_document.allow_cloudwatch_to_publish_sns.json } Chatbot # chatbot用のIAMロール data "aws_iam_policy_document" "chatbot_assume_policy" { statement { effect = "Allow" principals { type = "Service" identifiers = [ "chatbot.amazonaws.com" ] } actions = [ "sts:AssumeRole" ] } } data "aws_iam_policy_document" "chatbot_slack_notify" { statement { effect = "Allow" actions = [ "sns:Subscribe" , "sns:ListSubscriptionsByTopic" , "sns:GetTopicAttributes" , "sns:Publish" ] resources = [ aws_sns_topic.slack_notify_error.arn ] } } resource "aws_iam_role" "chatbot_slack_notify_error" { name = "chatbot-slack-notify-error-role" assume_role_policy = data.aws_iam_policy_document.chatbot_assume_policy.json } resource "aws_iam_policy" "chatbot_slack_notify_error" { name = "chatbot-slack-notify-error-policy" description = "IAM policy for Chatbot to notify error to Slack" policy = data.aws_iam_policy_document.chatbot_slack_notify.json } # Chatbot チャンネル設定 resource "aws_chatbot_slack_channel_configuration" "slack_notify_error" { configuration_name = "slack-notify-error" slack_channel_id = "YOUR_CHANNEL_ID" slack_team_id = "YOUR_WORKSPACE_ID" logging_level = "INFO" sns_topic_arns = [ aws_sns_topic.slack_notify_error.arn ] iam_role_arn = aws_iam_role.chatbot_slack_notify_error.arn } terraform apply を実行したら、 AWS コンソールからリソースが作成されたことを確認してください。 Chatbotのコンソールから「テストメッセージの送信」を行い、Slackチャンネルに通知が届けばOKです。 ※弊社開発環境のものなので、微妙にリソース名が異なります なお、2024/12月現在、Chatbotのロググループは強制的に バージニア 北部に作成されるので、東京リージョンを彷徨わないようにご注意下さい。 DynamoDB Stream -> EventBridgePipes -> EventBridgeCustomBusの部分 DynamoDBに書き込みがあった時、それをCustomBusにEventとして送信するEventBridgePipesの設定を行います。 DynamoDB ## InfectedScanResultsテーブル resource "aws_dynamodb_table" "infected_scan_results" { name = "InfectedScanResults" billing_mode = "PAY_PER_REQUEST" hash_key = "ObjectPath" attribute { name = "ObjectPath" type = "S" } # EventBridge -> SNS -> Slack通知を行うためのストリーム stream_enabled = true stream_view_type = "NEW_IMAGE" } EventBridge # カスタムのイベントを記録するためのバス resource "aws_cloudwatch_event_bus" "notification" { name = "notification" } # PipeのためのIAMロール data "aws_iam_policy_document" "eventbridge_pipe_assume_role_policy" { statement { actions = [ "sts:AssumeRole" ] effect = "Allow" principals { type = "Service" identifiers = [ "pipes.amazonaws.com" ] } } } resource "aws_iam_role" "dynamodb_pipe_role" { name = "dynamodb-pipe-role" assume_role_policy = data.aws_iam_policy_document.eventbridge_pipe_assume_role_policy.json } data "aws_iam_policy_document" "dynamodb_pipe_policy" { statement { actions = [ "dynamodb:DescribeStream" , "dynamodb:GetRecords" , "dynamodb:GetShardIterator" , "dynamodb:ListStreams" , ] effect = "Allow" resources = [ aws_dynamodb_table.infected_scan_results.stream_arn ] } statement { actions = [ "events:PutEvents" ] effect = "Allow" resources = [ "*" ] } statement { actions = [ "logs:CreateLogStream" , "logs:PutLogEvents" ] effect = "Allow" resources = [ "*" ] } } resource "aws_iam_role_policy" "dynamodb_pipe_policy" { name = "dynamodb-pipe-policy" role = aws_iam_role.dynamodb_pipe_role.name policy = data.aws_iam_policy_document.dynamodb_pipe_policy.json } # ロググループ resource "aws_cloudwatch_log_group" "dynamodb_infected_scan_results_write" { name = "/aws/vendedlogs/pipes/dynamodb-infected-scan-results-write" } # InfectedScanResultsのストリームをEventBridgeに通知するPipe resource "aws_pipes_pipe" "dynamodb_infected_scan_results_write" { name = "dynamodb-infected-scan-results-write" role_arn = aws_iam_role.dynamodb_pipe_role.arn source = aws_dynamodb_table.infected_scan_results.stream_arn target = aws_cloudwatch_event_bus.notification.arn log_configuration { include_execution_data = [ "ALL" ] level = "INFO" cloudwatch_logs_log_destination { log_group_arn = aws_cloudwatch_log_group.dynamodb_infected_scan_results_write.arn } } source_parameters { dynamodb_stream_parameters { batch_size = 1 starting_position = "LATEST" } } target_parameters { eventbridge_event_bus_parameters { detail_type = "InfectedScanResultsWrite" source = "custom.dynamodb.infected-scan-results" } } } terraform apply を実行したら、DynamoDBにレコードを追加してみてください。ロググループ /aws/vendedlogs/pipes/dynamodb-infected-scan-results-write に dynamodb-infected-scan-results-write というストリームが作成され、いくつかログが出ているはずです。 # 最後のログがこんな感じだったらOK { "resourceArn": "arn:aws:pipes:ap-northeast-1:******:pipe/dynamodb-infected-scan-results-write", "timestamp": 1734671733500, "executionId": "8e3e7d3c-0c1e-4b47-a6b7-******", "messageType": "ExecutionSucceeded", "logLevel": "INFO" } ちなみにこのイベントはCroudTrailなどには記録されません。(最初はCroudTrailに記録されるものだと勘違いして時間を溶かしました)EventBridgeのコンソールからここら辺確認できるようになるとありがたいなぁ......と、しみじみ思います。 EventBridgeRule -> SNS の部分 最後に、CustomBusにイベントが送信された時に、それを拾って SNS にパブリッシュするRuleを作成します。Chatbotが受け取ることができる json の形式は決まっているので、EventBridgeの入力トランスフォーマを使用し、良い感じに整形します。 EventBridgeRule # busのイベントをSNSに通知するためのルール resource "aws_cloudwatch_event_rule" "dynamodb_infected_scan_results_write" { name = "dynamodb-infected-scan-results-write" description = "Send DynamoDB InfectedScanResults write events to EventBridge" event_bus_name = aws_cloudwatch_event_bus.notification.name event_pattern = jsonencode ( { source = [ "InfectedScanResultsPuts" ] detail-type = [ "custom.dynamodb.infected-scan-results" ] } ) } resource "aws_cloudwatch_event_target" "dynamodb_infected_scan_results_write_target" { rule = aws_cloudwatch_event_rule.dynamodb_infected_scan_results_write.name arn = aws_sns_topic.slack_notify_error.arn event_bus_name = aws_cloudwatch_event_bus.notification.name input_transformer { input_paths = { "ObjectPath" : "$.detail.dynamodb.NewImage.ObjectPath.S" , "ScannedAt" : "$.detail.dynamodb.NewImage.ScannedAt.S" , "Message" : "$.detail.dynamodb.NewImage.Message.S" } # jsonencodeを使用すると<, >などが文字コードに変換されてしまうのでTEMPLATEを使用する input_template = <<TEMPLATE { "version": "1.0", "source": "custom", "content": { "textType": "client-markdown", "title": "⚠️ウイルス感染ファイルが検出されました⚠️", "description": "<!subteam^YOUR_TEAM_ID>\n<ObjectPath>\n<ScannedAt>\n<Message>" } } TEMPLATE } } <!subteam^YOUR_TEAM_ID> の部分はメンションしたいSlackのTeamIDです。TeamIDの確認の仕方はここでは割愛します。Chatbotが解釈できる json の形式は、 こちらのドキュメント を参照しました。 これで全リソースの定義を作成できました。 terraform apply を実行して、もう一度DynamoDBにレコードを追加し、Slackに通知が飛んでくることを確認しましょう。 無事通知されました! おわりに 今回はLambdaを使用せず、DynamoDBへの書き込みをSlackに通知する方法について紹介させていただきました。EventBridge + SNS + Chatbotの構成は設定も簡単で再利用性が高く、監視モニタリングの整備をする際にはとても便利な仕組みですね。 本当はSlackのメッセージにカラーバーをつけたりとカスタマイズしたかったのですが、 AWS サポートに問い合わせたところ、2024/12現在ではそこまで細かい設定はできないようです。ただしEventBridgeRuleのターゲットにはHTTPエンドポイントも指定できるようなので、もっとこだわりたい人はこちらの方法を使ってみるのも良いかもしれません。 dev.classmethod.jp 以上、何かの助けになれば幸いです! MNTSQ株式会社 SRE 西室
こんにちは。MNTSQ( モンテスキュー )株式会社でQAエンジニアをしている坂本です。 今回は ソフトウェアテスト Advent Calendar 2024 の場をお借りして、 弊社の 自動テスト構築過程 をご紹介します。 QAメンバー4名だけでなく、PdMやSREにもご協力頂きながら進めており、 2025年1月からの稼働に向けた準備が大詰めの段階です。 品質と開発スピードの両立を目指したコラボレーションの様子が少しでも伝わればうれしいです。 テスト自動化の目的 MNTSQ社では アジャイル 開発を行っており、製品のリリースサイクルごとに新規開発機能のテストと、既存機能全体の リグレッション テストを実施しています。 リグレッション テストはテスト項目数が多いため、すこし早めにテスト実施担当者とスケジュール調整する必要があります。 そこで、テストスケジュールの自由度を高めることを目的として、 リグレッション テストの約9割を自動化することにしました。 テスト自動化ツールAutify 今回は、 Autify(オーティファイ) という自動テスト作成ツールを用いています。 主な機能は以下の通りです。 ノーコードのテストシナリオ作成 テストの定期実行 テストレポートの自動作成 テストの作成・管理・自動作成されたテストレポートの保管がWeb上で完結するため、URL一つで情報共有できるところがありがたいです。 テスト設計 手動 リグレッション テストをそのままAutifyシナリオにすることは、以下の理由で避けました。 製品の成長とともにテスト項目が膨大になっていた 徐々に継ぎ足されてきたため、テストの体系が見えにくくなってきていた その代わり、製品の性質を踏まえ、以下のテストを作成することにしました。 クリティカルパス の動作確認テスト CRUD テスト 権限テスト リグレッション テスト全体の1割についてはAutifyで実装しづらい動作であったため、手動テストとして継続することにしました。 テスト設計のレビュー テスト設計をQA内でピアレビューした後、特に重要な権限ごとの期待動作について各製品のPdMにもレビューを依頼しました。 レビューの過程で細かい仕様の認識違いも明らかになりましたので、色々な方にレビューをして頂けて大変助かりました。 テスト用データの準備 SREへ相談し、テスト用環境を特定のテスト用データで初期化できる仕組みを作って頂きました。 この仕組みはテスト実行の安定化に対して強力です。SREチームに大変感謝しています。 自動テストが途中で落ちた際にテスト過程で作成したデータが残ることがあるのですが、この仕組みがあれば毎回同じ条件でテストをスタートできます。 アカウント テストしたい権限を持つユーザーアカウントを作成します。 ファイル 処理結果をテストで確認したいファイルを用意します。 テスト用環境の設定値の調整 製品の実環境と同じ条件で作成されたテスト用環境に、一般的な利用場面を想定したユーザー設定値を追加していきます。 Autifyテストシナリオの実装 いよいよ、Autifyのシナリオを実装します。 Autifyレコーダーを起動させた状態で製品のUIを操作すると、その様子が記録され、Autifyの中にあるAIがステップに切り分け、テストシナリオ起こしてくれます。 このおかげで、テスト実装はコードを書かずに進めることができました。 頻度として多くはないのですが、HTMLと CSS の構成が特に複雑な画面では、 クリックしたい要素の特定方法をAutifyの中のAIがうまく割り出せないことがあります。 その場合は、人間が CSSセレクター や XPath で特定し直します。 大抵はブラウザの検証ツールからコピー&ペーストした CSS セレクタ で動くのですが、 もし画面の構成が複雑過ぎて、 CSS セレクタ ーをいい感じに簡略化できないときには、 Autifyのサポート窓口に相談すると、専門の方に手厚くフォローして頂けます。 リグレッション テストの9割を自動化する目標の元、 やることが多くて社内では細かい分析まで手が回らないことがありますので、 サポート頂けて本当に助かっています。 ひととおり実装を終えた感想 自動 リグレッション テストを設計する際に一番重視したのは、いかに安全にパターンを絞るかでした。 そのためには自社が提供するウェブアプリの特徴を捉え、必ず押さえなければいけないポイントを見極めることが重要でした。 このポイントの整理に一番力を注ぎました。 ポイントを整理してからテスト項目の骨格を組み立てることで、既存の手動 リグレッション テスト項目との比較もしやすくなり、 本当に移行して大丈夫か、既存のテスト項目から漏れているものはないか、追加すべきものはないかを検討する際の道筋を得られたと思います。 自動化したい内容の全体像を予め設計できたことで、目標の「9割」を実装したと表現しやすくなり、 チーム外とのコミュニケーションも取りやすくなりました。 実装後、自動テストの内容を、QAチームで日頃仕様している手動テスト項目書のフォーマットに書き起こし、 ドキュメントとして利用できるようにしています。ドキュメントを作成するとテストシナリオの粗が見えてくるので、 リファクタリング も同時に行っているのですが、最初にポイントを整理していたために迷わず作業できているように思います。 運用体制構築に向けて 来月から自動テストの運用フェーズに入ります。 ここまでは効率重視で、テスト自動化のための役割を分担して進めてきました。 プロジェクト管理 データ整備 テスト設計&実装 QAチームは比較的新しい組織で、4名のQA歴は長い方から1年3ヶ月、1年1ヶ月、5ヶ月、2ヶ月ですから、 よく協力して頑張っているのではないかと思います。 ここからは、QAチームの誰もが自動テストのメンテナンスが出来るようになろう! という目標に変わりますので、以下のような活動が始まります。 Autify勉強会 リグレッション テスト追加・削除基準のすり合わせ テストレポート作成方針のすり合わせ 年明けからの運用フェーズ立ち上げも頑張ろうという意気込みで、年末の振り返りとして自動テスト構築のまとめ記事を発信させていただきました。 ここまでお読み頂きありがとうございました!
こんにちは、MNTSQ でバックエンドエンジニアをやっております河久保です。 先日 Kaigi on Rails 2024 に参加してきたので、参加記をしたためます。 Kaigi on Rails は昨年に続き2回目の参加となります。 MNTSQ もアプリケーションのバックエンドは Ruby on Rails で実装されており、昨年に続き今年もスポンサーとして手を挙げさせていただきました。 個人としてプロポーザルを出したことや、担当しているシステムのより深いところまで触れる機会が増えたことで、昨年より主体的にカンファレンスに関われたと実感しています。 スポンサーボード 今回は以下の講演を聴講してきました kaigionrails.org 1日目 Hall Red Hall Blue 1 基調講演 2 Rails の仕組みを理解してモデルを上手に育てる - モデルを見つける、モデルを分割する良いタイミング - 3 そのカラム追加、ちょっと待って!カラム追加で増える ActiveRecord のメモリサイズ、イメージできますか? 4 モノリス でも使える!OpenTelemetryで Rails アプリのパフォーマンス分析を始めてみよう 5 cXML という 電子商取引 の トランザクション を支える プロトコル と向きあっている話 6 リリース8年目のサービスの1800個のERBファイルをViewComponentに移行した方法とその結果 7 ActionCableなら簡単? 生成 AIの応答をタイピングアニメーションで表示。実装、コスト削減、テスト、運用まで。 8 現実の Ruby / Rails アップグレード 9 (中抜け) (中抜け) 2日目 Hall Red Hall Blue 1 都市伝説バスターズ「Webアプリの ボトルネック はDBだから言語の性能は関係ない」 2 Cache to Your Advantage: フラグメントキャッシュの基本と応用 3 OmniAuthから学ぶOAuth 2.0 4 約9000個の自動テストの時間を50分から10分に短縮、 偽陽性 率(Flakyテスト)を1%以下に抑えるまでの道のり 5 Sidekiq vs Solid Queue 6 The One Person Framework 実践編 7 Data Migration on Rails 8 30万人が利用するチャットをFirebase Realtime DatabaseからActionCableへ移行する方法 9 サイロ化した金融システムを、packwerk を利用して無事故で リファクタリング した話 10 Identifying User Identity 11 基調講演 2日間通して振り返ると、それぞれの講演に連動があり Kaigi on Rails オーガナイザーからのメッセージを強く感じました。 One Person Framework Rails 7 から One Person Framework という旗を掲げて、 Rails 8 も当然その流れをくんだ機能・ コンポーネント を提供しています。 0(Idea) →→→→ 1( IPO ) というコンセプトは、 初期は資金もないし、人もいないから Rails Way に乗って最速で立ち上げる。 サービスが当たってグロースして IPO というころには資金もあるわけなので、そのときに顕在化した ボトルネック の アーキテクチャ を乗り換えれば使えばいいし、人も雇用できるでしょ。 と私は解釈しており、とても納得感のあるアプローチだなと思います。 『One Person』からは「じゃあ規模大きくなったらダメなの?」という疑問符浮かぶのも必然だと思います。 シンプルさ、 Rails Way の追求 小規模なアプリケーションであれば1人の脳に収まるでしょうが、サービスが成長するに従いそれは難しい問題となるでしょう。 加えて Ruby 言語の特性がゆえに避けられない問題(主に型の側面)もあります。 多人数が触るコードベースは時間とともに統制が取れなくなってくるでしょう。 そういった面のアプローチとして「シンプルに保つ」という話が、2つの基調講演で触れられていたのが印象的でした。 初日の palkan さんによる講演では、新しい層を持ち込むことへのアプローチについてサンプルコードを添えたレクチャーがありました。 ここではプレゼンテーション層へのインタフェースとなる Form オブジェクトを取り上げていたと記憶しています。 このアプローチは将来的なプロダクトコードのおぼろげながら抱いていたイメージに合っていたので、勇気づけられました。 2日目の 島田 さん講演で「オプションを手に入れよう」「シンプルさを維持するための修復」という点が響きました。 『オプション』とは何でも受けられる設計ということを指すのではなく、シンプルさを維持することで将来的に取れる択を残しておきましょうということ。 『修復』とは単に元の状態に戻すことではなく、損傷を直しつつそのときの環境、新しい技術を使ってより環境に適応した状態にすることで、これは創造的なことだよね。 と話されてました。 損傷を直しつつそのときの環境、新しい技術を使ってより環境に適応した状態にすること この部分が私の持つポリシー *1 とフィットして心震えました。 どちらも アーカイブ が公開されたらぜひとも見返したいです。 Solid Queue Rails 8 から ActiveJob のデフォルトバックエンドになるという「Solid Queue」からも初期は極力 ミドルウェア を排除するという強い意志を感じています。 willnet さんの講演を聞くまで DB ベースのキュー管理って大丈夫なんだろうか?という不安がありました。 しかし、事例紹介で 2,000万 job/日は捌けているよ(Sidekiq は 20,000 job/秒 !!)という言及されており、次回 rails new する際に触ってみようと思います。 Open Telemetry ちょうど Open Telemetry に興味を持ち始めたので、 ymtdzzz さんの講演を聞きました。 speakerdeck.com 今回は Rails アプリケーションという領域での紹介でしたが、トレースとアプリケーションログの連動のといったデモを見て、「これこれ、これがやりたいんよ」ってなりました。 弊社では Rails アプリケーションは DataDog の APM を導入しておりますが、 DataDog Log を掛け合わせて同様のこともできそうですが、 Open Telemetery でメトリクスとログをコレクトして、 DataDog なり他サービスなりに放り込んでリッチなビューアーとして利用するというアプローチは現実的だな思いました。 またフロントエンドからトレースを送ることで、ユーザーリク エス トから始まる各レイヤーのトレースが取得でき、サービス全体での ボトルネック の発見に向けて適切なアプローチをとることもできそうだという感覚を得られました。 ほかにもたくさん osyoyu さんの講演からも CPU バウンドなのか、 I/O バウンドなのか、その比率から適切な Puma スレッド数が変わってくるというはなし。 ohbarye さんの講演では、最近私達が db:migration のスキームに乗りつつ、手オペを減らしたのですが、それ以外のアプローチの紹介。 moro さんの講演の User モデルの拡張のアプローチはとても斬新(User テーブルは id だけもつ)で、提示されたコードもエレガント。 and more ... おわりに Kaigi on Rails 2024 を終えて色々とやりたいことが湧いてきて仕方がないです。 こういうワクワク感が出てくると目先のタスクがおざなりになりがちな性格なので、チームメンバーには「掛かり *2 気味になってたら、しっかり手綱を握って制御お願いします」と言ってまわっています💦。 2日目の各社の Drink Up から流れ着いた二次会、三次会、、、でも参加者と講演の話などで盛り上がりました。 懐がデカすぎる! @kawakubox 「ここのテーブルは全部俺が持つッ」 唐突なスケールにも対応できるMNTSQさんありがとうございます! #kaigionrails pic.twitter.com/arWbYF78ov — iberianpig(Kohei Yamada) (@nukumaro22) 2024年10月26日 弊社について「あぁ、もんてすきゅーさんね」と言われるくらいに認知はされ始めてきていると思いますが、プロダクトについてまったく知らないという方も多いと思います。 プロダクトについて知りたい!! Kaigi on Rails のことで話したい!! などありましたら、カジュアル面談でもよいですし、私の X アカウントでもよいので気軽にメッセージを送っていただけたらと思います。 careers.mntsq.co.jp Kaigi on Rails 2024 の クリエイティヴ は CC BY 4.0 のライセンス下で提供されております *1 : カジュアル面談でも飲みの場でもいくらでも話します *2 : 騎手と馬の呼吸が合わず、ちぐはぐな状態
みなさんこんにちは、SREチームメンバーの中岡です。 2024年10月16日に開催された「 Datadog Summit Tokyo 」に参加しましたので、そのレポートをお届けしたいと思います。 Datadogは SaaS で提供されている、 クラウド アプリケーションのためのモニタリングとセキュリティプラットフォームです。 弊社サービスの監視にもDatadogを使用しており、SREチームで取り組んでいるモニタリング改善の参考になればと思い、参加しました。 イベントの内容 Datadog Summit Tokyo Datadog Summitが東京で開催されるのは2019年以来、5年ぶりの開催とのこと。 参加者も多く、盛況なイベントでした。 Datadogセッション + お客様セッション 午前中はDatadogの歴史から始まり、日本国内にデータセンターを開設したことや、Datadog認定資格の日本語対応(まだ一部だけ)、日本法人の強化など、日本への対応に力を入れている事が伝わってきました。特に日本国内のデータセンターは、ログの保存先を国内にしたいといった需要が一定数あるのではないかと思います。 国内データセンター開設 お客様セッションは、Datadogのユーザによる活用事例の紹介です。 普段、他社でどのようにDatadogを利用しているかといった話を聞く機会がないため、このセッションは大変参考になりました。 以下、それぞれのセッションで聞いた内容をレポートします。 【Datadog ダッシュ ボードで 見える化 する、新たなビジネス価値創造のチャンス】 NTT DoCoMo 野部様 www.datadoghq.com d払いのサービス監視にDatadogを活用 DatadogのMonitor / APM 機能を活用 Anomaly機能(異常検知)も使っている ダッシュ ボード活用事例 新機能リリース時(d払いスタンプ機能) エラー数、レイテンシ、アクセス数をモニター(ユーザストーリごとに ダッシュ ボードを分ける) スタンプを集めた後の宝箱の 開封 率が低いと分かったので、UIを改善した ビジネス観点のモニター ダッシュ ボードとの向き合い方 必要なデータや見方を目的ごとに最適化 リク エス ト数が目標値に達しているか? 起動時間がどれくらいかかるか? 多く使われる 動線 はどちらか? 新機能がきちんとユーザに使われているのか?UIの 動線 が意図した通りに使われているか?といった分析に ダッシュ ボードを活用しているのが印象的でした。 【SLO監視文化の立ち上げジャーニー】 株式会社ワンキャリア SRE 渡邉様 www.datadoghq.com 短期間で複数サービス立ち上げ 2021年にSREチーム発足 当初はサービス監視のみ 2022年 パフォーマンスの維持をするためのSLO運用 始めてみたがうまくいかなかった SLO監視と違反対応の優先度が上がらない SREチームだけで定義し、それを開発チームに移管したのが原因 2023年 SLOの再構築(SREの 民主化 ) ナレッジの共有、運用負荷の低減、カルチャーの醸成、人事評価指標との連動 SLOに関する勉強会(SRE -> DEV) 開発チームと議論してSLI/SLOを決める Datadog SLO Dashboard を使った SLO Dayの開催(内部改善Dayのようなもの)/ SLO定例 / 経営陣への月次定期報告 人事評価にSLOの遵守の目標を導入する SLOの改善、パフォーマンス改善の効果があった 2024年 さらなるSLO改善 ユーザー体験を軸にしたSLO運用 アプリやフロントエンドのメトリクス計測 注力指標の選定(Critical User Journey:CUJ) CUJはPdMと議論しながら決める 重要度 x 頻度で決める SLO監視文化を根付かせる 同じSREとして、共感できる内容が多い講演でした。 SLOの運用をSREだけで進めても、開発チームは目の前の開発があり、なかなかSLO改善の優先度が上がらないというのは、私も過去実感しました。 SLO遵守を目標設定に組み込んだり、ナレッジ共有をしてDevOpsを進めたりといった活動など、SREに閉じずに活動をされているのが印象的でした。 【 クラウド マネージドサービスの挑戦:多様なSI/ SaaS 環境の共通基盤化】 東芝 デジタルソリューション 鹿野様 www.datadoghq.com アプリ寄りなSRE / インフラ寄りなSRE / PJ横断的なSRE -> 今回はインフラ寄りなSREの話 多数のプロジェクトのインフラを管理するSREチーム SREのミッション 共通基盤を開発、24/365の運用、安定稼働 多様なSI/ SaaS 環境の運用を共 通化 問題解決のアプローチ クラウド 、オンプレ、アプリそれぞれのエキスパートをSREチームに編成 何を共 通化 するか選定 閉域網を使って、複数の クラウド サービスを運用するためのネットワークを構築 共通基盤「 クラウド マネージドサービス」 クラウド 、 プライベートクラウド 、オンプレ環境を監視する共通基盤サービス 運用チーム立ち上げ 監視ツールは全てDatadogに統一し、学習コストを下げる アラート件名と本文の統一 -> 運用手順の共 通化 東芝 グループが提供する様々なソリューション・サービスの クラウド 環境を24時間体制で運用するための共通基盤として、監視ツールをDatadogで統一するという内容でした。 DatadogはIntegrationsを使用して、多様な クラウド サービスの監視を行えるので、確かに統合モニタリングとしても有用です。 また、アラートの件名や本文を統一するという運用ルールは、弊社の監視モニタリング改善でも取り組みたいと思いました。 【Workflow automation によるインシデント原因調査の自動化】Degica SRE 伊藤様 www.datadoghq.com 決済代行システム: KOMOJU サービス監視にDatadogを利用 インシデント復旧時間の改善 迅速に障害検知できること 問題の原因特定の時間をいかに短縮するか? インシデント発生時の例 -> 4xx / 5xxエラーが多発した 何から調べるか? 直近のデプロイ有無確認 LBのログ確認 APM のトレースログ 真因は、ユーザが誤ったカード情報を入力したことによりエラー数が増加した 偽陽性 アラート 優れたエンジニアであっても、 ドメイン 知識がないと解決に時間を要してしまう 対策:One Monitor, one runbook DatadogのWorkflow Automation アラートを契機に起動 条件によって処理を分岐 ユーザのカード情報に起因するエラーか判断 偽陽性 アラートか、要調査かをSlackに通知する ただし、ログの料金には注意 明日からできること 障害を迅速に検知できるようにする 監視モニターには必ずRunBookを用意 必要に応じてAutomation化 automation化する価値があるかどうかはよく考える このセッションでは、いかにインシデント発生時の復旧時間を改善するかといったテーマで、自社でサービスを運用していると必ず課題となる内容です。 Workflow automationは使用した事がないのですが、条件や対応手順が明確であれば、障害発生時に自動的にアクションまで実行されるのは便利ですね。 とはいえ、automationの作り込みは難しいと想像します。伊藤様も、「年に一回起きるアラートに対してautomationを作っても、価値は大きくない」とコメントされていました。 全体的に、登壇者にSREの方が多いのが印象的でした。 確かにこうしたモニタリングに関する業務はインフラ運用チームやSREが担う事が多いですが、SREという職種について国内での認知が広まったのも一因ではないかと思います。 ワークショップ 昼休みを挟み、午後はワークショップとパネルセッションに分かれます。 私はワークショップ「Datadog101:SRE」に参加しました。 午後のワークショップ、パネルセッション案内 ワークショップ用のDatadogアカウント、コンソール、 IDE などが用意され、設問に従ってDatadogの活用方法を学んでいきます。 Datadogは非常に機能が多いため、ワークショップでこれまで使ったことのない機能に触れる事ができるのは良いですね。 ワークショップは基本的に英語での提供ですが、今回のプログラムはDatadog日本法人によって翻訳されていました。 最後に クラウド 上でサービスを提供する事が当たり前となり、マイクロサービスによる コンポーネント 間の通信の複雑化などに対応するため、モニタリングも クラウド ネイティブである事が求められます。Datadogはそうした要求に対応するモニタリングツールの1つであり、弊社も活用していこうとしています。 今回のSummitでは特にユーザーのセッションが参考になり、モニタや ダッシュ ボード活用方法のヒントを得る事ができました。 また、ワークショップを通じてDatadogの機能理解も深める事ができました。
備忘録として残しておきます。 こんな感じで、IAMユーザーにStatement AとStatement Bを付与するインラインポリシーがあり、このインラインポリシーにStatement Cを追加しようとしました。 ところが、コードを変更してterraform applyをかけたところ、「LimitExceeded: Maximum policy size of 2048 bytes exceeded」と怒られてしまいました。 │ Error: putting IAM User (****) Policy (****): operation error IAM: PutUserPolicy, https response error StatusCode: 409, RequestID: ****, LimitExceeded: Maximum policy size of 2048 bytes exceeded for user **** どうやらポリシーの合計が2048 bytesをオーバーしてしまったようです。 仕方ないのでインラインポリシーを2つに分けました。 怒られました。 │ Error: putting IAM User (****) Policy (****): operation error IAM: PutUserPolicy, https response error StatusCode: 409, RequestID: ****, LimitExceeded: Maximum policy size of 2048 bytes exceeded for user **** 「言いがかりをつけるな!」と言いたい気持ちをSlackに吐き出しながら調べてみると、 こんなドキュメント を見つけました。 IAM ユーザー、ロール、またはグループに必要な数のインラインポリシーを追加できます。ただし、エンティティごとの総ポリシーサイズ (すべてのインラインポリシーの合計サイズ) は以下の制限を超えることはできません。 ・ ユーザーポリシーサイズは 2,048 文字を超えることはできません。 ・ロールポリシーサイズは 10,240 文字を超えることはできません。 ・ グループポリシーサイズは 5,120 文字を超えることはできません。 どうやらインラインポリシーを分けたとしても、合算されて制限がかかるようです (どうして) この方法では制限を回避することはできないようです。 結局それぞれを個別の管理ポリシーにし、IAMユーザーにアタッチすることによってこの問題を回避しました。 ユーザー、ロールに関わらず、インラインポリシーが太っていくような実装はしないほうが良さそうですね。 MNTSQ株式会社 SRE 西室
こんにちは、MNTSQでエンジニアをやっている平田です。 ありがたいことに生成AI関連のイベントでLTする機会を何度かいただいており、その発表資料の小ネタについて嬉しいコメントをいただきましたので、記事にすることにしました。 トーク ン節約の方法考えてたら、ぴったりのもの見つけた。 JSON を unpretty-print して、改行と空白を消す方法めちゃめちゃよい。 https://t.co/hGCW8AT9eL — Yuto (@YutoY3629) September 4, 2024 はじめに プロダクトで生成AIを活用する際、 トーク ン数(特に出力)の節約は重要です。 その理由は主に次のようなものです: 生成AIの課金体系が トーク ン数に依存する トーク ン数が多いとターンアラウンドタイムが長くなる また、プロダクトでの生成AI活用では、 JSON 形式で出力を構造化することが一般的です。 関連する話題として、ChatGPTやGeminiでは JSON 出力を強制するStructured Outputsが提供されていますね。 openai.com この記事では、プロダクトでの生成AI活用を念頭に置き、出力 JSON を圧縮(unpretty-print)することで トーク ン数を節約する方法をご紹介します。 やり方 やり方は非常に簡単で、プロンプトで「unpretty-printして」と指示するだけです。 次の例は、文章を JSON スキーマ に従って構造化するプロンプトで、unpretty-printの指示を加える前後の差分を示したものです。 {text} に文章、 {json_schema} に JSON スキーマ が入ります。 {text} -Transform the above text into a JSON according to the following JSON schema. Do not output anything other than the JSON. +Transform the above text into a JSON according to the following JSON schema. Output the JSON in an unpretty-printed format, and do not output anything other than the JSON. ```json {json_schema} ``` 日本語でも同じようにできます。 {text} -上記の文章を次のJSONスキーマに従って変換してください。JSON以外は何も出力しないでください。 +上記の文章を次のJSONスキーマに従って変換してください。JSONはunpretty-printedフォーマットで出力し、JSON以外は何も出力しないでください。 ```json {json_schema} ``` 効果 unpretty-printにより JSON から空白や改行が除去され、わたしが試した一例 *1 では 約43%の トーク ン節約 *2 になりました🎉 JSONのunpretty-printによるトークン節約効果 また、(少なくともわたしが試した限りでは) unpretty-print前後で空白や改行以外の差分はありません でした。 もちろん出力内容によっては差分が出ると思いますが、プロダクトとして許容可能な差分なら コスパ の高い手法だと思います。 ぜひ、試してみてください。 この記事を書いた人 Takumi Hirata MNTSQの アルゴリズム エンジニア。流離の なんでも屋 。 *1 : https://tech.mntsq.co.jp/entry/2024/06/17/110000 で紹介しているタスク *2 : tiktoken による計数
こんにちは! SREチームマネージャーの藤原です。 今回はライトな話題として、s3:ListBucketの プレフィックス 指定で時間を無駄にしたお話をしたいと思います。 よくあるs3リソースへのアクセス用ポリシー 特定のS3 バケット 内オブジェクトへの書き込み、読み込み、一覧表示としたい場合以下のようなIAMポリシーを作成することがよくあるでしょう。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": [ " s3:PutObject ", " s3:GetObject ", " s3:ListBucket " ] , " Resource ": [ " arn:aws:s3:::バケット名 ", " arn:aws:s3:::バケット名/* " ] } ] } 特定の バケット 内のオブジェクト全ての場合はこれで問題ありません。 では、 指定した バケット の特定 プレフィックス を持つオブジェクトのみを書き込み、読み込み、一覧表示したい場合 はどうなるでしょう。 意図した動作をしないパターン まずは意図したとおり動作せず失敗したパターンです。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": [ " s3:PutObject ", " s3:GetObject ", " s3:ListBucket " ] , " Resource ": [ " arn:aws:s3:::バケット名/aaaa/bbbb/* " ] } ] } 一見良さそうに見えます。 オブジェクトのアップロード、ダウンロード共に問題なく、 s3:PutObject 、 s3:GetObject は意図した通りに動作しています。 一方で AWS CLI を使って aws s3 ls s3://バケット名/aaaa/bbbb/ とすると以下のようなエラーが返ってきます。 An error occurred (AccessDenied) when calling the ListObjectsV2 operation: User: arn:aws:iam::アカウントID:user/IAMユーザー名 is not authorized to perform: s3:ListBucket on resource: "arn:aws:s3:::バケット名" because no identity-based policy allows the s3:ListBucket action 指定した バケット への s3:ListBucket は認可されてないと言われてしまいます。 原因の特定 原因の特定のために、 AWS の公式ドキュメント( Amazon S3 のアクション、リソース、条件キー )を見てみましょう 1 。 正常に動作している GetObject 、 PutObject を見てみましょう。 Resource type として object が必須指定となっています。 オブジェクトのARNは arn:${Partition}:s3:::${BucketName}/${ObjectName} の形で指定します。 ListBucket の行を眺めてみると、 Resource type として bucket が必須指定となっています。 バケット のARNの指定例は arn:${Partition}:s3:::${BucketName} となっており、先で失敗した内容では Resource で指定している内容が適切ではないことになります。 では、 ListBucket で特定 プレフィックス の配下だけを参照できるようにするにはどうしたら良いでしょう。 再度公式ドキュメントを眺めてみます。 ListBucket では、 Condition keys に s3:prefix が指定できることがわかります。 s3:prefix 条件キーを利用することで、キー名の プレフィックス を基にアクセスをフィルタリングすることができます。 最終的な形 s3:ListBucket はリソースとして バケット を指定しなければならず、 プレフィックス でアクセス制限をしたい場合は s3:prefix を条件キーとして指定する必要があることがわかりました。 最終的に 指定した バケット の特定 プレフィックス を持つオブジェクトのみを書き込み、読み込み、一覧表示 するためのポリシーは以下のようになりました。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": [ " s3:PutObject ", " s3:GetObject " ] , " Resource ": [ " arn:aws:s3:::バケット名/aaaa/bbbb/* " ] } , { " Effect ": " Allow ", " Action ": " s3:ListBucket ", " Resource ": " arn:aws:s3:::バケット名 ", " Condition ": { " StringLike ": { " s3:prefix ": " aaaa/bbbb/* " } } } ] } トラブルシュートの際は最終的には公式ドキュメントをしっかり読み込むことが重要ですね。 なお、本エントリで解説している内容は2024年9月時点のドキュメントをもとにしています。将来的には変更されている可能性がある点にはご留意ください。 ↩
こんにちは、MNTSQでエンジニアをやっている平田です。 先日、「生成AI時代のリーガルテック」という題目でお話させていただきました。 generative-ai-conf.connpass.com 合計240名の方にご参加いただいたとのことで、ご視聴いただいた方々、ありがとうございました。 本稿で簡単に内容を紹介させていただきます。 発表資料 speakerdeck.com パネルディスカッション テーマ1: リーガルを扱う上で難しいこと/それに対して工夫していること リーガルテック業界全体でいえば弁護士法72条との関係を取り上げられることが多いですが、MNTSQはサービスの性質上、弁護士法72条との関係で大きな問題は顕在化していません。MNTSQの生成AI活用で注目したいのは契約データの性質で、次のような観点があります。 契約データは機密情報なので、プロダクトは強固なセキュリティの上に実現する必要がある 契約データは長いので、生成AIを活用する上でコンテキストウィンドウが十分でなくchunkingが必要になったり、Lost in the Middle *1 の影響が大きくなったりすることがある 契約書の専門性が高いため、 アノテーション やモデル評価において ドメイン エキスパートとのコラボレーションが重要になる テーマ2: ドメイン 知識ある人とどう融合させているか ドメイン を横断するコラボレーションはMNTSQが創業当初から重視している文化の1つです。 https://speakerdeck.com/mntsq/mntsq-careersdeck?slide=22 コラボレーションを重視している点は変わりませんが、仕事の内容は変わります。 ML時代 ドメイン エキスパートによる アノテーション -> MLエンジニアによる モデリング -> 推論結果を一緒にエラー分析 生成AI時代 訓練用の アノテーション と モデリング がプロンプトエンジニアリングに置き換わった ドメイン エキスパートにプロンプトエンジニアリングを開放することで機能開発をスケール コードとプロンプトをうまく分離して管理する仕組みが重要 推論結果を一緒にエラー分析するところは変わらず、エンジニアはプロンプトの改善をサポート Slidoの質問 いくつか質問をいただいたのですが、時間の都合ですべてにお答えすることができませんでしたので、この場をお借りして回答させていただきます。 Q. 法律が変わったりしたときに、過去の事例が使えなくなったりするケースがあると思うんですがどうやって学習データの管理をされてますか? MNTSQで扱っている契約データは比較的時間依存性の低いデータということもあり、法律改正等によるデータ変化がアウトカムに著しく影響を及ぼすといった問題はまだ顕在化していません。問題が顕在化した際には、関連する法令等を契約データに メタデータ として持たせてフィルターするといった方法が考えられます。 Q. 品質はどうやって管理されてますか?テストもどうやって行われているのか?改善手法も。 タスクによって方法が変わります。テキストの分類・抽出といったタスクであれば アノテーション データを使った 定量 評価が可能ですので、MLOpsに近い仕組みで品質管理できます。一方で、自然文を出力するタスクは ドメイン エキスパートと意思決定者による定性評価になります。改善手法はまだベストプ ラク ティスがないので探索的です。プロンプトエンジニアリングの沼にハマらないように、 テストファースト で進めること、プロンプトを生成AIにレビューさせることを検討しています。 Q. リーガルテックならではのプロダクト開発で苦労した点や注意しなければならないことは何かありましたか? パネルディスカッションのテーマ1と同じ回答になります。 Q. RAGを活用されているとのことですが、fine-tuningモデルの開発などに取り組まれる可能性ありますか?また、リーガルテックにおけるfine-tuningとRAGのメリット・デメリットについてご意見あれば伺いたいです もちろんfine-tuningや(継続)事前学習は関心のある技術領域です。コストや体制の都合でまだ十分な検証はできていないですが、関連研究や事例を調査しながら今後チャレンジしていきたいと考えています。リーガルテックにおけるfine-tuningとRAGの関係に関しては関連研究 *2 でも報告されているように、うまく組み合わせることで高い性能を発揮するのではないかと期待しています。 Q. LLMのバージョンが上がったり、GPTに加えてGeminiその他の選択肢も増えてきたと思いますが、どのよう選定していますか。 次のような観点を重視しています。 タスクに対して十分な性能(コンテキストウィンドウ、精度、速度、スケーラビリティ等)かどうか ビジネスが成立するコストかどうか 日本リージョンで提供されているかどうか Q. 生成AIを活用した機能開発で、取り組んだけどうまくいかなかった事例は何かありますか?あれば理由も知りたいです 基盤モデルの知識をベースにして生成するタスク(例えばゼロから契約書を作る等)はあまりうまくいっていないです。理由は多分に推測を含みますが、基盤モデルが十分な契約データを学習していないこと、条文の機微を制御しきれないこと等が考えられます。 Q. 契約書を作成する過程で、契約当事者のレビューの後に契約内容の交渉、歩み寄り、妥協を検討する場面があると思いますが、交渉や歩み寄りの部分で生成AIにサポートを期待できることはありますか? あります。過去の契約データに付随する交渉のログや成果物を分析し、新たな契約の交渉に活用するという考え方はCLM(Contract Lifecycle Management)の本質であり、この分析や交渉アプローチの提案に生成AIは大きく寄与すると考えています。 Q. 生成AIを活用したプロダクトをユーザーに提供する場合、AIの性質を理解した上でサービスや機能設計を行うことが重要と考えますが、「これはリーガルテックならではの生成AIに関するサービス設計だろう」と思う部分があれば教えてください。 網羅的でなくて恐縮ですが、パッと思いつくものだと例えば次のような観点があると思います。 弁護士法72条に抵触しない(非弁行為にならない)ようにユーザー体験を設計 契約データを扱う上で、強固なセキュリティの上にプロダクトを実現 コードとプロンプトを分離しプロンプトエンジニアリングを ドメイン エキスパートに開放することでプロダクトデリバリーを高速化 Q. 他の業界の活用事例などをキャッチアップすることはありますか?具体的にどの業界が参考になるなどあれば教えていただきたいです なるべく業界を固定せずにリサーチすることを心掛けていますが、企業の業務プロセスに生成AIを組み込んだ事例はMNTSQの ユースケース にも近いので特に注目しています。 Q. リーガルテックでの生成AI活用はベストプ ラク ティスがなく各社検証中の状態かと思いますが、今現在で生成AIがフィットするための必要条件として見えてるものはありますか? ビジネスは意思決定の連続であり、業務プロセスは非構造化データのやりとりです。また生成AIをビジネスにフィットさせるには、ビジネス価値に直結させるのが良いと思います。必要条件とは少し違いますが、次のような使い方がフィットするのではないかと思います。 意思決定の根拠を提供することで、意思決定の精度を高める 生成AIの説明可能性は旧来のMLと比べて飛躍的に向上しています そのため生成AIの出力を意思決定に使うハードルが低くなっています 業務プロセスをシームレスに統合する 生成AIは非構造化データを構造化するのにとても適しています 例えばMNTSQの ユースケース では、契約データから メタデータ の抽出等に活用できます 抽出した メタデータ は契約書の検索・推薦・管理等で役立ちます もしもっと詳しい話を聞いてみたいという方がいらっしゃいましたらお気軽にDM *3 等でお問い合わせください。 カジュアル面談でもお待ちしています🙌 careers.mntsq.co.jp この記事を書いた人 Takumi Hirata MNTSQの アルゴリズム エンジニア。流離の なんでも屋 。 *1 : https://arxiv.org/abs/2307.03172 *2 : https://arxiv.org/abs/2403.01432v1 *3 : https://x.com/_hrappuccino
こんにちは、MNTSQでエンジニアをやっている平田です。 MNTSQでは 自然言語処理 を使って契約書を解析したり検索したりする機能を開発しています。 契約書解析には、次のようなタスクがあります。 秘密保持契約等の契約類型に分類 契約締結日や契約当事者等の基本情報を抽出 条項(第1条, 第2条, ...)単位で分解 本稿では、これらの契約書解析タスクをGPT-4oに解かせてどんな結果になるか見てみます。 ざっくりやり方 GPT-4oの API を呼び出すところ ここではAzure OpenAIのGPT-4oを使います。 Microsoft のサンプルコードほぼそのままですが、一応貼り付けておきます。 from openai import AzureOpenAI client = AzureOpenAI( api_version= "2023-05-15" , azure_endpoint=os.getenv( "AZURE_OPENAI_ENDPOINT" ), api_key=os.getenv( "AZURE_OPENAI_API_KEY" ), ) response = client.chat.completions.create( model= "gpt-4o" , messages=[{ "role" : "user" , "content" : "ここにプロンプトを入れる" }], temperature= 0 , ) completion = response.choices[ 0 ].message.content 解析結果を JSON 形式で出力させるプロンプトテンプレート 解析結果をソフトウェアで扱いやすくするために、 JSON 形式で出力させます。 JSON 形式で出力させるテクニックはいくつかありますが、ここではプロンプトで JSON スキーマ を渡します。 プロンプトのテンプレートは次のようなイメージです。 prompt_template = """ \ {content} Transform the above text into a JSON according to the following JSON schema. Output in a code block, and do not output anything else. ```json {json_schema} ``` """ content : 契約書本文 json_schema : 解析結果の JSON スキーマ Pydanticで JSON スキーマ を生成するところ Pydanticは JSON スキーマ の生成とバリデーションができるので、今回やりたいことにぴったりです。 docs.pydantic.dev Pydanticを使わずに JSON スキーマ を直接書いてもよいのですが、 JSON スキーマ は複雑すぎて少なくとも私は読みたくないですし、 JSON スキーマ 自体のバリデーションが必要になるので Python で書いてmypyに静的解析させるほうが楽だと思います。 Pydanticで次のようなデータモデルを定義します。 from pydantic import BaseModel, Field class ClauseResponse (BaseModel): number: str | None = Field(default= None , description= "条番号" ) heading: str | None = Field(default= None , description= "条見出し" ) class ContractResponse (BaseModel): document_name: str | None = Field(default= None , description= "ドキュメントのタイトル" ) is_contract: bool = Field(description= "契約書のテキストかどうか" ) execution_date: str | None = Field(default= None , description= "契約を締結した日" ) effective_date: str | None = Field(default= None , description= "契約の効力が発生した日" ) renewal_term: str | None = Field(default= None , description= "契約の自動更新期間" ) notice_to_terminate_renewal: str | None = Field(default= None , description= "契約の自動更新を終了するための条件" ) governing_law: str | None = Field(default= None , description= "契約の準拠法" ) parties: list [ str ] = Field(default_factory= list , description= "契約の当事者名リスト" ) clauses: list [ClauseResponse] = Field(default_factory= list , description= "契約の条項リスト" ) JSON スキーマ は ContractResponse.model_json_schema() で生成できます。 { "$defs": { "ClauseResponse": { "properties": { "number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "条番号", "title": "Number" }, "heading": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "条見出し", "title": "Heading" } }, "title": "ClauseResponse", "type": "object" } }, "properties": { "document_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "ドキュメントのタイトル", "title": "Document Name" }, "is_contract": { "description": "契約書のテキストかどうか", "title": "Is Contract", "type": "boolean" }, "execution_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約を締結した日", "title": "Execution Date" }, "effective_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の効力が発生した日", "title": "Effective Date" }, "renewal_term": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の自動更新期間", "title": "Renewal Term" }, "notice_to_terminate_renewal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の自動更新を終了するための条件", "title": "Notice To Terminate Renewal" }, "governing_law": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の準拠法", "title": "Governing Law" }, "parties": { "description": "契約の当事者名リスト", "items": { "type": "string" }, "title": "Parties", "type": "array" }, "clauses": { "description": "契約の条項リスト", "items": { "$ref": "#/$defs/ClauseResponse" }, "title": "Clauses", "type": "array" } }, "required": [ "is_contract" ], "title": "ContractResponse", "type": "object" } GPT-4oが出力した JSON 文字列を抽出する プロンプトで Output in a code block と指示しているので、GPT-4oはコードブロックで囲われた JSON を出力します。 ここでは 正規表現 を使ってコードブロックから JSON 文字列を抽出します。 import re json_pattern = re.compile( r"```json\n(.+?)\n```" , re.DOTALL) if m := json_pattern.search(completion): json_text = m.group( 1 ) Pydanticで JSON 文字列をバリデーションするところ Pydanticを使うと JSON 文字列が JSON スキーマ に従っているかバリデーションできます。 json_object = ContractResponse.model_validate_json(json_text) もしバリデーションでエラーになった場合は、次のようなテンプレートでエラーメッセージをプロンプトに追加するとうまく修正してくれることがあります。(まあGPT-4oだとほぼ完璧な JSON を返してくれるので使う機会のないテクニックですが...) """ \ The following errors occurred, fix it. ``` {errors} ``` """ 実際にやってみた MNTSQにたくさんある契約書サンプルの1つを使って実験してみます。 契約書サンプル(先頭と末尾のページ) 上記契約書から OCR で抽出したテキストを入力すると、GPT-4oから次のような出力が返ってきます。 ```json { "document_name": "金銭消費貸借契約書", "is_contract": true, "execution_date": "2021-04-20", "effective_date": "2021-04-30", "renewal_term": null, "notice_to_terminate_renewal": null, "governing_law": null, "parties": [ "株式会社フォイエルバッハ商事", "MNTSQ株式会社", "板谷隆平" ], "clauses": [ { "number": "1", "heading": "消費貸借" }, { "number": "2", "heading": "借入条件" }, { "number": "3", "heading": "連帯保証" }, { "number": "4", "heading": "期限の利益の喪失" }, { "number": "5", "heading": "届出義務" }, { "number": "6", "heading": "反社会的勢力の排除" }, { "number": "7", "heading": "公正証書の作成" }, { "number": "8", "heading": "費用負担" }, { "number": "9", "heading": "管轄" } ] } ``` GPT-4oの出力から JSON 文字列を抽出し、Pydanticのデータモデルでバリデーションしたところ、エラーなく読み込めました。 解析結果の値も完璧です 🚀 実は execution_date (締結日)と effective_date (発効日)って別物なのですが、これらをちゃんと区別できていますね。すごい! 感想 ちょっと前まで、契約書解析は次のような工程で開発していて、1機能リリースするのに数ヶ月かかることが通常でした。 アノテーション モデリング テスト リリース ChatGPTを使った開発では、 アノテーション と モデリング の代わりにプロンプトエンジニアリングを行います。 これにより次のような嬉しさ(開発のスケールアウト)があります。 数ヶ月かかっていた アノテーション と モデリング が数日になる MLエンジニアがいないとできなかった モデリング が非エンジニアでもできるようになる GPT-4でも同様の開発はできるのですが、 API の料金が高く選択肢に入りにくい状況でした。 一方、GPT-4oはGPT-4に比べて破壊的に安く、選択肢に入るビジネスモデルが大幅に増えたのではないかと思います。 Azure OpenAI Serviceの価格表 (執筆時点) こうやってできることが増えていくとワクワクしますね 🙌 残る課題 これでAIが仕事を奪ってくれて明日から遊べるぞ!!!!とはならないのが残念なのですが、実際に契約書解析のプロダクトでGPT-4oを使う上でどんな課題があるか、いくつか挙げてみます。主に契約書というデータが長過ぎることに起因するものです。 GPT-4oの トーク ン制限数 GPT-4oの入力 トーク ン数は最大128kです。やばいですね。一方、契約書の トーク ン数はだいたい1ページ500 トーク ンです。MNTSQには数百ページの契約書が入ってきたりするのですが、仮に300ページとすると150k トーク ン必要なのでそのままプロンプトに入力するにはちょっと足りないのです。 また、GPT-4oの出力 トーク ン数は最大4kです。条項数が100を超える契約書もあるので、本稿で紹介した解析結果を得るには心許ない数字です。 Lost in the Middle こちらの論文 にもあるように、プロンプトの中間にある情報は忘れられがちです。契約書は長いのでこの問題が結構クリティカルです。 GPT-4oの料金 いくら安くなったとはいえ、128k トーク ン全部使い切るようなプロンプトを入力すると1回のリク エス トで100円くらいかかるので、サービスのプライシングロジックに配慮した戦略が必要です。 GPT-4oのターンアラウンドタイム 爆速!と言われたGPT-4oですが、60ページくらいの契約書データを使ってプロンプト入力から JSON 出力の時間を測ると1分くらいでした。サービスで提供したい体験によっては許容できない可能性があります。 他にもありますが、MNTSQではこのような課題と向き合いながらLLMを活用したプロダクト開発を行っています。 もしこの記事に興味をお持ちいただいて、「他のデータだとどうなの?」とか「課題は解決したの?」とか聞いてみたい方がいらっしゃいましたらお気軽にDM *1 等でお問い合わせください。 この記事を書いた人 Takumi Hirata MNTSQの アルゴリズム エンジニア。流離の なんでも屋 。 *1 : https://x.com/_hrappuccino
こんにちは。 すべての合意をフェアにしたいMNTSQ(もんてすきゅー)のJessie ( @Jessica_nao_ ) です。 仕事では、 Ruby on Rails を"利用"させていただき、法務業務に利用される SaaS Webアプリの開発を行っています。 RubyKaigi2024が間も無く開催されますね! 土日から沖縄入りされている方の投稿が Twitter に流れてきており、既に私の気持ちもかなり高まってきています✈️ この記事では、「今回RubyKaigi に初めて参加するぞ!」という方に向けて、2023年に初めてRubyKaigiに参加した私が、知っておくと嬉しいと思うTipsやRubyKaigiのあれこれをいくつか紹介していきます📝 なお、公式アナウンスは非常に重要です。オンサイト参加の方で、 Onsite Information - RubyKaigi 2024 をまだご覧になっていない方は、必ずご確認を! いくつかは私のチョイスでここにも記載しておきます📝 プレチェックインが、Day 0(5/14) 16:00 ~ 19:00 にある Day1 の混雑緩和のため、ぜひ使ってほしそう Discord → https://rubykaigi.org/go/discord イベントページ → Events - RubyKaigi 2024 day1 の公式 Party は別チケット 写真に写ってOKか、否かの意思表示できるストラップがある 「仕事では何をしていますか?」「 Rails を書いています」 Webエンジニアの私がするコミュニケーションでは、「 Rails を利用してWebアプリを開発している」という意味で、「 Rails を書いている」と言ってしまうこともあると思います。しかし、RubyKaigiではこの言い方は誤解されてしまう恐れがあります⚠️ RubyKaigiには、 Ruby や Rails のコミッターの方もたくさんいらしています。そんな中で、「 Ruby / Rails を書いています」と言ってしまうと、相手は文字通りに受け取り「あ、コミッターだ!!」となってしまいますね。笑 私は昨年、初めましての方に対して「 Rails を書いています」と伝えてしまったせいで、ちょっと気まずい思いをしました😇 自己紹介では「 SaaS の業務アプリケーションを開発しています」のように始めるべきでしたね 1 。 Ruby を"使う"人には高度なことも多いセッション 昨年の私は、「 スケジュール (※ 2024版のリンクは こちら ) を見て、ピンと来ないものも多いがとりあえずこれこれ聞くぞ」と直感でピックアップしたセッションを聞こうと挑みました。正直予習も全然できておらず、 "YJIT" や " RBS " 2 など、当時の私には言葉自体馴染みのないものもあり、セッションを聞いてもチンプンカンプンなこともあり面食らってしまいました。 これを読んでくださっているRubyKaigi初参戦の方の中にも、心構えを間違えて、同じように絶望してしまう人が出てくることを懸念しています。 この点に関して、ちょうど、 STORES CTO藤村さんが語っていらっしゃる良いBlog を見つけたので、一部をここで共有いたします。 RubyKaigiって Ruby を開発している人のカンファレンスで、 Ruby についてのカンファレンスなんですよ。 Ruby を使ってどうとかいうのよりも、 Ruby 本体をどう作るかにフォーカスがあるので、 プログラミング言語 の最新機能とか最新の実装って作ってる人しかわからない人が多いんですよ。(中略) わからなくていいのだろうかっていう引け目を感じることは一切必要なくて、こんなにわからないことがわかったみたいな、わからないジャンルを知ること自体がもはや成果だと思います。 弊社EMの飯田さんも同様に、昨年RubyKaigiについて、次のようなことを語ってくれました。 Ruby の最先端であるから、分からないことがあるのは当然 Ruby を開発している人たちが、こんなことに今取り組んでいるのかと知れることは成果 普段の業務ではあまり触れられることのないところに接点を持って、興味の幅を広げられると最高 RubyKaigiは、例えば Kaigi on Rails と比べて、 Ruby / Rails を利用して仕事をしている人には"実用的" 3 ではないと感じる話題も多いです。 それでも分かりたいセッション とは言え、せっかく参加できて得た貴重な機会ですからスピーカーの方の熱量を少しでも身近に触れて、自分の幅を広げられる機会にしていきたいところですね🚀 今回は、CRubyについてのセッションが多いとのことなので、 Masaki Hara (@qnighy) さんが #rubykaigi2024_koryaku にて発表されたスライド「 CRuby超入門 」に目を通してみるのはとても良さそうです! 本スライドでは、読み進めるのに「難しい部分」の補足をメインにしてくださっていますが、ここでは、「CRubyなんて初めて知ったよ」という方向けに、CRubyに触れる一歩目のヒントを書いてみたいと思います。 例えば、 ruby/array.c#L1388 を見ると、 Array#pop はここで実装されているのが分かります。これだけでも、このレポジトリが”完全なる未知のもの”ではなくなり、「 Ruby の実装見たことある!!」が真になりますね😆 もう一歩進んで、無駄なメモリを解放するために resize している様子も見られます 4 。 Ruby を使っている際には、Arrayの長さについては無関心でいられますが、このような実装のおかげなのですね。 parser や Garbage Collection などについても、あとほんの少しでも踏み込んでいけるよう、とりあえずレポジトリをcloneして、移動中でも眺めていきましょうか✈️ Be Nice Yukihiro Matz (@yukihiro_matz) さんの「Be Nice」という言葉が印象的でした。 この大きなカンファレンスを良いものにするために、Organizerやスタッフの皆さんの努力と、コミッターやスピーカーの方々の貢献、最後に、これらに加えて、RubyKaigiの参加者全員の楽しむ姿勢と気配りが欠かせないと感じました。 私の昨年の体験をお話しします。 Day1のお昼のことでした。 上述の飯田さんが、前職のエンジニアの方も誘って、MNTSQメンバー6人とその方の計7名でランチに行きました。 最初、何が起きているのか良く分かりませんでした。私の日常の常識では、「これって通常だったらやや気まずい状況じゃないっけ?」という気持ちになっていました。笑 RubyKaigiはみんなが交流を楽しむ場でもあり、これは至って自然な、推奨されるべき事象でした。初めましての人とであっても、きっかけさえあれば自然とご飯に行けます。目の前で手持ち無沙汰にしている方がいれば、例え初めてお会いした方でも、積極的に話しかけて大丈夫です。きっと、目の前の方もあなたとの会話を望んでいるでしょう。 去年の私は当初、(お酒を飲んでいない時には)「セッションにも全くついていけない自分が、初めましての Rubyist の方と会話を成立させられるのだろうか?」という不安もありましたが、そんなことを心配する必要は実は全くありません。 Ruby の話はもちろん、普段の仕事のことを尋ねたり、自分の話を聞いてもらっても良いですね。せっかく社外の人と多く話せる機会ですから、自分のチームで今抱えている・取り組んでいる問題について、他のチームではどうしているのか教わるのも良いです(私は今回、カンバン事情や Rspec のお作法などを聞いていきたい気持ちがあります)。 Twitter で利用できるタグ達 RubyKaigi会期中、あるいはその前後でぜひ使っていきたい Twitter のタグを紹介します。 #rubykaigi RubyKaigiに関することを、どんどんつぶやいて盛り上げていきましょう🚀 #rubykaigi2024 も一定使われるだろうと思いますが、多数派は #rubykaigi という印象ですね👀 #rubykaigiA , #rubykaigiB , #rubykaigiC Schedule - RubyKaigi 2024 に記載のタグを、見つけることはできましたか?👀 セッションが行われる会場ごとにタグが用意されていて、セッションの内容や感想、あるいは期待について、みなさん多く投稿されています。 スピーカーの皆さんに感想を届けるのも非常に良いことなので、活用していきましょうっ! #rubyfriends 2023の会期中の様子は こんな 感じです! セルフィーを投稿する際には、ぜひ利用していきましょう🙌 ちなみに、 Onsite Information - RubyKaigi 2024 によると、今回は自分が写真に映ることに関する意思表示用のLanyard(ストラップ)が配布されるようですね📸 素敵な取り組みです。 #MNTSQ 弊社MNTSQのタグです!! MNTSQは、RubyKaigi 2024 にGold Sponsorとして協賛いたします🙌 弊社の参加メンバーとの写真や、 ノベルティー について Twitter 上に載せる際には、一緒にタグ付けしていただけると嬉しいです! 無理をせず楽しむ! 最後になりますが、終わっても最高だったと言えるRubyKaigiを過ごすために、以下の点には気をつけましょう。 RubyKaigiのメインコンテンツはセッションですね。初参加で真面目な方は、「セッションは全部出るもの」と思うかもしれませんが、体力的にも、内容の高度さ的にも、それは厳しいです。 セッション以外にも、他の Rubyist と食事に行ったり立ち話をしたり、企業ブースを回ったり、飲み会などの企画に(ref: Events - RubyKaigi 2024 )に参加したりと、楽しむべきことは多くあります。 去年の私は連日エンジョイして、睡眠時間もそこそこに会場に足を運んでいたら、RubyKaigi後、1週間程度倦怠感が続きました。他のメンバーにも帰ってきてから具合が悪くなる人もいました。 お祭りごとですし、本当に楽しいRubyKaigiがあなたを待っていると思います。最高の思い出として記憶に残しておくためにも、無理はせずに身体は労わりながら楽しみましょう🙌 私は今回、火曜から土曜の朝まで 那覇 にて参戦します! みなさんにRubyKaigiでお会いできることを、とても楽しみにしています。 仮にコミッターでない場合に「 Ruby を書いています」と言っても、それはそれで笑ってくれるので安心してください📝 ↩ 今は、MNTSQでも導入されています ↩ 今すぐ活用することは難しいという意味で、何が自分のためになるかは、今全て分かることではありませんね! ↩ rb_ary_pop -> ary_resize_capa -> ary_heap_free_ptr -> ruby_sized_xfree と辿ると、メモリ解放まで行っているのだなぁという雰囲気が掴めます ↩
おはようございます!こんにちは!場合によってはこんばんわ! SREチームマネージャーの藤原です。 今年の3月からSREチームの中で、勉強会を定期的に実施するようになりました。 本エントリでは勉強会を開催するにあたって考えたことと、実際の進め方についての事例解説です。 勉強会を通じて実現したいことの説明から、テーマ設定、効果最大化のための開催形態と頻度、現時点(2024/4・5時点)の結果について解説しています。 勉強会を通じて実現したいこと チーム内勉強会を通じて実現したいこととして、以下を設定していました。 チーム・個人としてのレベルアップ 積極的な議論への参加 相互理解 チームとしての目線合わせ 勉強会を開催するからには何かしらのスキルや考え方を身に付けたいはずです。 また、学習効果を最大化するためにも、積極的な議論への参加を実現したいと考えました。 さらには、勉強会の中での議論を通じてチームメンバーが常日頃考えていることをメンバー間で相互に理解し、その上で今後のチームとしての取り組みについてメンバー間で目線のすり合わせをしたいと考えました。 基本的にはこれらの項目を実現できるような材料(テーマ)設定や開催形態を考えました。 勉強会の材料(テーマ)設定 勉強会としては、一種の読書会形式としました。題材としては、 The DevOps ハンドブック 理論・原則・実践のすべて を選びました。 The DevOps ハンドブック 理論・原則・実践のすべて 作者: ジーン・キム , ジェズ・ハンブル , パトリック・ボア , ジョン・ウィリス 日経BP Amazon 少し古い書籍では有りますが、名著として評価がある程度定まっている書籍であるため、適切に勉強会を運営さえできれば組織・チームとしてのレベルアップに確実に繋がるであろうということを考えて選定しました。もっと直接的に業務に役立つような書籍(たとえば、詳解Terraformなど)も材料として検討しました。 詳解 Terraform 第3版 ―Infrastructure as Codeを実現する 作者: Yevgeniy Brikman オーム社 Amazon この書籍自体はTerraformについて学ぶという意味合いではまちがいなく良書です。今回の題材が一通り終わったのちに具体的な技術を掘り下げる際には第一候補となり得ます。 一方で今後の組織として取り組みを進めていく上でメンバーの目線を揃えるという意味では、特定の技術について深ぼるよりは、その前提となる考え方、取り組み姿勢の観点を学べる書籍がよかろうという結論に至りました。 勉強会の効果を最大化するために考えたこと 私自身の経験の観点でもなんとなく本を読んだりしだだけよりも、他の人と議論をしたり、教えたりした場合の方が効率的ですし、 ラーニングピラミッド のような観点からも、議論したり教えあったりということが学習効果の最大化には良いようです。 career-ed-lab.mynavi.jp 一方的に情報を受け取るだけでなく、相互にコミュニケーションをすることで学習効果を最大化することを意図して、開催形態や頻度を考えました。 開催頻度・時間 頻度が低すぎると前回の内容を忘れてしまうこと、頻度が高すぎると日常業務に支障が出ることからから、業務時間中に週1回の開催としました。 1回あたりの時間が短すぎると本論としての議論が中途半端になってしまうこと、長すぎると議論がだらけてしまうことから、適度な長さとして45分と仮決めしました。 結果、毎週水曜日に45分間実施しています。 開催形態 勉強会の開催形態としてはさまざまな形があると思います。 講義形態もあればハンズオン形態もあります。それぞれの形態にも長所・短所があります。 現在実施している勉強会では、以下の3つを意識しています。 ひと通り対象書籍は読み通すこと 勉強会の準備が負担となりすぎないこと 積極的な発言、議論につながること 具体的な勉強会の開催形態としては、次のように進めました。 開催前までにやることと、開催時にやることの2つがあります。 開催前は対象となる章(1回あたり2章を想定)を読みます。 読んだ内容のうち気になる文言をひたすら Google Docs に引き写します。 引き写した文章にたいして、自身の感想やエピソード、そのほか追加情報(たとえば似たような内容の記述のある他書籍の紹介など)があれば記述します。 勉強会当日は、参加者ごとにメモした部分について軽く説明してもらいます。他の参加者はそれらにコメントや、感想を投げかけたり、場合によっては反論したりします。また事前に準備した Google docs に、当日議論した内容をログとして残したり、 Google docs のコメント機能でコメントを追加したりもします。 そのような形で、勉強会の開催前に考えたこと、勉強会の最中に他の人が考えたことを受け止めつつ、自身が他の人が考えたことをどう捉えたかをフィードバックするといった形で勉強会を進めています。 結果としてどうなのか(現時点 2024/4時点での感触) 結果としては、チームの学びの場として機能していると思います。 積極的な議論といった観点ではかなりうまくいっています。1回の開催でA4サイズでおおよそ3ページ、 Google Docs のコメントで30コメント強程度の分量になります。 45分では少し足りないかもしれないくらいの感覚は少しありますが、次回もっと議論したいという飢餓感に似た感覚を持ち続けるにはちょうど良い開催時間になっています。 勉強会のログ(具体的な中身については非公開です) 勉強会の準備についても、書籍を読み進めた上で気になった部分を抜き出すことが準備としてはミニマムなので、さほど準備の負担にもなっていないようです。 具体的な学びについては、勉強会の中で、”対象の捉え方としてそのような捉え方をしたことがなかった”といった新しい視点を身につけられたり、”よく言われるプ ラク ティスがどのような経緯で出来上がったものなのか理解できた”といったフィードバックが得られています。参加者からも勉強会を有意義なものだと感じてもらえているようです。 勉強会についての参加者フィードバックの例 書籍的には特定のツールやテク ノロ ジー にたいしてのスキルを向上させるものではないので、すぐには効果は出ないと思います。 考え方や取り組み姿勢に関わる部分なため、中長期的にさまざまな局面で効果が出るであろうことを期待しています。 今後は、学びをチームとしての成果にいかに繋げていくことができるか、勉強会参加者のモチベーションをどう高く維持していくかが試されそうです。
こんにちは。 GitHub Copilotを先日初めて触って、感銘を受けたMNTSQ代表の 板谷 です。MNTSQの代表をしておりますが、現役の弁護士でもあります。 なぜ私が、 GitHub Copilotに感銘を受けたかというと、「プログラミングの LLM による進化」は、契約という言語をコーディングするためにもドンピシャで使えそうだと感じたからです。 例えば、 GitHub Copilot では、自分の過去のコードを参照して、最適なコードをサジェストしてくれます。 これは、 契約に関わるすべての ビジネスパーソン が求めていたもの です!契約の 99.9%が過去のコードの使い回しであるにもかかわらず、毎回ゼロからコーディングするのが本当に苦痛だからです。ちなみに、前回契約と理由なく diff があると取引先に怒られます。笑 しかし、 GitHub Copilot 的なものが プログラミング言語 だけでなく契約言語でも求められるのは、よく考えると当然です。 以前にも記事にした のですが、 プログラミング言語 と契約言語はとても似ているためです。 どちらも「一定のインプットを入れると、必ず一定のアウトプットが返ってくるように設計された独自の言語体系」だからです。 契約言語とは「一定の紛争⇒一定の判決が出るように構造化されたコード」です。処理するのがコンピューターではなく裁判官であるというのが違いでしょう。 もし「リーダブルコード」を弁護士が読んだら? - MNTSQ Techブログ もし、契約言語の世界に GitHub Copilot が産まれたら、すべてのビジネスが圧倒的に加速し、無駄に難解な契約というものが変革されるのではないでしょうか…? この記事では、このすべての ビジネスパーソン の夢を追及してみます。 プログラミングと LLM  コード / コードコメント / コミットメッセージ ソフトウェア開発においては、まずコードを書き、コードの変更箇所の説明のため コミットメッセージ や プルリク エス トの説明 を書きますよね。 GitHub Copilot は、 自然言語 での指示によって、これらの作成を助けてくれます! 1 GitHub Copilotによる、コードやコミットメッセージの生成 GitHub Copilotによる、プルリク エス トの説明の生成 2 これらのアウトプットの流れは、契約業務でも実はまったく変わりません! ・まず 契約本文 (=コード)を書き、 ・次にその意図が伝わるように Microsoft Word にコメント (=コミットメッセージ)をして、 ・差分の発生理由を メールに記載 して関係者に通知するのが(=プルリク エス トの説明)、契約業務の流れです。 すべて、心をこめて手作りしています。もしも、これらの契約業務のアウトプットを GitHub Copilot のように助けてくれるなら、本当に夢のようです。確信があります、 私なら泣きます 。  Copilot?いやまずは VS Code だ GitHub Copilot の素晴らしいところは、LLM によるサジェストが、例えば VS Code などエディタのなかで完結するところです。コミットメッセージは、 GitHub に一元的に記録されます。 ここで契約に関わる ビジネスパーソン は、そもそものプログラミング環境の違いに絶望します…… VS Code ??…それって、Word のこと? GitHub ??…それって、メールのこと? そう、 GitHub Copilot 以前に、そもそも契約には プログラマー を思った開発環境がないのです。Word とメールと紙だけで、歯を食いしばって生きてきました。 しかし、この 1 年に私たち MNTSQ が解決してきた課題はまさにそこでした。いかに Word を契約の プログラマー にとって開発しやすい環境にできるか、 GitHub のようなチームでの開発基盤を提供できるかが、ちょうど今クリアされたところです! RAG の設計  なぜ RAG なのか? GitHub Copilot が手に馴染む理由は、利用者の過去のコードを参照して、コードの記述の仕方を合わせてくれるところです。 その裏側にあるのは RAG という構造に近く、現在作業中の内容を踏まえて、過去のデータのうち参考になるものを特定し、それを変形して適用する(generation)という流れを辿っています。 契約に LLM を活用するため、RAG は非常に重要です! なぜなら、会社ごとに法務戦略は異なっているためです。 法務戦略は、それ自体が 言語化 されることは少なく、過去の類似シチュエーションにおける交渉データとしてのみ表現されています。これを踏まえて新たな契約を生成できれば、 契約の世界では本当に革命が起こせるはず です。  そもそも DB がない…! しかし、契約業界ではまたもここで、そもそも論にぶつかります…… 「過去データの DB?そんなものなくね?」 契約データは、オンプレのファイルサーバーに格納されていればまだマシで、メールの添付ファイルとして探せば残っているかも…という状況です。 実は、これこそ MNTSQ が創業以来解決してきた問題です。MNTSQ の登場以前には、一流企業ですら「自分たちがどのような契約を締結しているかわからない」という状況でした。 しかし、私たちが契約をデータベース化して、 NLP によって自動的にタグ付け・整理をすることで、契約というデータはようやく活用可能な状態になりました。 「データベースを提供している= RAG で使えるベストな参考情報を持っている」 ということです。ここを レバレッジ して、最高品質の契約を誰もが作れるようにしていきたい。  推薦 AI DB があれば、そのうち参考になるデータを特定できるか(retrieval)が次のポイントです。 これこそ、 ドメイン 特化 NLP の腕の見せ所です!ユーザーの意図に沿った過去データを特定できるように、 ドメイン 特化の メタデータ を自動付与し、絞り込みをかけていきます。 その選りすぐりされたデータを、いかに Word を VS Code 化し、最高の UX でサジェストするかをデザインしていきます。まさに契約のプログラミング基盤を作っているようなワクワク感があります。  ベストプ ラク ティスの提供 なお、皆さま日本最高の法律事務所をご存じでしょうか?その一つが「 長島・大野・常松法律事務所 」という、 形態素解析 が難しすぎる事務所です。 MNTSQ では、この長島・大野・常松法律事務所のトップ弁護士によるコード(=契約文言)を、ベストプ ラク ティスとしてコンテンツ化し、オープンに提供しています。 このベストプ ラク ティスであるコードも RAG においてサジェストし、ユーザーの契約に自然と取り込ませていくことによって、社会全体からアンフェアな契約を駆逐していきたいと考えています。 終わりに プログラミング言語 と比べると技術的進歩の余地がある契約言語ですが、これから アルゴリズム によって変革されるスピードは プログラミング言語 よりも圧倒的に速いはずだと考えています。まもなく契約は、 アルゴリズム が書き、交渉するものになるでしょう。 なぜなら、契約言語はプログラミングより圧倒的にパターン性が高いためです。実は、100-200 くらいのライブラリ(=契約条項)を組み合わせるだけで、90%以上のビジネスシーンを網羅できるのではないかと思います。 これが実現したときの社会的意義も大きいです。契約が不要であるビジネスはありません。また 「契約言語は、全国民が完全理解できることがなぜか前提になっている」 なかで、アンフェアな契約に騙されてしまう人を減らせるかもしれません。 「契約をつくる アルゴリズム 」が誕生するとすれば、これまで最前線の弁護士が書いてきたような最高品質のものにしたいと私は考えています。そのための、長島・大野・常松法律事務所というトップ事務所との提携です! 誰もが一瞬で、フェアな合意ができるようなプラットフォームを作れれば、職業人として一つ社会貢献ができるかな…という想いです。今まさに社会が大きく変化しようとしているところですので、もし興味を持ったエンジニアの皆さまがいれば、是非私とカジュアル面談をさせてください! hrmos.co この記事を書いた人 板谷  隆平 MNTSQ( モンテスキュー )社のCEO/ファウンダー。長島・大野・常松法律事務所所属の現役弁護士です。 /* 動画のためのCSS */ video { width: 100%; height: auto; } サンプル動画は GitHub 社様の ブログ から引用させていただきました。 ↩ プルリク エス トの説明を生成する機能は、 GitHub Copilot Enterprise でのみ提供されています(本記事投稿日現在)。 ↩
こんにちは。MNTSQの下村です。 コーポレートエンジニアとしてMNTSQ従業員の生産系向上施策等を実施していたりします。 最近では情報セキュリティ責任者としてもセキュリティ活動を推進しています。 Twitterもやっている のでフォローしてもらえると嬉しいです! こんなアイコンです 今回はMNTSQが 従業員同士の相互理解を深める ための施策として実施している Weekly Sync という取り組みについて紹介致します。 この記事で紹介したいこと Weekly Syncという制度の紹介について。 Weekly Syncの仕組み。 Weekly Syncを実施したことによる効果。 Weekly Syncについての概要&取り組み後の声 まずはざっくりとWeekly Syncの概要と実施したことによる従業員の声を紹介します。 概要 Weekly Syncとはすごくざっくりまとめると 自部署の取り組みのアップデート内容を他部署のメンバーに要約して伝えること を毎週実施することです。 他部署の取り組み紹介を知ることで相互理解や、自部署の取り組みについての気づきを発生させる取り組みとなっています。 Weekly Syncに寄せられた声 下記のような形でWeekly Syncの取り組み自体にはポジティブな効果が出ています。 新入社員からはMNTSQの事業内容のキャッチアップに役立ったという声 PdMとSalesが相互感謝する例 アポ獲得のトリガーになったり Weekly Syncとは? さて、ここからはWeekly Syncの内容について詳細を記載させていただきます。 まずMNTSQにはクリスタルクリアという Value の観点で、マネージメントミーティングログという経営会議の内容が完全公開されています。 (そもそも完全公開自体がすごいことなのですが、ここでは詳細を割愛し CEO板谷の記事のリンク だけ貼らせてもらいます。) Weekly Syncではこのマネージメントミーティングログに記載された各部署のアップデートを4-5人程度の小グループを bot がランダム作成、メンバー選出し 選出されたメンバーが5分程度に要約 して他の部署のメンバーに説明する取り組みです。 ミソなのが、その内容を 「自分なりの理解で説明すること」 にあります。 書いてある内容の正確性を最重視するなら、部署の長が説明すればよいのでは?と思う方もいらっしゃるかもしれません。ですがここで重視しているのは選出者が「自分なりの理解で説明すること」です。この取り組みにより 選出者の当事者意識が高まり、自部署への気づき・発見 が生まれることにつながります 。 (もちろん正確性も重視しており、読むだけで正確な理解が得られるよう情報は整理されています!) また、他部署のメンバーからすると、他部署のメンバーの補足説明等により、マネージメントミーティングログの内容だけでは把握しきれなかった 「現場の生の声」 が聞けることにより 生きた情報をキャッチする ことにも繋がります。 以上のことにより、短時間で 最新の事業状況を把握 でき、説明内容についての質疑応答などにより 相互の理解・発見を深める場 になっています。 なお、この取り組みは以前に Daily-sync と呼ばれて運用されていたものを100人組織に合わせてアップデートしたものになります。 Weekly Syncの実装内容について ここからはWeekly Syncの実装内容について記載させていただきます。 実は設計方針についてはCEO 板谷 の要望を元に、 アルゴリズムエンジニアの平田 が設計してくれました。 ただ、平田はMNTSQの中心人物であり 常に多忙です 。 「となると、これはコーポレートエンジニアの仕事でしょう!」と実装は私が引き受けることにしました。 余談 その日、平田が「NE KO NO TE MO KARITAI」「KASANAI...」と書いてあったTシャツを着ていたことからも想像を絶する多忙だったと予想します。(嘘) Tシャツ【ネコ ノ テモ カリタイ】黒字にピンクプリントの「ネコの手も借りたい」Tシャツ。忙しい時にさりげなく着たいデザインです。 #グラニフ https://t.co/EvKgONikDk pic.twitter.com/xEPMFatciM — グ ラニ フ (@ graniph _updates) 2020年8月10日 要件整理 まず、前身の Daily-sync についての流れを汲む必要がありました。 (成功している施策であったので、無為にしたくなかったのです。) Daily-syncについては組織規模が30人程度であれば、そこまで時間をかけずに相互理解が可能になっていてポジティブに効果していたのですが従業員数が増えてきたことにより、時間対効果が悪くなってきた側面がありました。 そこでDaily-syncの最大のメリットであった「 異職種間コラボレーションの質を高めること 」「 限られたリソースの中で、みなが最も優先度の高い仕事に取り掛かっている状況を実現すること 」をWeekly Syncで時間対効果を高めつつ実現するにはどうしたら良いかということをまず、CEO 板谷 と平田とディスカッションしました。 そこで、「 日次で行っていたDaily-syncを週次で30分という凝縮しつつ、相互理解を深めるにはどうしたら? 」という問いに対して前述した 「マネージメントミーティングログをお互いで説明しあう」ことが時間対効果が高いのでは? という結論に達しました。 また、CEO 板谷 からは下記のような要望もありました。 入社間もないオンボーディングメンバーは視聴のみ(喋らない)にしたい。 各部署のメンバーが数人程度でランダムに アサイ ンされるようにしたい。 そして、これに加えて更に... 毎週のことなので運用を極力、楽にする必要があります。 (事務/総務アシスタントさんが、手動で参加者を募り、ランダム アサイ ンするとかは絶対に避けないといけません) そして、参加者の方も 1. 参加/不参加の意思表示を容易にする必要がある。 2. 新入社員がWeekly Syncの取り組みを即座に理解するために運用方法を工夫する必要がある。 といった形で 正直めんどくさい しっかり実装方法を考える必要があります。 こんなことは思っていない こんな感じになりました1 - 通知処理 通知処理 「共有カレンダーに「Weekly Sync」という件名の予定が作成されているか否かをAM9時頃にチェックします。 「Weekly Syncが開催される日」みたいなものを別途管理するのは嫌なので、カレンダー登録状況をチェックすることで回避します。 予定があれば通知先チャンネルに「Weekly Sync参加有無」を ヒアリ ングする内容を通知します。 この時、従業員は参加の有無を回答しやすいように Slackリアクションで反応すれば良いだけ にします。 更に更に、リアクションしやすいように、 reactions.add method で押すべきリアクションをリアクションしておきます。 こうすることで、従業員はリアクション絵文字を探してリアクションするのではなく、ポチッとリアクションをすれば良いという1アクションで済みます。 また、参加表明リアクションには「リモート参加」か「物理参加」かを選べるようにしています。 こうすることで、リモート参加組はMeetのみで完結するように、物理参加組は対面で会話できるように会議室を bot が自動取得できるようにしておきます。 リアクションを付け忘れてしまう従業員にそなえて、 conversions.members method で通知先チャンネルに所属している人一覧を取得し、 reactions.get method でリアクションした人を 除いた 一覧にリマインド通知します。 こうすることで、リアクション済みの人には余計な通知をせず、リアクションがまだの方にはしつこくメンションされるのでリアクション付け忘れを最小化できます。 こんな感じになりました2 - ランダム小グループを作成 ランダム小グループ作成処理 マネージメントミーティングログが格納されている Google Drive フォルダを DriveApp でチェックし、 スクリプト 実行日のファイルを見つけ、そのファイルのURLを取得します。 このURLをこの後作成する小グループ宛にお知らせします。こうすることで従業員は「今日のファイルどれだっけ...?」と探す時間を無くせます。 reactions.get method で参加表明した方の一覧を抽出します。 社員マスタを スプレッドシート で管理しているので参加表明した方々の「所属部署」と「入社日」を抽出します。 入社日を抽出しているのは「入社3ヶ月以内」のメンバーはまだ不慣れな状況なので傍聴者として アサイ ンされるようにするためです。 あとは上記で取得したメンバーを「所属部署ごとにランダムでPick」し、「4-5人程度」の小グループ化したメンバー一覧を作成します。 その小グループごとにSlackスレッドを作成し、説明する人を bot が指名します。 新入社員が戸惑わないように、作成したスレッドにWeekly Sync開催の趣旨を説明した内容も通知しておきます。 こんな感じの通知をします。 通知内容 従業員側の対応 以上のことにより、従業員は下記のようなオペレーションでWeekly Syncに参加可能になります。 bot から参加可否の質問が到着。 従業員はリアクションするだけで参加要否の連絡が完了。 物理参加かリモート参加なのかも考慮しています。 開催時間になったら、参加者向けにWeekly Syncの内容を通知。 これによりWeekly Syncを知らない新入社員も運用可能。 通知内容 やってみた後の声 下記のような形で良いフィードバックを頂けたり... 嬉しい声 従業員に楽をしてもらうための工夫に気づいて頂いてお褒めの言葉頂いたりと... 嬉しい声2 Weekly Syncは誰の負荷をかけることもなく、ポジティブに運用され続けています(嬉しい) 最後に 本記事ではMNTSQで実施されている相互理解の仕組みとして「Weekly Sync」とその運用の裏側を紹介させていただきました。 相互理解の仕組みを検討中の方の参考になると幸いです。 MNTSQでは すべての合意をフェアにする というミッションを掲げています。 sg.wantedly.com そんなミッションに共感してくれる方、ぜひMNTSQで一緒に働きましょう!
こんにちは。MNTSQの下村です。 コーポレートエンジニアとして、MNTSQ従業員の生産系向上施策等を実施していたりします。 Twitterもやっている のでフォローしてもらえると嬉しいです! こんなアイコンです 本日は社員からの問い合わせ業務 いわゆる ヘルプデスク業務について効率化するためのツールを自作した 話を書いてみます。 この記事の要約 一人目コーポレートエンジニアとして参画したがヘルプデスク業務が非効率だったので効率化した。 質問に対して特定のemojiを押すと GitHub ProjectsのItemを作成するようにした。 Slackスレッドのコメントと GitHub ProjectsのItemを双方向同期するようにした。 Azure OpenAIも利用して効率化した。 きっかけ 2023年5月からMNTSQの一人目コーポレートエンジニアとして参画しています。 情報システムを色々と整備している真っ最中なのですが、 ヘルプデスク業務のフローが未整備である というものがありました。 (あるあるですね。) 放置するとヘルプデスク業務の 工数 が増加する恐れがあったので対策を打つことにしました。 どんな問題があったか? 様々なSlackチャンネルで質問があり、どこでどんなステータスになっているかわからない。 「あの回答したっけ、、」とかとなり、Slackの過去書き込み等を検索して状況整理していた → ムダ ナレッジがたまらない。 Slackでのみ回答をしているので類似事例等を検索できない → 属人化 ヘルプデスクにどの程度の時間を費やしているか等の傾向分析ができない。 よくある解決方法として ヘルプデスク用チャンネルを作ってSlackワークフロー等で質問を受け付ける というのがセオリーかと思います。 私自身もそうしようとしたのですが、下記のような悩みを抱えることになるなと思い何か別の方法を考えることにしました。 社員の生産性向上を行うのがコーポレートエンジニアなのに社員にルールを増やすことになる。 ワークフローで質問してねーというルールを社員に周知徹底する必要がある。 ワークフローを知らない方や、急ぎの質問などはワークフローが使われないケース等がありそう。 ワークフローを知らない方にはワークフローのことを説明して、急ぎの質問だったりすると場合によってはコーポレートエンジニアが代筆するとかありそう。 ワークフローで質問を受け付けるのは良いが、解決済か否かのステータス管理は結局難しいまま。 別の方法を探ってみる まず、どんな機能が欲しいのかを整理してみました。 初手として下記機能を満たす SaaS を探してみることにしました。 問い合わせ内容をチケット管理したい。 MNTSQは GitHub を全従業員が利用しているので GitHub Issue or GitHub Projectsと連携できると良し。 チケット化することでナレッジ化もしたい。 従業員側はSlackにコメントするだけにしたい。 従業員にヘルプデスク用 SaaS にログインさせて、質問をしてーみたいな面倒なことはさせたくない。 どのSlackチャンネルからでもヘルプデスクメンバー側に負担なくチケット化したい。 Slackの投稿内容がチケットに同期して欲しい。 ヘルプデスク業務をアウトソースしたいので、アウトソースしやすいシンプルな仕組みが欲しい。 質問傾向等や対応時間などを解析できるようにしたい。 せっかくなのでAIを活用して業務を簡略化したい。 できるだけコストも抑えたい。 そんなわけで SaaS を探してみる。 探してみたのですがズバピタなものは見つかりませんでした。。。。 Halp がイメージに近い。 ただ、MNTSQではJiraを使ってないので断念。 THENA も良さそうだが、 GitHub 連携は無い。 issit が良さそう!と思いつつチケット化するだけなので、同期とかは難しそう。 じゃあどうする? 無いなら作ろう! ということで1週間ほどかけて突貫で作ることにしました。 こんな感じ のを作りました。 構成図 こういうフローになりました。 質問者 質問者は任意のパブリックチャンネルで質問をして、そのスレッドで状況の説明をする(だけ) ヘルプデスクメンバー 作成された GitHub ProjectsのItemに回答をコメントするだけ。(Slackスレッドに自動同期されるのでSlackを開く必要は無し) ヘルプデスク宛の質問を GitHub ProjectsのItemのチケット化するのはできるだけ、AIに任せて自動化する。 それぞれの機能についての解説1: ヘルプデスク宛の質問にemojiをつける 10分おきに下記処理を行うCloud Runを用意してヘルプデスク宛の質問だった場合は helpdesk といったemojiを該当メッセージに付けるようにしました。 (なぜemojiを付けるのかは後述します) 質問者が記載した質問内容を search.messages API を利用して検索。 検索条件はヘルプデスクメンバーが含まれるグループメンション or ヘルプデスクメンバーのメンション。 検索した内容をAzure OpenAIの API を利用してヘルプデスク宛の質問か否かをAIに判断させる。 下記のようなプロンプトで判断させてます。 投稿された内容はITヘルプデスクチームの問い合わせだと思いますか?なお、回答については「Yes」もしくは「No」という回答のみで答えてください。 このようにすることで後続の判定処理に利用しやすくしています。 AIがヘルプデスク宛だと判断した場合、 reactions.add API を利用して helpdesk といったemojiを該当メッセージに付ける。 次回実行時用に、Firestoreに「どこまで検索済か」を記録するようにして終了。(同じメッセージに対して二重でemojiを付けないようようにしています) ヘルプデスク宛の質問にemojiをつける それぞれの機能についての解説2: GitHub ProjectsのItemを作成 先述した、emojiを利用して下記のような処理を行います。 Slack公式ツールである リアク字チャンネラー を使い、ヘルプデスク用Slackチャンネルにメッセージを転送する。 emojiがついたことをトリガーにヘルプデスク用Slackチャンネルにチケット化したい内容が通知されるので Event Subscriptions を利用して、Cloud Runに GitHub Projectsの API を利用してItemを作成する処理を命令する。 このようにリアク字チャンネラーの機能を利用することで下記のようなメリットがあります。 各SlackチャンネルにEvent Subscriptions用の bot を招待しなくても良い。 ヘルプデスク用チャンネルに転送するといった機能を自前で実装しなくて良くなる。 GitHub API を利用して、該当メッセージに関する GitHub ProjectsのItemを作成する。 この際、下記のようなことを行う。 Azure OpenAIの API を利用して、質問内容を要約した内容でitemの件名を設定する。(itemを見やすくするため) プロンプトは ITヘルプデスクのチケットとして起票したいです。テキストを50文字以内で要約してください。 といった感じにしています。 Slackの質問内容をスレッドの内容も含めて、 GitHub ProjectsのItemに記録する。 後述の同期処理用にFirestoreにSlackスレッドの情報(タイムスタンプなど)と GitHub ProjectsのItem ID等を記録しておく。 GitHub ProjectsのItemを作成 こんな感じにチケットを作成します それぞれの機能についての解説3: Slackと GitHub ProjectsのItemを同期 10分おきに下記処理を行うCloud Runを用意して、Slackと GitHub ProjectsのItemを同期します。 こうすることによりヘルプデスクメンバーは GitHub Projectsだけを見る or コメントすれば良いことになります。 GitHub API 及び Slack API を利用して、ItemやSlackスレッドの内容に新しい投稿が無いかをチェックする。 新しい投稿があれば、その内容を同期して投稿する。 例: Slackスレッドに投稿があれば、 GitHub ProjectsのItemにスレッドの内容を自動記載。 例: ヘルプデスクメンバーが GitHub ProjectsのItemにスレッドの内容を記載した場合Slackスレッドに自動投稿。 次回実行時用に、Firestoreに「どこまで同期済か」を記録するようにして終了。(同じメッセージに対して二重で投稿しないようにしています) Slackと GitHub ProjectsのItemを同期 それぞれの機能についての解説4: 解析用のデータを蓄積 後ほど、ヘルプデスク対応について解析するために上記Cloud Runが実行されるたびに、下記内容を Google Sheets API を利用して スプレッドシート に記録、 スプレッドシート の内容をもとにLooker Studioでレポート化しています。 質問者メールアドレス 質問者所属部署 対応開始、終了日時 全体所要時間 解析用のデータを蓄積 レポート例 その他、工夫したポイント 対応開始と終了を bot から通知させるようにしてます。 ちょっとしたことなのですが人対人のやりとりだと、「対応します!」→「お願いします!」みたいなやり取りが発生しがちなのでその時間も節約。 対応開始メッセージ ヘルプデスクは即対応を求められがち、ヘルプデスクメンバーも即対応をしがち。ただ、優先度に応じた対応をしたほうがいいのでその旨を毎回周知する。 対応開始メッセージ 対応終了メッセージ ヘルプデスク側は対応終了したつもりだったが、当人は「そうじゃない(まだ終わってない)」みたいなケースもあるので、その旨も告知。 対応終了メッセージ リリース後の結果 リリース後は下記のように問題が解決しました。 様々なSlackチャンネルで質問があり、どこでどんなステータスになっているかわからない。 → ストレスなくチケット化できる環境が整ったので、 GitHub Projectsだけを見ればステータス管理できるようになった。 ナレッジがたまらない。 → すべて GitHub ProjectsのItemに蓄積されているので過去ナレッジが集約された。 今後は溜まったナレッジをAIに学習させて自動返答なども検討したいと考えているが、それの礎になるものができた。 ヘルプデスクにどの程度の時間を費やしているか等の傾向分析ができない。 → Looker Studioにより 見える化 できるようになったので傾向分析が可能に。 今後、どの部署に対してどのように手厚くすべきかなどが検討可能になりました。 また、運用が簡略化されたことで 対応 工数 が削減 できたのもポイントです。 「あれ?あの回答したっけ??」みたいにSlackを検索する時間も無くなりました。 また、未回答の総量等も見れるようになったので、「ある程度連続して時間を確保して一気に片付けよう」 みたいな計画も立てられるようになりました。 その他嬉しいこと 開発者から褒められた。 本業の方から褒められるのは純粋に嬉しいですね☺️ ほめられた 横展開を希望された。 横展開できるように想定していたのでこう言われるのも嬉しいですね☺️ 横展開 低コストでできた。 月額3,000円程度の維持費。 Halpだと最低でも一人あたり月額$150- なのでだいぶ低コストで出来た☺️ 自作ツールなので汎用性がある。 今後、仮にNotionやasanaにタスク管理を移行する!となっても同じ仕組みで流用できそう。 後悔することも 自作なので 自分の理想形 になるのは良いのですが、自作なので自社メンテが必要になるため、負債化してしまったなと思ってます。 今後もヘルプデスク管理ツールの動向を見守りながら、良さそうなツールがあれば即リプレイスしたいなとは思っています。 wrangle.io は今は気になり(このシステム構築後にこのツイートを気づいた) 2024年1月追記 GitHub Issueにチケットを貯めるのではなく、Notionでヘルプデスク用のDatabaseを作成してそちらに貯めるように変更しました。 チケット作成ごとにNotion Pageを作成して、Slackスレッドの内容はNotion Page上のコメントに同期するようにしました。 この形の方が便利でした。 (問い合わせ者への返信はNotion Pageのコメントに投稿、そうじゃない内部向けのメモはPage上に記載するようにすることで情報整理できる) ニーズがありそうなら、どこかのタイミングで新たに投稿したいなと思っています。 最後に 本記事ではヘルプデスク業務を自作ツールで効率化した話を記載しました。 同じようにお困りの方の参考となれば嬉しいです。 MNTSQでは すべての合意をフェアにする というミッションを掲げています。 sg.wantedly.com そんなミッションに共感してくれる方、ぜひMNTSQで一緒に働きましょう!
はじめに  こんにちは、MNTSQ( モンテスキュー )の アルゴリズム エンジニアの清水です。本記事では事前学習済み 言語モデル の一つであるLUKEを用いた固有表現抽出の実装方法について紹介します。 LUKEとは  LUKEは、 LUKE: Deep Contextualized Entity Representations with Entity-aware Self-attention において提案された 言語モデル です。LUKEは、単語とエンティティの文脈付きベクトルを出力する知識拡張型(knowledge-enhanced)の 言語モデル であることが大きな特徴です。単語とエンティティの双方を独立した トーク ンとして扱い、(他のBERT系モデルと同様に)マスクされた単語を予測する訓練を行うと同時に、マスクされたエンティティを予測する訓練を行うことで、エンティティを考慮した文脈表現を獲得できます。また、固有表現抽出などのエンティティの知識を使うことが重要なタスクにおいて、当時のSoTAを達成しています。  詳しくは論文著者の スライド で分かりやすく解説されているので、ぜひご覧ください。 TransformersのLukeForEntitySpanClassificationによる固有表現抽出  LUKEは Transformers から簡単に利用することができます。LUKEではエンティティ表現を使って各種 NLP タスクを解くことが可能であり、Transformersでは LukeForEntitySpanClassification を用いることで固有表現抽出を行うことができます。ただし、少し独特な実装が必要であり、公式のドキュメントにも多くの記載がないため、本記事で実装方法を紹介します。また、 GitHub に 実装コード を公開しているので、そちらも合わせてご参照ください。 モデルへ入力  (詳しくは論文著者のスライドをご覧いただければと思いますが)LUKEの事前学習タスクは以下の通り、テキストとは別に入力されるエンティティを復元するタスクになっています。  (本記事では図中の”Words”を「単語」、”Entities”を「エンティティ」と呼称します。)  TransformersのドキュメントのLUKEのページを参照すると、通常のBERTに入力する各単語の特徴量( input_ids 、 attention_mask など)と同じように、エンティティの特徴量を入力する必要があることがわかります。 LukeForEntitySpanClassificationの入力(Transformersより)  これらの特徴量は、 entity_spans という特徴量(後述)を LukeTokenizer (もしくはその多言語版である MLukeTokenizer )に入力することによって自動で作成されるため、実装上は特に意識する必要がありません。ただ entity_position_ids の計算方法を把握しておくと、モデル理解の助けになるでしょう。 entity_position_ids はエンティティがどの単語に対応するかを示すことで、テキスト中のエンティティの位置を表現します。エンティティが複数の単語に対応する場合、下記スライドのようにポジションエンベディングからの出力を平均し、モデル本体に入力します。 入力の作成方法  以下のようにTransformersの LukeTokenizer (もしくは MLukeTokenizer )に entity_spans を与えることで、前述したモデルに必要な入力を自動で作成することができます。引数の task には "entity_classification" 、 "entity_pair_classification" 、 "entity_span_classification" のいずれかを指定する必要があり、本記事では固有表現抽出を行うことができる"entity_span_classification"を指定します。 tokenizer = MLukeTokenizer.from_pretrained( "studio-ousia/luke-japanese-base-lite" , task= "entity_spans_classification" ) text: str = "MNTSQ株式会社は全ての合意をフェアにします。" # entity_spansの具体的な作成方法については後述 entity_spans: list [ tuple [ int , int ]] = [( 0 , 5 ), ( 0 , 9 ), ( 0 , 10 ), ( 0 , 12 )...] encoding = tokenizer(text, entity_spans=entity_spans, return_tensors= "pt" )  この entity_spans が、通常の固有表現抽出から連想される形式と異なるため注意が必要です。テキストを任意の方法( MeCab ・Sudachiなどによる 分かち書き )で分割し、連続する トーク ンの組み合わせを entity_spans として入力します。より正確には、テキスト中のエンティティの始点位置・終点位置を示す二つの整数のタプルのリストを渡します。入力イメージとしては以下の通りです。 text = "MNTSQ株式会社は全ての合意をフェアにします。" # 任意の方法で分割し、連続するトークンの組み合わせを`entity_spans`として与える。 # MNTSQ/株式会社/は/全て/の/合意/を/フェア/に/し/ます/。(Sudachiによる分かち書き) entity_spans = [ ( 0 , 5 ), # "MNTSQ", ( 0 , 9 ), # "MNTSQ株式会社", ( 0 , 10 ), # "MNTSQ株式会社は", ( 0 , 12 ), # "MNTSQ株式会社は全て", ( 5 , 9 ), # "株式会社", ( 5 , 10 ), # "株式会社は", # 省略 ( 20 , 21 ), # "し", ( 20 , 23 ), # "します", ( 20 , 24 ), # "します。", ( 21 , 23 ), # "ます", ( 21 , 24 ), # "ます。", ( 23 , 24 ), # "。" ]  この entity_spans を使用して、 entity_position_ids などのモデルに必要な特徴量が LukeTokenizer によって作成されます。また、正解ラベルはこの entity_spans 内のスパン一つ一つに対して付与されます。上記の例の場合は上から2つ目の"MNTSQ株式会社"のスパンに対してのみ"ORG"などのラベルが付与されることが期待されます *1 。また、( LukeTokenizer によって作成される) entity_ids には一律して"[MASK]"という特殊 トーク ンのIDが割り振られます。 *2 *3 。 その他実装上の注意  LUKEと他のBERT系のモデル間で最も異なる点は entity_spans を作成して トーク ナイザに入力する必要がある点です。具体的な実装は 実装コード をご覧いただければと思いますが、本記事でも実装上注意する必要がある点をご紹介しておきます。 一つのentity_spansにおける最大長  全ての連続する トーク ンの組み合わせを entity_spans として作成すると、スパンの数が膨大になりますし、また明らかにエンティティではないスパン(例えばテキストの最初から最後までのスパン)も作成されることになります。そのため、スパンの最大長を決めて、一つ一つのスパンがその最大長を超えないように entity_spans を作成する必要があります。また、この最大長は トーク ナイザによって分割される単語(上記論文著者のスライドの18ページの”Words”に当たる)の数を基準に設定する必要があります。つまり、一つのエンティティに定めた個数以上の単語が含まれないように制限します。また、その最大長を LukeTokenizer に max_mention_length という引数として与える必要があります。詳細は実装コードの こちらの部分 を参照ください。 各データに対するentity_spans内のスパンの個数  また、メ モリー エラーを防ぐために、一つのデータ(一つのテキスト)に対する entity_spans 内のスパンの個数も制限する必要があります。溢れてしまったスパンは、テキストを複製し別のデータとしてモデルに入力します。この一つのデータに対するスパンの最大個数は LukeTokenizer に max_entity_length という引数として与える必要があります。詳細は実装コードの こちらの部分 を参照ください。  また、このように別データとして入力された同一テキストに対する entity_spans は、推論時に再び集約する必要があります。詳細は実装コードの こちらの部分 を参照ください。 推論時の処理  推論時にも独特の処理が必要です。具体的には、予測確率の高い順に、テキストの各文字に対して予測結果を反映していく処理が必要になります。詳細は実装コードの こちらの部分 を参照ください。 精度評価  ストックマーク株式会社が公開している 日本語の固有表現抽出データセット で評価 *4 したところ、Accuracyが0.96、F1スコアが0.89となりました。詳しい結果は WandBのログ をご覧ください。 終わりに  LUKEによる固有表現抽出は通常の 言語モデル による固有表現抽出とかなり性質が異なります。私は通常の固有表現抽出の先入観に引っ張られ、当初は誤った実装をしてしまいました。LUKEにご興味があり、実際に使ってみようと考えている方の参考になれば幸いです。 MNTSQ, Ltd.では一緒に働く仲間を募集しています  MNTSQでは 自然言語処理 やLLMにご興味のあるエンジニアを募集しています。是非、下記リンクからカジュアル面談をお申し込みください! この記事を書いた人 清水健 吾 自然言語処理 エンジニア TransformersのRelease Notesを眺めるのが趣味 *1 : 一般的な固有表現抽出の"B-ORG"、"I-ORG"のようなBIO形式でないことに注意してください。一方で、推論時には seqeval を使ってスコアを計算するためにBIOタグに変換しています。 *2 : 事前訓練時に使った"[MASK]" トーク ンを入力することで、入力テキスト中からエンティティに関する情報を集約した表現が得られます。詳しくは こちら 。 *3 : entity_idsは全て"[MASK]" トーク ンであるため、エンティティのエンベディング層が不要です。そのためエンティティのエンべディング層を持たない liteモデル を使用します。 *4 : 全データのうち20%をテストデータとして使用しました。
こんにちは。「リーダブルコード」を先月読破して、感銘を受けた弁護士の人です。 なにに感銘を受けたかというと、「エンジニアが 高級言語 を効率的にコーディングするための工夫」は、契約という言語をコーディングするために援用できることがとても多いということです。 例えば、リーダブルコードは 「関数には空虚な名前(tmpとかretvalとか)でなく、エンティティの実体に即した名前をつけよう!」 と提案しています。 これめっちゃわかります!!!なぜなら、契約言語では当事者というクラスの表現のために「甲」 「乙」 という定義を未だに使います。そして、甲と乙を逆に書いてしまったままReviewを通過することが実際によくあります。 オライリー さんには激怒されるでしょう。 しかし、よく考えると 高級言語 と契約言語が似ているのは当然だと思うようになりました。それは、どちらも 「一定のインプットを入れると、必ず一定のアウトプットが返ってくるように設計された独自の言語体系」 だからです。 契約言語とは 「一定の紛争⇒一定の判決が出るように構造化されたコード」 です。処理するのがコンピューターではなく裁判官であるというのが違いでしょう。 調べれば調べるほど、プログラミングの世界でのこれまでの工夫は、契約言語の世界の未来の姿だと思うようになりました。 ソフトウェア開発のプロセスになぞらえて、少し書いてみます。  ビジネス モデリング   モデリング の重要性 ソフトウェア開発においては、まず実際の業務を モデリング しますよね。まず業務要件を適切に分解することが重要で、クラスを定義したりER図を作ったりするのはその後です。ここを間違うと、プログラムとして内的整合性が保たれていても、実際には意味のない / 役に立たないものになってしまいます。 契約でも、最もセンスが問われるのがここです!実際の取引を、契約という独自の言語体系に モデリング していきます。いくら契約のフォーマットが美しくても、実際の取引を反映していなければ、裁判で役に立たずにむしろ有害です。  ノーコード/ ローコードの流れ そのため、プログラミングでも契約でも「コードを求めているユーザー」と「コーディングできる職人」とのコミュニケーションがとても重要です。 しかし、これが超難しい。契約の世界では、法務部と事業部とのやりとりにめちゃめちゃ時間がかかるのです。日本全体で契約のために消費している時間は、10億時間とも言われます。 そうすると「全部を職人さんがコーディングするのではなく、コードを求めている人でもコーディングできるようにしよう!」という流れが産まれます。いわゆる、ノーコード/ ローコードですね。 「契約のノーコード」は、LegalTechでもアツい分野の一つです。ChatGPTに契約を書かせてみたらどうなるのか…という tweet を当社創業者がしましたが、もし誰もがフェアな契約を瞬時にドラフトできれば、世界中のビジネスはものすごく加速するはずです。 ただ、「CopilotをProduction Codeに使うのはまだ怖い」というくらいの感覚で、 NLP のなかでも生成モデルの活用は道半ばという感じです。契約の世界でも、生成モデルよりも検索や推薦 アルゴリズム といったもう少し地に足のついた技術活用が進んでいます(後述)。  コーディング   DRY原則 プログラミングでは、汎用化できる部分はライブラリ化され、同じコードは何度も書かないようにすべきという考え方が当然のように受け入れられています( DRY原則 )。 さらに、社会全体でも共 通化 できるレイヤーは、 デファクト・スタンダード となるようなプラットフォームから API が提供されています。当社も、 SaaS プロダクトを提供するにあたって AWS を利用したり、随所でライブラリを利用することで、エンジニアがプロダクトのために必要な部分にフォーカスできるという便益を享受できています。 弁護士から見ると、これって本当に羨ましいことです……! なぜなら、 契約はその99.9%が過去のものの繰り返しであるにもかかわらず、毎回ゼロからコーディングしているからです 。 契約言語には、未だに デファクト・スタンダード が存在しません!年間5億件も契約されてるにもかかわらずです!  クリエイティブコモンズ やJ-KISSなど標準化が進んだ領域は、契約全体の0.1%未満です。 そのため、契約をしたい世のビジネスマンは、ライブラリ等を一切使わない フルスクラッチ 開発を常に強いられて疲弊しています。ちなみに IDE のサポートもないので デバッグ 作業も重いです。 これは非効率だというのもありますが、めちゃめちゃつまらないことです。法務の人たちはとってもツラいのです。   デファクト・スタンダード と 自然言語処理 技術 プログラミングの世界では、長い歴史のなかで デファクト・スタンダード を積み上げてきました。 契約言語にも、今ようやくその機運が訪れています。その契機となったのが、実は 自然言語処理 技術の登場です。契約言語は比較的明確な構造を持っているため、 自然言語処理 技術との相性がとてもよかったのです。 私たちのようなLegalTechが広く使われていくと、社会全体で「どのような契約条件が標準なのか」がついに可視化されます。これまで弁護士の職人芸頼りであったものが、スタンダードを 定量 分析できるようになるかもしれません。 また、日本最高の職人=トップ弁護士によるコードを オープンソース 化する取り組みも始まっています。LegalTechは、フェアな契約言語への API を提供するという社会的役割を果たそうとしています。  Review ソフトウェア開発では、 GitHub をはじめとした バージョン管理システム を中心としたプラットフォームが当然に使われています。これがあまりに便利なので、当社では経営上の意思決定すら GitHub でPull RequestをApproveすることで実施しています。 かたや契約言語は、コードがベタ打ちされたWordファイルをメールに添付してやりとりすることで更新しています。交渉過程で、複数のビジネスマンが異なる修正をしたにもかかわらず、conflictに気づかぬままハンコを押してしまった…ということも起こります。 そもそも裁判では、取引先と最後に合意された契約コードだけでなく、そこに至るまでの更新経緯も参照されます。しかし、誰がなぜどのようにコードを更新したのかという情報は、たいていメールが離散してわからなくなっています。 これを解決するために、昨今のLegalTechは「契約のバージョン管理」に乗り出しています。一つのプラットフォームで契約言語のReview履歴が見れるように、これからようやくなっていくかもしれません。  管理・更新 プログラムは「動いてからが本番」であり、リリース後に改善活動が アジャイル に繰り返されます。ここが SaaS の面白さでもあります。 契約言語であっても、本来は同じです。そもそも契約言語は、裁判官というリソースが超貴重でテストが不可能であるため、ぶっちゃけ間違いが多いです。 また、「契約期間」というEOLもあるのでメンテナンスも必要になります。 他方で、契約言語は「現在有効なコードがなにか」ということすら管理することができていません(!)。これはすごい話で、なぜかというと、最終版の ソースコード が紙で管理されているからです。 そのため、LegalTechベンダーは、 OCR を提供するだけで喜ばれることもあります。さらに、 自然言語処理 技術によって契約期間を抽出し、メンテナンスが必要なタイミングでアラートを出すことができるだけでも社会にWOWを与えられます。  終わりに こうして見ていくだけでも、契約言語はプログラミングの歴史から大いに学ぶべきであると痛感します。 さらに悪いことに、 高級言語 と契約言語との大きな違いがあります。それは 「契約言語は、全国民が完全理解できることがなぜか前提になっている」 ことです。ビジネスの世界では、契約言語を通じてしか、他者と関わることができません。 例えば、外部サービスを利用するときには、 利用規約 に同意する必要があります。あれって、皆さんは本当に読めていますでしょうか? ぶっちゃけ、現状のものは弁護士である私ですら読む気にならないです。そのくせ「私たちは一切の損害賠償責任を負いません」だとか、明らかにアンフェアな条項がこっそり含まれていたりします。 LegalTechとは、契約言語をモダナイズし、誰でも他社とフェアな協力関係を築けるようなツールに進化させるものだと考えています。契約言語のノーコードと、充実したライブラリ推薦アルゴと、 IDE と、 GitHub を私はこの世界に送り出したい!(これが言いたかった) 今まさに社会が大きく変化しようとしているところですので、もし興味を持ったエンジニアの皆さまがいれば、是非私とカジュアル面談をさせてください! カジュアル面談申込フォーム | MNTSQ株式会社 ※右上にも採用ページへのリンクがあります この記事を書いた人 板谷  隆平 MNTSQ( モンテスキュー )社のCEO/ファウンダー。長島・大野・常松法律事務所所属の現役弁護士です。