メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

監視にかかるコストを見直し半額にした話

SRE の田中 @kenzo0107 です。

メドピアグループでは主に AWS をプラットフォームとし、監視は Datadog で実施しています。

監視対象や課金対象のサービスの増加で徐々にコストが増加していたので、 利用状況を分析し、削減できる項目を調査しまとめました。

Datadog 子組織・サービス毎の利用料金の確認の仕方

親組織にある Usage & Cost > Individual Organizations *1 の Cost タブで各 org 毎の利用料金を確認できます。*2

事前に利用する量をコミット

www.datadoghq.com

事前に利用する量をコミットすることで 2~3割程、コスト削減できます。

ANNUALLY (年間でコミットを決定) の方がコスト削減率は高いですが、 MONTH-TO-MONTH (月毎にコミットを決定) を採用しています。

月毎に監視対象リソースの増減が頻繁にある為です。*3

Datadog APM のサンプルレートを下げる

Datadog APM の Ingested Span*4量は課金対象です。

docs.datadoghq.com

上記参考に Ingestion Control *5で Monthly Usage で使用量を確認し、 100% を超える分を冗長なデータとして取り込まない様にすることでコスト削減します。

サンプルレートを下げるコンテナの設定例

  • DD_APM_MAX_TPS = 1 とすることで 10 分の 1 で全体の 10% を取り込むように抑える
  • DD_APM_ENABLE_RARE_SAMPLER = true とし、稀に発生するサンプルを取りこぼさない様にする

Fargate タスク定義 JSON では以下の様になります。

{
    "name": "datadog",
    "image": "datadog/agent:latest",
    ...
    "environment": [
        {
            "name": "DD_APM_ENABLED",
            "value": "true"
        },
        {
            "name": "DD_APM_MAX_TPS",
            "value": "1"
        },
        {
            "name": "DD_APM_ENABLE_RARE_SAMPLER",
            "value": "true"
        }
    ],
    ...
}

Datadog APM EC2 vs Fargate

  • EC2 1 ホスト: 31 ドル / 月
  • Fargate 1 タスク: 2 ドル/月

既知の方は多いかと思いますが、念の為。
Fargate で Datadog APM を有効化した方が格段に安いです。

利用しない監視対象リソースのメトリクスを収集しない

  • 利用していないリージョンのメトリクス取得を無効化*6
  • 利用していない AWS サービスのメトリクス取得を無効化*7
  • EC2 のタグフィルターで datadog:true のタグを所持する EC2 のみ監視対象とする*8

Terraform で実装すると以下の通りです。

locals {
  enable_integration_list = [
    "application_elb",
    "elb",
  ]
}

resource "datadog_integration_aws" "this" {
  ...

  # 監視を有効化したいサービスのみ有効化する
  account_specific_namespace_rules = { for c in data.datadog_integration_aws_namespace_rules.rules.namespace_rules : c => contains(local.enable_integration_list, c) }

  # 除外リージョン
  excluded_regions = [
    "af-south-1",
    "ap-east-1",
    "ap-northeast-2",
    "ap-northeast-3",
    "ap-south-1",
    "ap-south-2",
    "ap-southeast-1",
    "ap-southeast-2",
    "ap-southeast-3",
    "ap-southeast-4",
    "ca-central-1",
    "ca-west-1",
    "eu-central-1",
    "eu-central-2",
    "eu-north-1",
    "eu-south-1",
    "eu-south-2",
    "eu-west-1",
    "eu-west-2",
    "eu-west-3",
    "il-central-1",
    "me-central-1",
    "me-south-1",
    "sa-east-1",
    "us-east-1",
    "us-east-2",
    "us-west-1",
    "us-west-2",
  ]

  # NOTE: メトリクスを収集する EC2 タグのフィルター設定
  #       datadog-agent インストール済みの EC2 のみ監視する為、以下タグをフィルターとする
  # see: https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/integration_aws#optional
  filter_tags = ["datadog:true"]
}

data "datadog_integration_aws_namespace_rules" "rules" {}

上記対応により、 Datadog AWS Integration による AWS GetMetricData API の発行数が下がり、大幅にコスト削減できました。

AWS CloudWatch メトリクスで監視できるリソースは CloudWatch Alarm で監視

こちらも AWS GetMetricData API 発行コストを削減できます。

アラートの遅延*9を回避でき、副次的な効果がありました。

CloudWatch Alarm のリソースを監視する Terraform Module を作成し、監視の移行コストを下げました。

EC2 監視を Datadog Agent から CloudWatch Agent へ移行

Datadog の EC2 監視に掛かる Datadog Infrastructure Host のコストを削減したい意図です。

Datadog で監視していたメトリクスを CloudWatch Agent で取得し CloudWatch メトリクスに保存*10し、 CloudWatch Alarm でアラート発行する様にします。

CloudWatchAgent の設定ファイル例

{
    "agent": {
        "metrics_collection_interval": 60,
        "run_as_user": "root"
    },
    "metrics": {
        "aggregation_dimensions": [
            [
                "InstanceId"
            ]
        ],
        "append_dimensions": {
            "AutoScalingGroupName": "${aws:AutoScalingGroupName}",
            "ImageId": "${aws:ImageId}",
            "InstanceId": "${aws:InstanceId}",
            "InstanceType": "${aws:InstanceType}"
        },
        "metrics_collected": {
            "collectd": {
                "metrics_aggregation_interval": 60
            },
            "disk": {
                "measurement": [
                    "used_percent",
                    "inodes_free",
                    "inodes_total"
                ],
                "metrics_collection_interval": 60,
                "resources": ["*"]
            },
            "mem": {
                "measurement": ["mem_used_percent"],
                "metrics_collection_interval": 60
            },
            "statsd": {
                "metrics_aggregation_interval": 60,
                "metrics_collection_interval": 60,
                "service_address": ":8125"
            },
            "procstat": [
                {
                    "exe": "nginx",
                    "measurement": ["pid_count"],
                    "metrics_aggregation_interval": 60
                },
                {
                    "exe": "unicorn",
                    "measurement": ["pid_count"],
                    "metrics_aggregation_interval": 60
                }
            ]
        }
    }
}

以下カスタムメトリクスとして CloudWatch メトリクスに保存します。

  • ディスク使用率
  • ディスク i-node フリー
  • ディスク i-node トータル
  • メモリ使用率
  • procstat でプロセス監視

CPU 使用率はデフォルトで取得できるので設定していません。 inodes_free, inodes_total からディスク i-node 使用率を計算しています。

docs.aws.amazon.com

CloudWatch Agent に移行できないパターン

Datadog APM を利用している EC2 ホストは Datadog Agent を起動させておく必要があり、 CloudWatch Agent への移行ができません。*11

Datadog APM を有効化している EC2 ホストは CloudWatch Agent へ移行せず、移行できるものだけ移行することとしました。
監視設定が Datadog と AWS とハイブリッドになりますが、コスト削減メリットが大きく、移行を進めました。

通知内容自体も Datadog と CloudWatch Alarm で若干異なりますが、欲しい情報は取れていたので特段問題ありませんでした。*12

上記対応により、逐次 Datadog Infrastructure Host を削減できました。

過去 6 ヶ月の Datadog Infrastructure Host 利用量

Datadog に残したもの

代用できるサードパーティや自作する見通しが直ちに立たなかった為、元々使用していたものを利用することとしました。

まとめ

以下の対応により監視にかかるコストが年間 1 千万円ほど削減できました。

  • 事前に利用料をコミット
  • Datadog APM サンプルレートの適正化
  • 利用しない監視対象リソースのメトリクスを収集しない
  • AWS CloudWatch メトリクスで監視できるリソースは CloudWatch Alarm で監視
  • EC2 監視を Datadog Agent から CloudWatch Agent へ移行

全体的に監視を AWS に寄せたことが大きなコスト減の要因ですが、用法用量を適切に管理すること、サービスの知見を得ることで未然に意図しないコスト増を防ぐ必要があると改めて学びがありました。
怠惰に利用しない様、心に刻みます。

以上
参考になれば幸いです。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

*1:Datadog ログイン後、親組織で admin 権限を持つアカウントのみアクセス可能です。

*2:プロジェクト毎の利用料金をわかりやすくすべく、 子組織に分けています。 メドピアグループでは terraform で Datadog リソースを管理しておりますが、terraform provider も子組織もサポートしています

*3:キャンペーンが多い月はサーバ数が増加したり、サービスがクローズした場合の影響度を下げる等、フレキシブルに変更可能にしたい意図です。毎月更新している訳ではなく、必要が生じた際に担当のカスタマーサポートに連絡し、変更いただいています。

*4:Ingested Span は、スタック内の個々のサービスに対する個別のリクエストです。Datadog に取り込まれたスパンのギガバイトの総数に基づき月末に課金されます。

Ingested Span = 上記図に見られる APM で取得 (Ingested) した各ブロック (Span)

参考: https://docs.datadoghq.com/ja/account_management/billing/apm_distributed_tracing/

*5:Datadog にログイン後に閲覧可能なリンクです。

*6:基本 ap-northeast-1, us-east-1 を利用しており、それ以外を除外しました。

Datadog AWS Integration の設定

*7:Datadog で利用したいメトリクス以外は無効化しました。

*8:タグフィルターを設定しないとデフォルトで全ての EC2 が監視対象となり、意図しないコスト増が発生します。

*9:docs.datadoghq.com

*10:CloudWatch のカスタムメトリクスを使用しましたが、いずれも無料枠で収まる量となりました。

*11:Datadog Agent, CloudWatch Agent 双方を起動させることは可能ですが、重複させる必要性はなく、Datadog Infrastructure Host の料金を下げる意図に反するので移行しませんでした。

*12:CloudWatch Alarm から Chatbot へポストし、 Slack 通知させています。

初めてのMariaDBバージョンアップのメンテナンスで大変だったこと、工夫したこと

はじめに

 2023年4月に新卒で入社したバックエンドエンジニアの冨家です。現在は、全国の医師が経験やナレッジを 「集合知」として共有し合う医師専用コミュニティサイト「MedPeer」の開発を行っています。
 「MedPeer」ではAmazon RDSのMariaDBを一部使用しています。最近まで10.6.11バージョンを使用しており2024年3月にRDS 標準サポート終了を迎えるので、私が主にバージョンアップ作業を担当することになりました。しかし、私自身初めてのデータベースバージョンアップ作業だったため、どのような点に気をつけるべきかわからず対応に苦労しました。
 そこで今回は、次回バージョンアップする方の参考になるように、実際に行ってみて大変だったことや工夫したところ、次回改善したほうが良いことなどを紹介していきます。

MariaDBバージョンアップに関連する「MedPeer」のAWS構成図

メンテナンス日時の選定の仕方

 まず大変だったことが、メンテナンスの日時を選定することでした。メンテナンスモードに切り替えると全てのユーザーがサービスを使用できなくなります。日時によっては大きな機会損失を出す可能性があるので、実施する日時を見極めることがとても重要でした。
 最終的には、2024年2月26日の23:00から24:00の間にメンテナンスモードに切り替えました。メンテナンスする日時は7つの条件から選定しました。

リスト1 メンテナンス日時を選定したときの7つの条件

1.1. バージョンアップのプロジェクト開始日から1ヶ月以上後であること
1.2. サポート期間が終了する1ヶ月以上前であること
1.3. メンテナンス当日と翌日が休日・祝日ではないこと
1.4. バッチ処理が少ない時間帯であること
1.5. ユーザーのアクセスが低い時間帯であること
1.6. メンテナンス実施日から翌日まで重要な処理がないこと
1.7. ステージング環境で実際にかかったメンテナンス作業時間以上を本番のメンテナンス時間として確保できること

1.1. バージョンアップのプロジェクト開始日から1ヶ月以上後であること

 データベースをバージョンアップするためには他チームとの日程調整やユーザーにメンテナンスを実施する告知が必要です。そのため、余裕を持って行えるようにプロジェクト開始から1ヶ月以上後にしました。

1.2. サポート期間が終了する1ヶ月以上前であること

 もしサポート期間終了直前でバージョンアップを試みて失敗した場合、再チャレンジする時間がありません。そこで、余裕を持って再チャレンジできるようにサポート終了日から1ヶ月以上余裕を持たせるようにしました。

1.3. メンテナンス当日と翌日が休日・祝日ではないこと

 メンテナンス当日や翌日は想定外のことが起きやすく障害が発生しやすいです。もし休日に障害が発生した場合対応が遅れやすくなるので、当日と翌日が休日・祝日ではない日程にしました。

1.4. バッチ処理が少ない時間帯であること

 バッチ処理が多いと考慮することが多くなり、想定外のことが発生しやすくなります。
 現在の 「MedPeer」では、22時から24時の間で1時間あたり3件程度バッチ処理が起動します。しかし、24時以降は1時間あたり数十件のバッチ処理が起動するように設定されているので、日程が変わる前にメンテナンスをすることにしました。

1.5. ユーザーのアクセスが低い時間帯であること 

 もしユーザーがメンテナンス中でアプリを使えなかった場合、サービスに対して不満を感じて、問い合わせ数の増加やアクティブユーザー率の低下に繋がります。そのため、トラフィックが少ない時間帯にメンテナンスを実施して、ユーザーへの影響を最小限にすることが大切です。
 調査した結果、 「MedPeer」では比較的月曜夜のトラフィックが少なめで、22時と23時では数万のアクセス数差があることが判明したので、選定しました。

1.6. メンテナンス実施日から翌日まで重要な処理がないこと

 深夜にアップデートした場合、利用者が増える翌朝に問題が発覚することが多いです。そのためメンテナンス日から翌日の夕方まで、ユーザー影響の大きなイベントが開催されないように他チームと調整を行いました。開催日時が定まっている「Web講演会」機能を筆頭に、特定日時での処理が必要な機能がメンテナンス前後で実行されないように周知・調整を徹底しました。

1.7. ステージング環境で実際にかかったメンテナンス作業時間以上を本番のメンテナンス時間として確保できること

 本番環境のインスタンスはステージング環境と比較してデータ量が多く、より時間がかかる可能性があります。
 ステージング環境でメンテナンス時間を計測してみた結果、メンテナンスモードに切り替えてから通常モードに戻すまで1時間かかりました。そのため、1時間以上切り替えられる日時を選定しました。

実際に選定した日時にバージョンアップした結果

 大きな問題は発生しませんでした。しかし、「メンテナンス中でアクセスできずデイリーボーナスポイントやログインボーナスポイントが獲得できなかった」という問い合わせが1件入ったので、次メンテナンスする方は、メンテナンス中に締切があるイベントやキャンペーンがある時間帯をできる限り避けるように選定できると良さそうです。

バージョンアップ作業の手順

 次に大変だったことは、バージョンアップの手順を作成することでした。もしプライマリーインスタンスで大きな障害が発生してしまうと、長時間サービスが止まってしまい大きな損失を出してしまいます。そのため、「どうすればプライマリーインスタンスで障害を起こさないようにできるのか」、「もし障害が発生した場合どのようにすれば最小限に抑えることができるのか」という視点を持ってバージョンアップの手順を考える必要がありました。
 リスト2 は、MariaDBをバージョンアップしたときの手順を表しています。手順のポイントは7つあります。

リスト2 MariaDBバージョンアップ手順

2.1 事前準備
2.1.1. バージョンアップに失敗した場合の復旧手順を確認しておく
2.2 メンテナンス作業
2.2.1. 社内に向けて作業開始連絡
2.2.2. リードレプリカのバージョンアップ
  • 対象インスタンスのリードレプリカのバージョンアップ
  • 追加されたデータがDBに反映できていることを確認
2.2.3. サービスメンテナンス開始
  • アプリケーションをメンテナンスモードに切り替え
  • 外部のユーザーがアクセスできないことを確認
2.2.4. プライマリーインスタンスのバージョンアップ
  • 対象のプライマリーインスタンスのスナップショットの取得および結果確認
  • プライマリーインスタンスをバージョンアップ
2.2.5. バージョンアップ後の確認
  • バージョンアップ後にデーターベースの読み書きができることを確認
  • プライマリーインスタンスのバージョンアップ後にRedashからリードレプリカのデータを取得できるかを確認
  • コンテナログ、監視ツールのアラート確認
2.2.6. サービスメンテナンス終了
  • アプリケーションのメンテナンスモードを解除
  • 外部のユーザーがアクセスできることを確認
2.2.7. 社内に向けて作業終了連絡
2.3 メンテナンス後の残作業
2.3.1. 不要になったコードの削除
2.3.2. 1週間程度バージョンアップ後のメトリクス(CPU使用率など)に異常がないか確認

Point1. バージョンアップに失敗した場合の復旧手順を確認しておく

 もしバージョンアップに失敗した場合、慌てず迅速に対処できるように事前に確認するようにしました。

Point2.リードレプリカをバージョンアップしてからプライマリーインスタンスをバージョンアップする

 リードレプリカでバージョンアップして問題が起きないことを確認することで、プライマリーインスタンスの障害発生率を低くするように工夫しました。

Point3. バージョンアップ後にデーターベースの読み書きができることを確認する

 閲覧ログを登録しているテーブルがあるため、そのページを表示し閲覧ログが書き込まえることを確認しました。

Point4. プライマリーインスタンスのバージョンアップ後にRedashからリードレプリカのデータが取得できるかを確認する

 プライマリーインスタンスをバージョンアップするとリードレプリカも置き換わります。プライマリーインスタンスのバージョンアップ後にリードレプリカが正常に動作しているか確認するため、Redashからリードレプリカのデータが取得できることを確認しました。

Point5. コンテナログ、監視ツールのアラート確認する

 バージョンアップ中やバージョンアップ後でアプリケーションにエラーが起きていないことを確認するために行いました。もしメンテナンス関連で問題がないエラー通知が来ていた場合(DBのコネクションエラー等)、その通知がメンテナンス作業のものであることを表明して、バージョンアップに携わっていない人にも問題がないことを伝えるようにしました。

Point6. 1週間程度バージョンアップ後のメトリクス(CPU使用率など)に異常がないか確認する

 バージョンアップ直後は想定外のことでメトリクスが変化しやすく、パフォーマンス悪化に繋がりやすいです。そのため、1週間程度異常がないか確認するようにしました。

2/26から3/4までのMariaDB CPU使用率の推移

Point7. 各手順でかかった時間を計測しておく

 各手順でかかった時間がわかると「次回バージョンアップ作業する時にスケジュールを立てやすくする」、「時間がかかった作業を分析して、次回以降の作業時間を短縮できる」などのメリットがあるので、計測するようにしました。

実際に手順通りバージョンアップした結果

 問題なくバージョンアップすることができました。バージョンアップによる障害が発生せず、予定よりも10分早くメンテナンスを終えてスムーズに行うことができました。

作業が終了したときの社内連絡

最後に

 以上、MariaDBをバージョンアップさせる上で大変だったことや工夫したこと、次回改善した方が良いことなどについて、紹介しました。
 実際にバージョンアップ作業を進めていくと、他社ではどのように行っているのか何度も気になりました。もし親切な方がいらっしゃいましたら貴社の事例を記事にしていただけると大変参考になります。
 また、今回のバージョンアップ方法で改善案がありましたら、コメントいただけると嬉しいです。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

社内版 ChatGPT を構築し、社内の ChatGPT 利用を促進した話

SRE の田中 @kenzo0107 です。

社内版 ChatGPT を構築し、社内の ChatGPT 利用を促進した話です。

社内版 ChatGPT が必要だった理由

以下要望を実現する為です。

  • 秘匿情報をクローズドな環境で OpenAI にポストしたい
  • 社員誰もが最新のモデルやバージョンで高精度、且つ、パフォーマンスの高い ChatGPT を利用したい

構成 - Web 版 社内 ChatGPT

  • Web サービスは AWS に配置
  • ALB を会社毎に分けて Google 認証する *1
  • ECS から Azure API Management 経由で Azure OpenAI Service に問い合わせ
    • API Management は Azure OpenAI Service の監査ログを取得する為に配置している *2
  • Azure 側ではネットワークセキュリティグループで AWS NAT Gateway の EIP を許可し、リクエスト制限する

AWS にサーバを立てている理由

Azure で完結した方が、よりクローズドでセキュアです。

弊社では AWS はセキュリティガードレールが整備されていますが
Azure は未完です。

その為、Azure 側は必要最低限の利用リソースのみにし
AWS 側で基本 Web サービスを配信する構成としました。

chatbot の選定

既に世の中には多くの OSS がありますが、 中でもコンテナに対応しており、スター数が多く、開発が活発だった mckaywrigley/chatbot-ui *3 を採用しました。

chatbot-ui v2 がリリースされていますが、 v1 を利用しています。*4

chatbot-ui v2 採用を見送った理由

  • supabase との連携を前提としている
    • AWS 上にクローズドな環境を構築するようなアーキテクチャを採用できない
  • 利用者が各自 Azure OpenAI Service デプロイ ID を設定・管理する必要がある

上記理由から採用を見送りました。

chatbot-ui v1 の問題点

v1 は各自設定不要で即利用できるメリットがあるものの、以下デメリットがありました。

  • API バージョンを 2023-03-15-preview からアップグレードするとエラーになる
  • Azure OpenAI Service からのレスポンスが途切れる
    • fetch メソッドで API エンドポイントからレスポンスを取得していたことが影響していた

上記はいずれも openai ライブラリを利用することで解決できました。*5

Slack から問い合わせたい!

社内リリースをし Google Analytics で利用者の増加を眺めながら一呼吸したのも束の間、 すぐさま以下の意見をいただきました。

  • VPN に繋いで Web ブラウザ開くのが手間
  • Slack からサクッと問い合わせたい

早速実現に向け動きました。

構成 - Slack 版 社内 ChatGPT

  • Slack Event Subscription を設定し、 Bot へのメンション・Direct Message をトリガーに Lambda を起動し Azure OpenAI Service へ問い合わせる
  • 弊社グループ会社毎の各 Slack Workspace で Slack App を作成*6
  • Slack 毎の Signing Secret や Bot User OAuth Token は Lambda 内で処理分け *7
  • VPC モードの Lambda から Azure OpenAI Service に問い合わせる
    • 出口 IP を NAT Gateway EIP に固定する為

Slack 参加メンバーはグループ会社のメールアカウントを所持しており、Google 認証はしない方針としました。

使用例1: チャンネル上から Slack App にメンションする

Slack Bot をチャンネルに招待し、 Bot へのメンションをトリガーに Azure OpenAI Service に問い合わせます。

スレッド中の Bot へのメンションは Azure OpenAI Service との会話に引き継がれます。*8

使用例2: Slack App に直接 Direct Message を送信する

チャンネル上でメンションする場合と異なり、スレッド上の全てが Azure OpenAI Service との会話となります。

利用上の注意点を社内通知

  • 秘匿情報を扱う際は Slack でなく Web 版を利用していただく
    • Slack はサードパーティであり、秘匿情報を第三者が取得できてしまう可能性がある為
  • Slack 検索時の @office-ai のノイズ除外したい時は -from:@office-ai して、というノウハウの通達*9

総評

Slack 版は、他の方が Azure OpenAI Service にどのように問い合わせることで回答を引き出すかのノウハウを知る機会を作ることができたと思います。

開発に際して、
Azure 関連のセキュリティや OSS の改修等、諸問題に対して、
Azure OpenAI Service を利用することで解決できました。

弊社では GitHub Copilot *10 を採用し生産性を向上していたりと AI による社内全体の業務促進する為に AI を利用するという好循環ができているように改めて感じました。

以上
参考になれば幸いです。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

*1:ALB のリスナールールのデフォルトアクションでのみ Google 認証の指定が可能です。メドピア株式会社メンバーが持つ @medpeer.co.jp のメールアカウントとフィッツプラス株式会社メンバーが持つ @fitsplus.co.jp で Google Workspace が異なる為、 Google Workspace 毎に ALB を用意することとしました。

*2:Azure OpenAI Service 単独では Azure OpenAI Service へのリクエスト・レスポンスのログは取得できない仕様でした。 learn.microsoft.com

*3:Next.js ベースで作成されています

*4:legacy ブランチが v1 相当です

*5:v2 の参照実装で事なきを得ました。

*6:workspace を跨いだ Slack Bot が作れませんでした

*7:Event Subscription で指定したエンドポイントに "?workspace=medpeer" を追加することで判定しました。

*8:Bot にメンションしていないメッセージは Slack Event Subscription をトリガーできない為

*9:用途があるかと思ったので

*10:Copilot Business を導入しています

Terraform コードリーディング会を開催し、エンジニア組織全体でインフラの知識の底上げができた話

SRE の田中 @kenzo0107 です。

Terraform コードリーディング会を実施した結果、 エンジニア組織全体でインフラの知識の底上げができた話です。

何故やることになったか?

弊社では以下のような背景がありました。

  • SRE チームが基本インフラ管理
  • 会社の成長に比例し管理するインフラが増加⤴️

SRE チームの処理能力が頭打ちにとなる未来が予想され、
インフラ管理は以下体制への移行が求められていました。

上記の体制へ移行活動の一環として、
まず 「Terraform を知る」こと、ひいては 「AWS を知る」きっかけを作るべく、 Terraform コードリーディング会を開催することとしました。

勉強会の頻度や基本方針

  • 週 1 回 30 分 × 15~6 回*1
  • 自身の携わるプロジェクトの勉強会に参加*2
  • 構成図を元にコードを読む範囲の構成を定める
    • 以下を見ながら、設定の意味や挙動を確認する
      • AWS 公式ドキュメント
      • Terraform のドキュメント
      • AWS Provider のドキュメント
  • おまけ
    • ディレクトリ構成
      • 弊社デファクトの構成やアンチパターンの共有
    • Actionsで実行してる内容:

参考図書

勉強会の例

以下開催した勉強会の一例です。*3

タイトル 内容
1 リーディング会実施の目的と terraform の概要 ・AWS とは?
・Terraform と Provider の関係性 *4
・バグ?と思った時の調べ先
・Terraformの文法
2 Terraformの使い方とTerraform Cloud ・Terraform インストール方法
・Terraform の各種コマンド
・State, Lock の役割
・Terraform Cloud
・Workspace
・Built-in Functions
演習 Built-in Function の挙動確認
 ・文字列分割, 配列長, 配列結合, 配列のネストの深さを揃える
3 RDS ・RDS とは?
・DB Subnet, Cluster, Cluster Instance
・Cluster Parameter Group と Parameter Group の違い
演習 DB パスワードのような秘匿情報はどの様に渡すか
4 LB ・ALB とは?
・NLB, CLB との違い
・メンテ時のリスナールールの挙動
演習 リスナールールで Host ヘッダーが正しいか制御しているのはどういう意図か?
5 ECS Part1 ・EC2 → ECS on EC2 → Fargate 歴史
・タスク定義で定義するもの
・EKS とは?採用するメリット・デメリット
演習 ECS EC2 と比較し Fargate を採用するメリット・デメリットは?
6 ECS Part2 ・ECS Cluster
・ECS Service
・デプロイ時の挙動 (RollingUpdate, Blue/Green Deployment)
・無限再起動ループ
AWS 勉強会 ECS 編 で補足
7 ECS Part3 ・ECS Service がどの機能で利用されているか認識合わせ
・タスクサイズを ECS Service 毎に変えている理由
・rails コンテナの WEB_CONCURRENCY とタスクサイズについて
8 Network Part1 ・VPC, Subnet, NAT Gateway の各役割
・AWS の各種リソースの所属ネットワーク
・provider のリージョン指定
 ・例: CloudFront の ACM は us-east-1 を指定する
 ・例: バックアップの設定で ap-northeast-2, 3 を指定する
・CIDR とは?
・Subnet の設計
 ・弊社デファクトの構成
 ・アンチパターン
演習
 ・NAT Gateway がどの AZ で起動しているかコードから追ってみてください
 ・NAT Gateway をマルチ AZ で配置している理由は?*5
9 Network Part2 ・Route Table
・Public Subnet 用の Route Table
・Private Subnet 用の Route Table
・Private Subnet からインターネットへアクセスする経路の確認
・VPC Peering
・演習: Route Table は何に紐付ける?
10 IAM Part1 ・IAM User と IAM Role
・IAM User は使わない!その理由
・IAM Group, IAM Policy
・IAM JSON Policy 言語
・IAMとリソース側のポリシー
・IAM ポリシーでアクセス許可と禁止がかちあった場合、どうなる?
演習
 ・パラメータストアに登録された SendGrid API Key を復号し値を取得する権限を所持する IAM ポリシー名は?
 ・その IAM ポリシーはカスタマー or AWS どちらの管理ポリシーですか?
11 IAM Part2 ・IAM Role
・Assume Role
12 S3 ・何故 S3 を使うか?
・プロジェクトでは何に S3 を利用しているか
・ACL (Access Control List) vs バケットポリシー
・パブリックアクセスブロック・SSE・バージョニング・アクセスログ・ライフサイクルポリシー
13 CloudFront ・CloudFront とは?
・CDN, エッジサーバとは?
・CloudFront を利用した各種 Web サービスの構成
余談 JR東日本が2023年5月27日以降、JR の改札機がセンターサーバ方式を採用 *6
14 WAF ・WAF とは?
・マネージドルール, カスタマールール
・WAF のルールの評価の仕方
・論理 statement 設定例
WAF でブロックされているか確認する方法
・おまけ:
 ・IP制限ってSecurity GroupとWAFのどっちでやるべき?
 ・セキュリティグループとWAFがアタッチされている場合どっちが優先される?
演習
 ・特定のリクエストでどの WAF ルールが評価されるか?
 ・本番・ステージング環境でデフォルトアクションを ALLOW にしている理由は?
15 デプロイパイプライン ・GitHub Actions
・Code シリーズ
16 Pull Requestの出し方 ・plan 内容を確認後レビュー依頼する*7
・適切なTerraform記法を利用しているか?
・専用のData Sourceを使える場面では使っているか?
・ファイル分割やファイル中のリソースの順序は周囲と統一してあるか?

弊社のサーバは基本 Fargate を採用していること、 ネットワーク関連・ IAM 関連は初学者を苦しめると思われる為、 手厚めに数回実施しています。

開催後のアンケート

とある 1 プロジェクトについて参加者にアンケートを取った結果です。

他プロジェクトを見渡しても
「ちょっとした変更なら実装できる気がする」以上になったことは 元々の関心の高さもありますが、理解の一助になったかと思います。

勉強会をして変わったこと

サービスチームへインフラ管理の移譲が進む様になり理解の底上げができた実感があります。

以下対応をいただくプロジェクトもありました。

  • RDS for MySQL から Aurora MySQL へリプレイス
  • Aurora MySQL 5.7 から 8 系へのバージョンアップ
  • Datadog の APM Tracer による監視追加

総評

まず勉強会で全エンジニアが Terraform を触るきっかけ作りができたことが良かったです。
プロジェクト間でノウハウを共有し効率化する流れが生まれたのも大きな変化です。

プロジェクトの人員数や理解度によって、段階的に管理を移譲していますが、 強制でなく、組織として望む体制として共有した上で進め、できる様になったら評価する、 というスタンスを取れたことが個人的にとても弊社らしいなと思いました。

アンケートでハンズオン形式でも開催を希望する声があったので今後の参考にしたいと思います。

改めまして勉強会参加いただきましたエンジニアに感謝です。

以上
参考になれば幸いです。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

*1:30分は参加ハードルを下げるのにちょうど良かった。アンケートで1時間参加は業務的に難しい、という結果でした。

*2:参加したいプロジェクトがあれば任意参加可能

*3:プロジェクトによっては希望するコンテンツを別途用意したりと適宜対応しました。

*4:ファミコンとカセットで例えたら若者には伝わらなかった 😢

*5:弊社では耐障害性向上の為、 NAT Gateway は基本マルチ AZ に配置するポリシーです。

*6:エッジサーバでキャッシュして高速化するのと逆の解決方法として紹介しました

*7:対応リソース以外の変更がある場合や意図しない変更となっていないか確認後にレビュー依頼を出すことが大事

MedPeerをVue 3にアップデートしました🥳

こんにちは、MedPeerのフロントエンド開発を主に担当している森田です。

MedPeer( https://medpeer.jp )ではVue 2 系を長らく利用してきましたが、公式からの発表の通り 2023年12月31日 でEOLとなっております。

With 2024 almost upon us, we would like to take this opportunity to remind the Vue community that Vue 2 will reach End of Life (EOL) on December 31st, 2023. https://blog.vuejs.org/posts/vue-2-eol

EOLを迎えても直ちにセキュリティリスクに晒される可能性は少ないとは思いますが、 MedPeerは医療を扱うサービスの特性上セキュリティリスクは最小限に抑えたいのと、 最新のVueの機能を利用できることは開発体験としても良いためVue 3へのバージョンアップを行うこととしました💪

そして2022年9月頃から検討を含めて着手を始め、約1年間という時間が掛かってしまいましたが 2023年11月29日 にVue 3にアップデートすることができました🥳

EOL直前ではあるもののMedPeerをVue 3にアップデートした際に工夫した点や躓いた点等を整理しましたので、これからVue 3へのアップデートする方の参考になれば幸いです🙏

前提事項

まずMedPeerというサービスですが、バックエンドはRuby on Railsで記載されており基本的にはhamlやerbといったサーバーサイド側のテンプレートエンジンを利用したモノリシックでMPAなサービスで、フォームやモーダル等といったリッチなコンポーネントをVue.jsを使って実装しています。 (フロントエンドエンジニアだけではなく、バックエンドのエンジニアも実装しております。)

また、Vue 2系の最新(v2.7系)にはアップデート済の状態で、Vueを利用したコードベースの規模感としてはリポジトリ内に.vueファイルが400ファイル、行数にすると55,000行程度あり、それなりの規模感のサービスなのではないかと思っております。

アップデートの基本戦略

上述の前提事項を踏まえて、Vue 3へアップデート基本戦略として次の2つを取りました。

  • バージョンアップ作業は通常の開発を止めずメンバー全員で行えるようにする
  • バージョンアップのリリース差分はなるべく小さくする

バージョンアップ作業は通常の開発を止めずメンバー全員で行えるようにする

Vue 3へのバージョンアップは破壊的変更も多く対応箇所が膨大で時間が掛かりそうに思ったので、通常の開発業務への影響を抑えメンバー全員で行えるような方法が望ましいと考え以下を行いました。

  • Vue 3の破壊的変更の影響を受ける箇所を静的解析でエラーとしCIで検知できるようにする
  • 静的解析のエラーをtodoとして管理することで対応内容・箇所を明確化する

Vue 3の破壊的変更がCIでキャッチアップできないと期間中にどんどん影響を受ける実装が増えてしまい、いつまで経ってもアップデートできないといったことになってしまう懸念があったので、CIでエラーにできるように静的解析で主要な破壊的変更の影響を受ける箇所を検知するようにしました。

特にplugin:vue/vue3-recommendedから先行して有効化したruleの中でもVue 2系の最新でも利用できてVue 3から必須になるemitsの定義を強制できるvue/require-explicit-emitsは非常にありがたかったです 🙏✨

Vue.extendといったGlobal APIの利用はESLintのno-restricted-importsを利用することでimport Vue from 'vue'のようなGlobal API利用目的のコードを検知しエラーにするようにしました。

"no-restricted-imports": [
  "error",
  {
    "paths": [
      {
        "name": "vue",
        "importNames": ["default"]
      },
    ],
  },
],

また、カスタムコンポーネントに対するv-modelで渡されるpropsemitされるイベントが変更されるのは影響が大きかったので、以下のようなカスタムルールを作成して明示的に@inputvalueを利用して影響を受けない実装を促すようにしました。

'use strict'

const utils = require('eslint-plugin-vue/lib/utils')

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description:
        'disallow using v-model for custom component is affected by a breaking change in Vue 3.',
      categories: undefined,
      url: 'https://v3-migration.vuejs.org/ja/breaking-changes/v-model.html',
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          ignoreComponentNames: {
            type: 'array',
          },
        },
      },
    ],
    messages: {
      error:
        'カスタムコンポーネントへのv-modelの使用は、Vue 3の破壊的変更の影響を受けるので使用しないでください。\nhttps://v3-migration.vuejs.org/ja/breaking-changes/v-model.html',
    },
  },
  /** @param {RuleContext} context */
  create(context) {
    const ignoreComponentNames = context.options[0]?.ignoreComponentNames ?? []
    return utils.defineTemplateBodyVisitor(context, {
      "VAttribute[directive=true][key.name.name='model']"(node) {
        const element = node.parent.parent
        if (
          utils.isCustomComponent(node.parent.parent) &&
          !ignoreComponentNames.includes(element.name)
        ) {
          context.report({
            node,
            loc: node.loc,
            messageId: 'error',
          })
        }
      },
    })
  },
}

静的解析でエラーとなる既存ファイルは以下のようにoverridesでファイルを無効化することで対応箇所・内容を明確化。todoとして管理することでCIが通ればVue 3の破壊的変更に対応できる状態とし、メンバー全員でVue 3のアップデート対応を行えるようにしました。

module.exports = {
  overrides: [
    {
      files: [
        'app/javascript/components/Foo.vue',
      ],
      rules: {
        'no-use-v-model-for-custom-component': 'off',
      },
    },
    {
      files: [
        'app/javascript/components/Bar.vue',
      ],
      rules: {
        'vue/require-explicit-emits': 'off',
      },
    },
    {
      files: [
        'app/javascript/components/Baz.vue',
      ],
      rules: {
        'no-restricted-imports': [
          'off',
          {
            paths: [
              { name: 'vue', importNames: ['default'] },
            ],
          },
        ],
      },
    },
  ],
}

バージョンアップのリリース差分はなるべく小さくする

リリース前の動作確認期間等でVue 3アップデートの対応branchをmergeせずに保持する必要があり、競合解決といったメンテナンスコストを抑えるためにリリース差分を最小限に抑えたいと思い、破壊的変更で事前に対応できるものは対応し、なるべくリリース時点での差分を少なくするように主なものとして以下を行いました。

  • snapshotテストの差分をリリース時点では無視する
  • createAppといったVue 3の記法の一部を先行して利用できるようにする

snapshotテストをリリース時に差分に含めてしまうだけでリリース用のPull Requestの差分が数万行になってしまうのと、Componentのtemplateの実装が変わるだけで競合してしまいメンテナンスコストが非常に高くなってしまいます。 snapshotテストでは以下のようなwrapperメソッド経由で検証することで、環境変数でskipを切り替えられるようにし、ローカルでは差分が問題なさそうなことを確認した上で、リリース時点ではCIでskipしsnapshotテストの更新をリリース後に遅延できるようにしました。

// NOTE: snapshotテストをCIではskip可能にする
// ```ts
// describe('components/Foo', () => {
//   skippableIt('snapshot', () => {
//     const wrapper = mount(Target)
//     expect(wrapper.element).toMatchSnapshot()
//   })
// })
// ```
export const skippableIt = (name: string, test: () => void) => {
  const skip = process.env.SKIP_TEST === 'true'
  if (skip) {
    it.skip(name, test)
  } else {
    it(name, test)
  }
}

さらに、以下のようなカスタムルールを作成しtoMatchSnapshotを含むitskippableItの利用を促し自動的に利用を促せるようにしました。

'use strict'

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
  meta: {
    type: 'problem',

    docs: {
      description: `
      require use skippableIt for snapshot test for Vue 3 version up.
      `,
    },
    fixable: 'code',
    messages: {
      error: 'Snapshotテストはskip可能とするために skippableIt を使用してください。',
    },
    schema: [], // no options
  },
  /** @param {RuleContext} context */
  create: function (context) {
    return {
      /** @param {CallExpression} node */
      CallExpression(node) {
        if (
          node.callee.name === 'it' &&
          node.arguments[1].type === 'ArrowFunctionExpression' &&
          node.arguments[1].body.type === 'BlockStatement'
        ) {
          const block = node.arguments[1].body
          const expects = block.body.filter((node) => node.type === 'ExpressionStatement')
          const isUseSnapShotIt = !!expects.filter((expect) => {
            return (
              expect.expression.type === 'CallExpression' &&
              expect.expression.callee.type === 'MemberExpression' &&
              expect.expression.callee.property.type === 'Identifier' &&
              expect.expression.callee.property.name === 'toMatchSnapshot'
            )
          }).length
          if (isUseSnapShotIt) {
            context.report({
              node: node,
              messageId: 'error',
            })
          }
        }
      },
    }
  },
}

また、Component周りの破壊的変更に関してはVue 2系の最新(v2.7系)を利用することでComposition APIがvueから利用できるようになり、Vue 3の記法に近い形で記述できるので助かったのですが、entry等でのGlobal APIを利用した実装への影響が大きくリリース差分が膨大になってしまいそうだったので、以下のようなWrapperを用意してVue 2でもcreateApp等のVue 3に近い形で実装できるようにし、アップデート時はWrapper内のVueに対する呼び出し部分をVue 3アップデート時に変更するだけで済むようにしました。

// NOTE: 各エントリーで以下のようにcreateApp相当の記述で利用できる
// import { createApp } from 'Vue3Impostor'
class Vue3AppImpostor {
  app: Vue
  constructor(options: object) {
    this.app = new Vue(options)
  }
  mount(...args: Parameters<typeof this.app.$mount>) {
    return this.app.$mount(...args)
  }
  use(...args: Parameters<typeof Vue.use>) {
    return Vue.use(...args)
  }
  component(...args: Parameters<typeof Vue.component>) {
    return Vue.component(...args)
  }
}
export const createApp = (options: object) => {
  return new Vue3AppImpostor(options)
}

export const reactive = <T extends object>(...args: Parameters<typeof Vue.observable<T>>) => {
  return Vue.observable(...args)
}

@vue/compatの利用も検討しましたが、2021年末でメンテナンスが終了する旨がREADMEに記載があり、いずれはVue 3相当の記述に書き直す必要があるため利用しない方針としました。

これらにより差分を減らすことで、最終的なVue 3アップデートの差分は27ファイル(+ 419, - 430)に収めることができました🎉

Vue 3アップデートのPull Requestの差分(27ファイル)

アップデートで苦労した点

以下にMedPeer固有の問題もあるとは思いますが、Vue 3へのアップデートした際に苦労した点を参考になればと思い記載します📝

周辺ライブラリの乗り換えやアップデート

MedPeerでは以下のようなVue関連のライブラリがVue 3に対応しておらず、乗り換え・バージョンアップを行いました。

以下の3ライブラリに関しては正式リリースは現時点でまだリリースされていないようですが、alphaやbetaバージョンでVue 3対応が行われており、そちらを利用するような対応を行いました。(現状は特に問題は問題は起きておりません)

  • vue-infinite-loading
  • vue-multiselect
  • vue-select

vue-js-modalに関しては、vue-final-modalを利用するように既存実装を修正しました。

乗り換えやバージョンアップを行うにあたって既存のコードの修正が発生したものの、 そこまで大きな影響が出ずに対応できたのでalphaやbetaバージョンや代替ライブラリが提供されていて助かりました 🙏

※その他サービス内の一部でだけ利用されているライブラリ等がありましたが仕様を調整、独自で実装して削除しました。

リアクティブにするとプライベートな値に依存したロジックが呼び出せない

バージョンアップ時にプライベートな値(#foo等)を持つインスタンスをrefでリアクティブにした後、プライベートインスタンスフィールドにロジック実行時にTypeError: Cannot read private member # ...といったエラーが発生する事象がありました。

Vue 3からrefでリアクティブな状態にする際にProxyでwrapされるようになりましたが、Proxy経由ではプライベートな値を参照できないため本事象が発生しているようなので、

プライベートプロパティは転送できない プロキシーは、やはり異なるアイデンティティを持つ別のオブジェクトであり、ラップされたオブジェクトと外部との間を運営する プロキシー です。そのため、プロキシーは元オブジェクトのプライベートプロパティに直接アクセスすることができません。 Proxy - JavaScript | MDN

実行時にtoRawを利用して元のオブジェクトに対して実行するようにして対応しました。

toRaw() Vue で作成されたプロキシの、未加工の元のオブジェクトを返します。 https://ja.vuejs.org/api/reactivity-advanced.html#toraw

参考情報) 関連してそうなissue

mountした要素の子要素を別のVueアプリケーションからmountすると、v-bindが削除される

これは元々の実装が良くなかったのですが、以下のように既にVueアプリケーションをmountしている要素の子要素に対して、別のVueアプリケーションをmountしている箇所があり、、、

<div id="vue-app-1">
  <!-- ... -->
  <div id="vue-app-2">
    <custom-component :active="false" />
  </div>
</div>

Vue 3へのアップデート後に、後者の子要素にmountしていたVueアプリケーションのComponentに渡していたpropsへの受け渡し処理 <custom-component :active="false" /> からv-bindが削除され <custom-component active="false" />となり、本来であればactiveの値はfalseとなるところ"false"となり、論理値の結果が正反対になってしまう事象がありました。

MedPeer内で発生したケースは、Componentに渡していたpropsへの受け渡し処理 がそもそも不要だったため該当箇所を削除して対応できたため、大きな影響はなく助かりました🙏

slotで挿入したコンテンツにscoped CSSが適用されない

Vue 3アップデート後に、Vue 2系では適用されていたslotで挿入したコンテンツに対するComponent内の<style scoped>内で記述したスタイルが適用されない事象が発生しました。

<template>
  <div class="custom-component">
    <slot />
  </div>
</template>
<style lang="scss" scoped>
.custom-component{
  .slot-content {
    // 挿入されるslotに対するスタイル
  }
}
</style>

Vue 3 Migration Guideには記述が見つけられなかったのですが、以下の通りVue 3のドキュメントには明記されており、

<slot/> によってレンダリングされるコンテンツは、デフォルトでは親コンポーネントによって所有されていると見なされるため、スコープ付きスタイルの影響を受けません。 https://ja.vuejs.org/api/sfc-css-features#slotted-selectors

当初気づくのに遅れてしまったのですが、ドキュメント記載の通り:slotted等のディープセレクタを利用して対応しました。

参考情報) 関連してそうなissue

本番ビルドでだけ例外が発生する

Vue 3アップデート後に、propsの値が不正なケース(:prop-name="")等で、開発ビルドを利用している場合にはVue warnでエラーにならないのですが、本番ビルドを利用しているとSyntaxErrorが発生する事象がありました。

<div id="vue-app-2">
  <custom-component :prop-name="" />
</div>

エラーやVue warnの発生箇所を特定し対応することで、エラー自体は解消できたのですが受け渡すデータによって発生するため、気づくのが難しく対応に苦労しました・・・!

参考情報) 関連してそうなissue

おわりに

Vue 3のアップデートは破壊的変更も多く作業に着手してから約1年と長い時間が掛かってしまいましたが、 MedPeerの開発メンバーにも多大に協力して貰いEOLを迎える前に無事にVue 3にアップデートできました🎉

静的解析等を活用して対応箇所を可視化しVue 3のアップデートの前に先行して対応できる環境を用意できたのは、 進捗状況が定量的に追いやすく、リリース時の差分もコンパクトで開発メンバー全員で対応・レビューもしやすい状況にでき、 孤独感も感じずバージョンアップを進められて良かったなと今回振り返って思いました 🙏

Vue 3アップデートは一旦無事に完了しましたが、まだまだ伸び代のあるサービスなのでこれからも頑張っていきたいです💪

最後まで読んでいただきありがとうございました✨

アップデートの参考にさせていただいた資料


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

After Kaigi on Rails LT Night 参加レポート

こんにちは、サーバーサイドエンジニアの古川(@frkawa_)です。

10/27(金), 28(土)の2日間にかけて行われたKaigi on Rails 2023、お疲れ様でした。
私も現地で参加しましたが、多くの刺激を受けることができてとても有意義な2日間となりました。普段関わることの無い多くの方と交流できることが現地参加の何よりの魅力ですね。

弊社ではKaigi on Rails 2023のセッションレポートの記事も投稿しているので、是非一度ご覧ください。

tech.medpeer.co.jp

そんなKaigi on Rails 2023の熱が冷めやらぬ中、株式会社スマートバンク様、株式会社マイベスト様、そして弊社メドピア株式会社の3社合同で After Kaigi on Rails LT Night と称したアフターイベントを11/9(木)に弊社オフィスで開催させていただきました。
私も現地で参加してきたので、イベントの様子を余すところなくお伝えしていきます!

会場の様子

参加者数は何と77名(connpassの参加者数より)!
メドピアさんの会場スペース広いですね、と言っていただけることが多いのですが、それでもかなりの密度となっていました。
私も設営のお手伝いをしたのですが、イベントスペースの椅子が足らず会議室の椅子をかき集めて設置しています。笑

各社紹介

スマートバンクCTO堀井さんの司会進行のもと、主催3社の紹介からイベントスタートです。
Kaigi on Rails参加しましたかー?という質問から始まりましたが、やはりアフターイベントということでかなりの割合で手が上がっていました。

LT① 「エンジニア9名でプロポーザル8件、採択3件」を支える技術と文化

speakerdeck.com

LTトップバッターはスマートバンクohbaryeさん(@ohbarye)。
スマートバンクさんはKaigi on Railsで3名もの方が登壇されていたのが記憶に新しいですが、プロポーザル採択のためにどんな活動をしたか、というテーマでお話しされていました。
どんなプロポーザルが通りやすいかという実践的な話から、プロポーザルを作る上でチームとしてはどんなサポートをしているか、なぜカンファレンス登壇の挑戦に力を入れているか、といった内容まであり、刺さった方も多いんじゃないでしょうか。
個人的には、「感情は伝染する」「情熱やモチベーションも伝染する」という言葉が印象に残っています。

LT② WebAuthnを使ったパスワードレス認証をRailsアプリケーションで実装する

speakerdeck.com

二番手はメドピアの伊藤さん(@yuma_ito_bd)がRailsにおけるWebAuthnを使ったパスワードレス認証についてのLTを行いました。
Kaigi on Railsのセッションに触発されこのテーマを選んだそうです。
QRコードで実際にWebAuthnを体験するところから始まり、FIDOやパスキーといったパスワードレス認証に必要な前提知識の説明からWebAuthnの仕組み、Railsにおける具体的な実装例の説明まで、短い時間の中で非常に綺麗にまとまっている素晴らしいLTでした!
私も興味が湧いたので、Railsアプリで簡単なFIDO認証を試しに実装してみようと思いました。

LT③ こわくないフレームグラフ - Singedで始めるパフォーマンス改善

speakerdeck.com

続いてのマイベスト横山さん(@_shrrk)のLTは、フレームグラフは怖くない!という内容のトークです。
パフォーマンス改善において、APMツール上には現れない、クエリ元となっているRubyのメソッドをフレームグラフというツールを使って可視化するためのノウハウについてお話しされていました。
フレームグラフのどういった要素に恐怖を感じやすいかを分解し、それぞれの要素に対しどんなアプローチができるか、複数選択肢があるgemをどう使い分けるかという詳細な説明が論理立てて説明され、比較的難しい内容でも理解しやすい説明になっていたんじゃないでしょうか。
導入方法の説明も実際のプロダクトでイメージが付きやすい内容で、パフォーマンス改善をする際は是非試してみたいツールの一つだなと感じました。

公募LT① Sidekiqのオブザーバビリティを向上させた話

speakerdeck.com

公募LTの一番手は、imaharuさん(@imaharuTech)によるSidekiqにおけるオブザーバビリティの向上についてのトークです。
Sidekiqにおけるにオブザーバビリティは、遅いジョブがどれか、ジョブが完了していない原因は何かを特定できる能力であり、それを実現するためにSidekiqのログに必要な情報を吐くように改善した、という内容でした。
エラーログにjidやジョブの状態をハッシュに詰め込んだSidekiq::Context.currentを出力することでこれらのオブザーバビリティを実現したとのことです。

公募LT② Kaigi on Rails 2023 〜運営の裏側〜

続いては寺井さん(@krpk1900_dev)によるKaigi on Rails 2023の運営の裏側についてのトークです。
時系列で今回のKaigi on Rails 2023の開催に向けて2022年11月から何をやってきたかを事細かに語っていただけました。
本業が終わった後に20時から23時までミーティングをやっていたこと、2024年の会場を23年の3月時点で始めていること、スポンサー抽選にRubyのshuffleメソッドを使っていること、115件ものCFPを19時間かけて見たことなど、へぇ〜な話から衝撃的な話まで聞けて非常に面白い内容でした。
そして何より運営の大変さが伝わって来て、本当にたくさんの人の大きな労力で成り立っているイベントなんだなと思いました。
そんな運営が大変な中でKaigi on Railsに登壇もされて、直後の勉強会でも登壇されて、今回のアフターイベントでも登壇された寺井さんのすごさが際立つLTでもありました。

公募LT③ 無用な認知負荷を減らしてお手入れしやすいコードを書こう

shinkufencer.hateblo.jp speakerdeck.com

最後の公募LTは、しんくうさん(@shinkuFencer)による認知負荷に関するトークです。 人間には長期記憶と短期記憶があり、短期記憶を長期記憶に効率良く移すにはどんな認知負荷の種類があり、どんな負荷を減らすべきかという話から始まり、プログラミングにおいて保守性の高いコードがどんなものかを考える上でのヒントになる、という内容でした。 しんくうさんの説明では課題外在性負荷、つまり本質的ではない本来は不要な負荷を取り除くことが重要で、コードで言うとlinterや分かりやすい命名を使おうねという話に繋がるものでした。 ある程度経験があるエンジニアであれば感覚的に理解していることかもしれませんが、初学者の理解を助けることもできる普遍的な内容と言えるLTだったのではないでしょうか。

懇親会

LTの後は懇親会が行われました!
LTの終盤、お腹が空き始める時間に続々とピザが届き、そわそわしていた方も多いんじゃないでしょうか。
マイベストさんからはCTO選りすぐりの日本酒をご提供いただきました。ありがとうございます!
各所ではKaigi on Railsや各社のプロダクト開発についてなど、技術的な話を中心に大きな盛り上がりを見せていました。
懇親会は1時間半ほど行われ、皆さん名残惜しい中お開きとなりました。
後片付けも参加者の皆様にご協力いただきスムーズに終えることができました。この場を借りてお礼を申し上げます!

おわりに

アフターイベント、楽しんでいただけましたでしょうか?
登壇者の皆さん、スマートバンクさん、マイベストさん、ありがとうございました。
RubyやRailsのコミュニティは大きくて強い繋がりで結ばれているな、と感じることができるイベントになったかなと思います。
また次のイベントで皆さんにお会いできる日を楽しみにしております!


メドピアでは一緒に働く仲間を募集しています。
少しでも当社に興味が湧いた方は、ぜひ下記をご覧いただければと思います。ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note style.medpeer.co.jp


是非読者になってください!

Daniel Roe から学ぶ!Nuxt ワークショップ参加レポート

こんにちは、フロントエンドサウナーの土屋 (@tutti2612) です。
フロントエンドサウナーとは、サウナの UI/UX にこだわるサウナ好きのことです。今考えました。
サウナ室、水風呂、外気浴スペースの動線がスムーズなサウナはポイント高いです。

先日、日本で唯一の Nuxt 公式パートナーである NuxtLabs Japan が主催する Nuxt ワークショップに参加してきたので、その様子をレポートしたいと思います。

ワークショップの概要

このワークショップは、Nuxt コアチームのテックリードである Daniel Roe さんが講師を務めるというなんとも贅沢なワークショップです。
さらに、NuxtLabs Japan のエキスパートの方々もワークショップのサポート役として参加してくれていました。

開催日時: 2023年10月25日(水) 13:30~ 18:30
開催場所: コワーキングスペース茅場町 Co-Edo

イベントの詳細は以下のページをご確認ください。
https://zenadvisor.io/nuxtlabs-japan/workshop

会場の雰囲気

ワークショップは、コワーキングスペース茅場町 Co-Edo で開催されました。親しみやすい雰囲気の中、Daniel さんに直筆のサインを書いてもらったり、記念撮影をしたり、参加者とスタッフとの距離の近さが感じられました。

気軽に写真撮影に応じてくれる Daniel さん

Daniel さん直筆のサイン

さらに、サプライズゲストとして、Nuxt コアチームの Harlan Wilton さんが登場し、ワークショップを盛り上げてくれました。

Harlan さんと私

ワークショップの内容

ワークショップは Daniel さんがデモを交えながら Nuxt の機能についてわかりやすく解説するという形式で進みました。
内容は Nuxt と Nitro の二つに大きく分けられ、以下のような特徴的な機能が紹介されました。

  • Nuxt
    • Volar
    • Routing
    • Nuxt DevTools
    • Middleware
    • Layout
    • client.js, server.js
    • useNuxtApp
    • definePageMeta
    • Nuxt Image
    • Nuxtr
    • useAppConfig
    • Nuxt Layers
    • runtimeConfig
    • useState
  • Nitro
    • useFetch
    • SWR
    • useStorage
    • Plugins
    • useSession

Nuxt3 の目玉機能から Nuxt エキスパートの方でも知らないような細かい機能まで丁寧に紹介してくれました。

この中でも、私は特に「Nuxt DevTools」に興味を持ちました。

Nuxt DevTools は Nuxt コアチームメンバーの Anthony Fu さんが中心となって開発している、Nuxt アプリケーションの構造を視覚的に理解することができるツールです。
詳しくは彼のブログ記事を見ていただけるとイメージが掴めると思います。

私が特に有用だと思った機能は、「コードジャンプ」機能と「Inspect」機能です。
Nuxt DevTools を使えば、ブラウザ上でコンポーネントをクリックすることで、そのコンポーネントのソースコードに素早くアクセスすることができます。
Inspect 機能は、Nuxt デフォルトのバンドリングツールである Vite が SFC(シングルファイルコンポーネント)をどのように処理しているのかをステップバイステップで確認できる機能です。
Inspect 機能のデモが行われたとき、参加者からは「おおー!」という感嘆の声が上がっていました。

Inspect 機能

Nuxt DevTools の一通りの機能は、公式の Playground で体験することができます。興味のある方はぜひ、試してみてはいかがでしょうか。

全編英語で進行されたワークショップでは、適宜通訳の方が Daniel さんの言葉を日本語に翻訳してくれたため、言語の壁を感じることなく内容を理解することができました。
参加者はワークショップ専用の GitHub リポジトリを通じて質問を投稿し、機能解説の合間に Daniel さんがそれに答えてくれました。質問は英語でも日本語でも受け付けていたのがありがたかったです。

機能紹介の後、ワークショップの締めとして、Nuxt と Supabase を使用した TODO アプリの作成に挑戦しました。Daniel さんと共に作業を進めることで、彼の卓越したタイピング速度とツール操作を間近で学ぶことができ、ツールの習熟度がいかに生産性に寄与するかを実感しました。

懇親会の様子

ワークショップの後は、懇親会が開催され、参加者は Daniel さんや Nuxt エキスパートたちと気軽に交流できました。Daniel さんからはまだ一般には公開されていない情報も聞くことができ、大変有意義な時間となりました。

懇親会の様子

おわりに

直接、著名なライブラリの作者と話ができる機会は、格別の学びがあると感じました。彼らの言葉は一次情報としての価値を持ちます。
日本において、国際的なエンジニアと交流できる機会は限られているため、今後もこのようなイベントには積極的に参加していきたいと考えています。

参加者全員で Nuxt ポーズ!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp