シンクロ・フード エンジニアブログ

飲食店ドットコムを運営する、株式会社シンクロ・フードの技術ブログです

社内サーバー上のJenkinsジョブからAWS Codeサービス群にデプロイ処理を移行しました

はじめまして、SRE チームの佐藤です。
SREチームの業務として、サービス信頼性向上のためのインフラの構築/改善や保守業務や、開発業務効率向上のためのCI/CD整備等に携わっています。

先日、社内サーバー上のJenkinsジョブで実装されていたビルド/デプロイの仕組みをAWS Code サービス群やAWS Step Functionsを利用した新構成へ移行しました。
今回はその対応や検討の経緯を紹介させていただきたいと思います。

移行の背景

既存構成

シンクロ・フードで提供しているサービスは基本的にRuby on Railsで構築されているのですが、昔から使っているシステムではJava (Seasar2)が利用されています。
Seasar2 から Railsへのリプレースを進めているためかなりの部分がRailsに移行されていますが、完全には移行完了できておらず、現時点でSeasar2側の開発もそれなりの頻度で発生している状況でした。

CI/CDの構成もそれぞれ異なっており、Rails側はGitHub Actionsを使ったテストやAWS Code サービス群を使ってビルド/デプロイを実施していますが、一方のSeasar2側は社内サーバー上のJenkinsジョブを利用してテストからデプロイまでを実施していました。

既存構成の課題

Seasar2向けのJenkinsジョブによるCI/CDは長らく運用されていたこともあり、処理自体は非常に安定していましたが、以下の課題感も持っていました。

課題1: 設備/HW面の懸念

開発業務を継続する上で重要な役割をもったサーバーですが可用性の観点に懸念がある状態でした。
社内サーバーは文字通り社内(オフィス)に置かれているサーバーです。データセンタのように電源系統冗長化はされておらず予備の電源装置などもないため、設備メンテナンスなどでオフィスが停電する際に必ず停止期間が発生していました。
また、HW保守契約なども特に結んでおらず、機器に障害が起きた場合は交換部材を探すところから対応する必要があり万が一の際の復旧時間にも懸念がありました。

課題2: 維持管理の手間

こちらはサーバー運用でよくある課題かと思いますが、OSやソフトウェア(Jenkins自体やJenkinsのplugin)のバージョンアップが後回しになっており、本サーバーについては塩漬け状態でした。
いつかは対応が必要と思いつつも、サービス提供側の基盤構築/運用をどうしても優先してしまう状況が続いていたため、そもそも維持管理が不要な状態としたいと感じていました。

移行方針

前述の課題に対する対応方針を以下のように整理し、詳細な検討を進めていきました。

"課題1: 設備/HW面の懸念"への対応方針

弊社ではデータセンタは利用しておらず、クラウドサービス活用が第一の選択肢となりそうです。
複数のクラウドサービスを利用している状況ですが、AWSを主なクラウド基盤として利用しているため、AWSへ移行することで設備/HW面の課題については対応する方針としました。

"課題2: 維持管理の手間"への対応方針

色々な選択肢がありそうですが、Rails側では既にGitHub ActionsやAWS Codeサービス群を活用したCI/CDが実装されているので、Seasar2側もこのあたりを軸に構成決めていくことで維持管理の手間は最小化する方針としました。

移行対応の詳細

ここからは、どのように移行を進めていったかの詳細を記載します。

構成概要

細かな検討経緯などは一旦後回しにして、対応の前後でどのように構成変化したかを記載します。

着手前の構成

移行前の構成

  • CI/CDに係る主な要素

    • AWS上
      • ソースコード管理: AWS上にGitHub Enterprise Server(以下GHES)
    • 社内サーバー上
      • CI: Jenkinsジョブでテストや成果物作成を実行していました
      • CD: JenkinsジョブでTomcatへのデプロイ処理を実施していました
  • デプロイ処理の大まかな流れは以下の通りです

    • 1: Jenkinsジョブ実行時のパラメータ指定でデプロイ対象等挙動を調整する
    • 2: デプロイ対象サーバーをLBから切り離す
    • 3: デプロイ対象サーバーのTomcatを停止する
    • 4: デプロイ対象サーバーにビルド成果物を配置する
    • 5: デプロイ対象サーバーのTomcatを起動する
    • 6: デプロイ対象サーバーをLBに再接続する
    • 7: 冗長構成のため次のデプロイ対象サーバー向けに同様の処理を行う
  • デプロイ実装方法については本番/非本番環境間の構成差異や実装タイミングの違いに起因して若干の差異がある状況でした
    • シェルスクリプトによる実装
    • Fabric(デプロイに活用できるPythonのライブラリ)による実装

移行後の構成

移行後の構成

移行後のJavaアプリデプロイフロー

  • ビルド部分はGitHub Actionsに移行
    • ビルド成果物はS3に配置する
  • デプロイ処理は以下の組み合わせで実装
    • AWS CodePipeline(以下CodePipeline): パラメタ指定で処理を起動
    • AWS Step Functions(以下Step Functions): パラメタに応じて処理調整
    • AWS CodeBuild(以下CodeBuild): Step Functionsから呼び出され、各State向けに用意されたAnsible Playbookを実行することでデプロイを行う
  • その他、本記事の記載対象とはしていませんが、社内ライブラリを管理するために利用しているMavenリポジトリをAWS CodeArtifactへ移行しています

検討経緯

テスト/ビルド部分の移行先として、組織としてCIにGitHub Actionsを利用する方針だったのでGitHub Actionsにすんなり決まりました。

一方でデプロイ箇所については結構悩むことになりました。
もともとは CodePipeline & Deployを軸としたシンプルな構成で実装したいと思っていましたが、AWS CodeDeploy(以下CodeDeploy) への移行に課題があったのです。

  • 障壁1: 複雑な条件分岐や順序制御が難しい

    • 詳細は本記事では割愛するのですが、セッションレプリケーションの仕様上、同一サーバー上のTomcatを連続して停止してしまうとセッションが巻き戻る問題がありました
      • 詳細についてご興味あれば弊社の過去ブログも是非ご参照ください
      • こちらについてはCodeDeployのデプロイグループをサーバー単位で作成し、順次呼び出しを行うことで解決できそうです
    • 既存環境ではJenkinsジョブ実行時のパラメタで挙動を制御することで、デプロイ対象を制御するなどを行っており、移行後も引き続き同機能を提供したいと考えていました
      • 実現方法検討してみたのですが、CodeDeployだけでこのような制御を実装することは難しそうに思えました
  • 障壁2: CodeDeploy Agentを導入できない問題

    • そもそものデプロイ対象サーバーのOSが残念ながらCodeDeploy Agentに対応していませんでした
    • Apache License 2.0で公開されているCodeDeploy Agentの導入も検討したのですが、こちらもRubyのバージョンが条件を満たしていない状況でした
    • 前述の障壁のこともあり、Rubyのバージョンアップ検証を実施するくらいならいっそ代替案を検討してみようと考えました

代替案

当初考えていたCodeDeployによる実装が難しいため、次の選択肢としてAWS上のなんらかのコンピュートリソース上でデプロイ処理を実行するという方式を検討しました。

まず、検討したことはデプロイ処理の実装方法です。
既存ではシェルスクリプトとFabricを利用していたため、比較的複雑な処理を実装していたFabric側に併せて処理統一することも検討したのですが、ここでも懸念がありました。

  • Fabricのv1を利用しているが、現在Fabric v2以上の利用が推奨されている
  • Fabricのv1から v2移行に際しては一定コード書き換えが必要そう

現在SREチームではサーバーのOS以上のレイヤの管理は原則Ansibleを利用して管理する運用となっていたこともあり、どうせ書き換えが必要ならいっそのことすべてAnsibleで実装してしまう。という方針にしました。
もう一つの選定理由としてはAnsibleは普段から書き慣れており、かつ処理ロジックも既存処理から流用できるので、移行コストがさほどかからないのではないか?という今振り返るとやや楽観的な見積もりもありました(実際はサーバー間の実行順序制御などツール間の違いを埋める検討が必要で片手間で対応できるほど容易ではありませんでした。)

次にAnsibleを実行するコンピュートリソースの選定です。
極力管理対象OSは増やしたくないという思いがあり、EC2利用以外で検討しました。
ECSタスクとして実行する方法や、CodeBuildから呼び出す方法を検討しましたが、既にRails側でCI/CDで利用しておりNW環境が整っていたこともありCodeBuildを採用する方針としました。

あとはCodeBuildをどのように呼び出すかを検討します。
移行前のJenkinsではパラメタを使って柔軟にデプロイ処理対象を制御していたので、移行後も極力同等の機能は提供したいと思いました。
こちらについては、CodePipeline のv2タイプ パイプラインでは実行時に変数が指定できるようになっているので問題はなさそうです。
(余談ですが、Jenkinsの場合はパラメタに設定する値をドロップダウンメニューから選べたりして操作が一目瞭然だったのですが、CodePipeline側では現状パラメタ指定は文字入力のみのようです。手順書を用意したり、パラメタ説明に期待する入力値を記載するなどでお茶を濁しています)

次に受け取ったパラメタを使ってどのように処理を調整するかを検討しました。
AnsibleのPlaybook内でパラメタを使って制御するという方法は以下の理由で検討外としました。

  • Playbookが複雑になりすぎてしまう懸念がある
  • デプロイ処理の各所でWEBページの応答確認を行っている都合上、単一Playbook内で冪等性を保証することが難しい

その他にも、なんらかの理由でPlaybookが失敗した場合、問題を取り除いた後に気軽に処理を再スタートしたいという思いもありました。
(既存のJenkins上の処理は単一のジョブとして構成されており、かつ処理に冪等性がない状態でした。稀にジョブが失敗した場面ではジョブ再実行ができないためあたふたしながら手動で後続の作業を実施してたりしたので、この機会に改善したかったという思いもありました)

今回は以下の理由からStep Functionsで処理の分岐を行い、Step FunctionsからCodeBuildを呼び出す構成を取る方針としました。

  • Step Functions は社内で活用されており、一定知見が溜まっていた
  • Step Functionsにはredrive機能で失敗したStateから再実行が可能で活用できそう

実装

AWSの管理はTerraformを利用しているため、Step Functionsの実装もTerraformから実施しました。
直接 Terraform のコードを新規に作成するのではなくAWS管理コンソールから処理を組み立てた後、JSON ベースの構造化言語(Amazon States Language)をTerraform に取り込み、これをベースに適宜修正を行うことで比較的短期間で実装できました。

前述の通りCodePipelineやCodeBuild周りの環境整備についても、既に利用している環境だったので追加で検討した箇所はIAMやSGくらいで、概ねスムーズに実装ができました。

一点実装段階で検討不足が露呈したのは、GHESとの接続部分です。
設計当初はCode Connections(旧CodeStar Connections)をCodePipelineのソースに指定することでStepFuntions/CodeBuildにGHESリポジトリのデータを渡せると思っていました。
ただ、Step FunctionsへアーティファクトのS3パスをパラメタの一部として渡す方法はなさそうだったためこの方法が利用できないことが分かりました。
こちらの対応策としては、GHES側でトークンを用意しCodeBuildでこのトークンを登録することでアクセス許可させる方式としました。

導入/切り替え

今回の移行対象ジョブは複数あったのですが順次導入していくと、デプロイ処理を行う開発者の方がどのジョブがJenkinsにまだ残置していてどのジョブがAWSに移行されたかわからなくなる恐れがありました。
なので、導入自体は裏で粛々と進めておき移行対象すべてのテストが完了した段階で切り替え宣言/運用ドキュメント差し替えを行うという方針を取りました。
この方針自体は問題なかったと思っていますが、並行稼働期間が生じることで顕在化した問題もありました。

  • 既存ジョブ側で権限不足でデプロイ処理に失敗する
    • こちらは単純なミスではありましたが、移行後のデプロイ処理でファイル配置時に指定した所有者に誤りがあったため、既存ジョブが実行された際に権限不足でファイルの置き換えができない問題でした

まとめ

JenkinsにはわかりやすいWEB管理画面があり、私を含め愛着を持っている開発者も多かったのですが、今回は基盤運用の手間を削減するために思い切ってAWSのマネージドサービスを活用したリファクタリングを行う選択をしました。

移行後に若干のバグ修正などが発覚したものの現状は安定して稼働してくれています。今回移行を行ったことでSeasar2からRailsへの移行が完全に完了するその日まではHW障害に怯えることなく、CI/CDを安定して提供できるようになったのではないかと思っています。

かなりニッチな内容になってしまった気もしますが、本取り組みの内容がどなたかの役に立てれば幸いです。

シンクロ・フードのエンジニアが書籍購入補助制度を利用して2023年度に購入した書籍ランキングTOP5を紹介します

こんにちは、シンクロ・フードの越森です。

昨年、書籍購入補助制度を利用して2022年度に購入した書籍を紹介する記事を書いていますが、今年も2023年度(2023年4月〜2024年3月)に購入した書籍のランキングを紹介してみようと思います。

tech.synchro-food.co.jp

2023年度の購入書籍

2023年度の1年間で購入された書籍数は195冊で、エンジニアは35名在籍していることから1人当たり5.6冊購入している計算になります。

2023年度の購入書籍ランキングTOP5

次に2023年度に購入された書籍のランキングTOP5は以下になります。

順位 書籍名 購入冊数
1位 システム設計の面接試験 5
2位 実践Terraform AWSにおけるシステム設計とベスト プラクティス 4
3位 Good Code, Bad Code ~持続可能な開発のためのソフトウェアエンジニア的思考 3
3位 SRE サイトリライアビリティエンジニアリング 3
3位 パーフェクト Ruby on Rails 【増補改訂版】 3
3位 プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで 3
3位 世界一流エンジニアの思考法 3

集計してみて驚いたことは重複して購入している書籍が少ないことです。
昨年は116種類、218冊で多くの種類の書籍を購入していると思っていたのですが、今年は141種類、195冊と昨年以上に重複して購入する書籍が少なかったです。
色々な書籍を購入していて面白いと思う反面、読んでみて良いと思った書籍を社内で共有する仕組みを用意していないことから重複することもないのかなとも思いましたので、次は紹介するような取り組みも進めてみたいと思います。
(今は書籍を購入してシステムに登録するタイミングでSlackに通知していますが、誰がどの本を購入したということしか分からないようになっています).

1位の「システム設計の面接試験」は書籍のタイトルにあるように面接試験を想定していることもあり、システム要件の確認から概要設計、要件に合わせた設計の深掘りと進むような形でシステム設計の検討の流れを追体験できるような形になっています。
取り扱っているテーマも幅広く、いつも利用しているキーバリューストアの設計から通知システムの設計、果てはYouTubeの設計と自身の業務でも関連する内容からあまり意識しない内容まで様々なシステム設計について学ぶことができます。
あまり理解していないシステムについては考えることも多く読み進めるのに時間がかかったりしますが、確実に自身のシステム設計の引き出しが増える感覚があり充実感があります。 章の最後に必ず自分を褒めてあげましょうとあるのもとても良かったです。

2位の「実践Terraform AWSにおけるシステム設計とベスト プラクティス」は弊社ではインフラは Terraform でコード化して管理しているため、SREチームのメンバーが Terraform の基本を学ぶために購入することが多いのと、最近はWebエンジニアも Terraform を触る機会があったりするため多く購入されていると思います。
2019年に出版された書籍ということもあり、バージョンの違いで書籍通りでは動かないこともあるため調べながら対応する必要はありますが、Terraform について体系的に学ぶことができます。

最後に

あらためてまとめてみるとたくさんの種類の書籍が購入されていることに驚きますが、今年に入ってからも多くの書籍が出版されていてそれも納得です。
色々読みたいと思う書籍があるのですが、全然読むスピードが追いつかず積ん読が多くなっています。
今のところ以下の書籍を次以降に読んでいく予定ですが、皆さんはどのような書籍を読もうと思われているでしょうか?

求人@インテリアデザインのRailsリプレースが完了したのでリプレースの効率とバグの傾向を分析してみた

はじめまして、開発部の大沼です。
求人インテリア内装建築グルメバイトちゃんなどのサービスの開発に携わっています。

2019年から始まった求人@インテリアデザイン(以下求人インテリア)のRailsへの移行プロジェクト(以下Railsリプレース)が完了したので、それについてお話ししたいと思います。
Railsリプレースの報告と共に他サービスでリプレースを行っていくときの参考になればと思い効率とバグの傾向を分析することにしました。

求人インテリアとは

求人インテリアは2005年にサービスを開始しているインテリアデザイン業界に絞った専門求人サービスです。
以前はJavaとSeasar2を使用して開発していたのですが弊社で現在メインで使っているRubyとRailsへのリプレースを行うことになりました。

リプレースの方針と状況

リプレース初期のバージョンはRubyは2.6でRailsが6.1になります。
サービスの規模はCRUDごとの機能数が107 で画面数が169となっています。

以下の方針でリプレースを進めました。

  • CSSとJavaScriptは変更しない
  • HTML構造はそのままでviewを移し替える
  • バックエンドはクラス設計から見直している

今回のRailsリプレースによって技術的には以下の恩恵が期待されます。

  • Seasar2のサポートが切れているためセキュリティリスクがあるがRailsは継続的なアップデートが期待できる
  • 黎明期の技術的に未熟なコードが残っているが、それらを保守性や可読性の高いコードに書き直せる
  • ビルド環境やアプリケーションが稼働するサーバなどのインフラもレガシーであり置き換えることでデプロイフローの簡素化やセキュリティアップデートを受けやすくなる
  • JavaのリポジトリはCIが整備されていないのでRailsリプレースを行い自動テストが常に実行される環境にすることで保守性の向上が期待できる

リプレースは完了して全ての機能がRailsで動くようになり、今後の開発で全ての恩恵を受けれる状態になりました。
具体的な数値があげられるものでは自動テストのカバレッジは約95%と高い水準になっています。

次項からは今回のRailsリプレースの開発について振り返ろうと思います。

開発効率について分析

Railsリプレースでは担当チームが一度変更されました。まずは開発期間などの基礎情報をまとめました。

前チーム 後チーム
開発期間 2019年 11月 ~ 2020年 3月 2020年 4月 ~ 2024年 4月
開発人数 3人 15人
開発工数 832h 828h
開発状況 Railsリプレースが中心 機能開発の合間で開発

リプレース前の状況としては求人インテリアの開発チームの人数が少なく、機能開発が常に動いていたためリプレースがなかなかできずにいました。そこで機能開発をよく行う主要機能のみを先に集中してリプレースをする選択をしました。
前チームは短期間で少数のメンバーが開発して後チームは多数のメンバーが少しずつ開発したことから前チームの方が効率よく開発できたのではないかと考えました。

開発効率の算出の仕方と前チームと後チームの開発効率について分析していきます。

開発効率の分析方法

開発効率を出すためには1タスクごとの粒度を定義する必要がありました。タスクはRailsリプレースを細かく分割したものでデプロイすることで完了します。Railsリプレース全体で77タスクに分けられています。
粒度としてファイルチェンジ数が使えるのではないかと考えてタスクごとの ファイルチェンジ数 / 開発時間 を効率として相関があるのかを調べました。
相関があることが確認できればチーム別にファイルチェンジ数の合計 / 開発時間の合計で全体の効率が算出できます。
補足として主に新卒入社された方が開発を行っていて年次はほぼ一緒でスキルの差はあまりなかったと思います。

抽出の条件は以下になります。

  • ファイルチェンジ数は JavaScript・CSS・画像ファイルはそのまま移行するため無視してrbとerbファイルのみを対象にする
    • rbとerbファイルの割合はどちらのチームでも7割ほどで差はなかった
  • 影響の大きい開発時間が10h以上のタスクを対象にする
    • 前半は26タスク・後半は23タスクが該当して全体の9割ほどの開発時間を占めている
  • 外れ値を除外するために要件ごとのファイルチェンジ数 / 開発時間を対数にとり箱ひげ図を用いて四分位範囲(第1四分位数から第3四分位数までの範囲)の1.5倍を上下限として判定する
    • 5タスクが該当して計算から除外した

結果

結果は以下のようになりました。相関係数が0.7を超えていることからある程度はファイルチェンジ数をタスクごとの粒度として使用できると判断しました。

前チームの開発効率の散布図

後チームの開発効率の散布図

外れ値を除いた10h以上のタスクの効率 前チーム 後チーム
ファイルチェンジ数(rbとerbのみ) 1046 339
開発時間(h) 757.93 483.58
効率 1.38 0.70
相関係数 0.86 0.72

前チームの効率のほうが2倍程度高くなっていることがわかります。
全てのタスクとファイルチェンジを対象にした場合でも効率は前チーム2.1で後チームが1.0であり比はほとんど同じになります。バーンアップチャートにするとこのようになります。

総工数と総ファイルチェンジ数の推移

結論

以上の結果から多人数で分散して対応すると効率が悪くなることがデータでも確認できました。
長期間で少しずつ開発すると開発メンバーが入れ替わりになるため仕様理解にかかる時間が増えるのが主な原因だと考えました。
リソースが限られている状態では難しいかもしれませんがRailsリプレースを担当するメンバーをある程度固定して短期間で完全に置き換えるまで進めた方が良いと思いました。

Railsリプレースによって発生したバグの傾向の分析

他のアプリケーションでRailsリプレースを行う際の参考にするために発生したバグの傾向を見ていこうと思います。
バグの対応件数は少なくとも22件は確認できました。発生頻度としては 工数 80 hに対して1件ほどの割合になります。
1件ずつ分類したところテストケースが網羅されておらずに起こるバグが全体の半数ほどでした。それらの根本的な原因としては以下のいずれかに該当していました。

  • 仕様の理解不足
  • 片方の言語の知識不足
  • ディレクトリ構成が不適切なことによって影響範囲から漏らしてしまう
    • 例: 非同期処理の移行でJavaScriptのファイルが想定外のページから呼ばれていることでそれに気づかずに修正をしてしまいエラーが出る
  • ステートフルな実装
    • 例: 詳細ページから一覧ページに戻る際に検索条件を残すためにcookieを使っているときにcookieが存在しない場合かつ特定のパラメータがある場合にエラーが発生する
  • 入力なしの時の初期値が異なることによって発生するケース
    • 例: 空白を初期値としてDBに保存していたところにNULLを入れてしまうことでそのレコードを使う際にエラーが出る

移行元のコードの可読性が低かったり配置が不適切だとバグの発生を移行段階のみで防ぎ切るのは難しいです。またRailsリプレースは経験の浅いメンバーが開発するケースが多くどちらかの言語の知識が不足していてバグが発生しやすくなるのではないかと思います。

まとめ

求人インテリアのRailsリプレースについての分析を行いました。他のアプリケーションで進めているRailsリプレースや将来行うであろうRailsからの移行時に生かしていきたいです。Railsリプレースは完了したばかりなので今後の求人インテリアの開発にも注目していけたらと思います。

担当サービス体験会をやってみた

こんにちは。開発部の飛澤です。
求人飲食店ドットコム関連サービスの開発・運用保守を担当している開発チームのチームリーダーをやってます。

普段は飲食店ドットコム求人飲食店ドットコムといった弊社の「飲食店と求職者のマッチングサービス」(※以下求人事業)に関わるサービスや業務システムの開発に携わっています。

今回はチーム活動の一環として、担当サービス体験会を実施したのでその感想などをお伝えしたいと思います。

担当サービスについて

弊社の飲食店向けサービス「飲食店ドットコム」は、飲食店の出店・開業から運営、退店までをサポートする経営・運営支援プラットフォームです。私たちの開発チームは、「飲食店運営」における重要な項目の1つである人材確保にフォーカスした求人サービスの開発を担当しています。
飲食店のお客様が「飲食店ドットコム」からお店の情報や求める人材についての求人情報を作成し、その求人情報は「求人飲食店ドットコム」に掲載されます。「求人飲食店ドットコム」に掲載された求人情報を求職者が閲覧し、希望に沿った求人に応募するといった飲食業界に特化した求人・求職のサービスです。
求人飲食店ドットコムに掲載の個人店の9割が弊社サービスのみで求人募集をされているため、求職者は個性を持った飲食店を探すことができます。また、飲食店専門の求人媒体であることから利用されている求職者は飲食経験者も多く、即戦力となる人材を効果的に採用できることが弊社の求人サービスの特徴です。

私たち求人サービス開発チームではこの求人事業領域を大きく3つの区分に分けて日々開発業務を行っています。
1つ目は飲食店が利用する飲食店ドットコム、2つ目は求職者が利用する求人飲食店ドットコム、3つ目は弊社社員が顧客・契約情報等を管理するために使用する業務システムです。
それぞれ個別のシステムとして動いており、それらの開発・運用保守を行っています。

背景・目的

今回の体験会を実施したのはSlackでのチームメンバーのつぶやきがきっかけでした。

「単語は聞いたことあるけど、ドッグフーディングってどんな効果があるのだろう?」と気になって調べてみると、「自社製品をユーザとして日常的に利用することで改善点を見つけることができる」ほかに「プロダクトを実体験を通して学ぶことができる」といった効果なども見込めるとのこと。導入ハードルもそこまで高くなく、今のチームの状況的にやってみる価値はあると感じました。

昨年度から今年度にかけて、求人サービス開発チームは新しいメンバーを複数人迎えることができました。
新メンバーの方には勉強会や業務を通じて、サービスが提供している機能や仕組みについて理解を深められるようにしていますが、どうしても 陰が薄い 触る機会の少ない機能についてカバーしきれないことも少なくありません。
また、求人サービス全体を通して概要は知っていても具体的な知識として定着できていないため、パターン漏れによるバグを埋め込んでしまったり、作業後半になって不足部分が発覚し予定よりもリリースが遅れてしまうという課題もありました。

今回はドッグフーディングを体験しつつ、各メンバーが知らなかった機能・仕様について1つでも多く学びながら共有することでチームの知見を増やすことを目的として実施することにしました。

また弊社開発部は現在フルリモート勤務が推奨されており、日本であればどこでも勤務OKとなっています。
チームメンバーには関西在住の方もおり、直接顔を合わせる機会も少ないためこういったオフライン活動の中でコミュニケーションが活発に取れれば良いなと考えました。

やったこと

求人サービスの全体の流れの復習

まずはシンクロ・フードの求人サービスを体験するにあたって、全体的な流れ・概要をチームで認識合わせを行いました。
以下、行った説明内容を抜粋

求人募集の掲載手続き〜掲載開始まで

  1. シンクロ・フードが飲食店(法人or個人事業主)に対して、求人飲食店ドットコムへ求人募集を掲載しないか営業を行う
    1. 飲食店からシンクロ・フードに対して問い合わせからどのようなプランで掲載するか営業担当とやりとりを行うケースもある
  2. シンクロ・フードと飲食店の間で契約を結ぶ
    1. ※基本的に契約の手続きはシンクロ・フードの営業担当が主となって進める。
  3. 掲載する求人原稿を作成する
    1. 飲食店のお客様が飲食店ドットコムで作成
    2. シンクロ・フードの営業担当が作成
  4. 掲載スタート

掲載された求人募集へ応募~面接まで

  1. 求職者が求人飲食店ドットコムに会員登録する
  2. 会員となった求職者は求人飲食店ドットコムに求人募集を掲載している飲食店に応募する
    1. ※先に会員登録が済んでいない場合は、会員登録とともに応募することも可能
  3. 応募先の飲食店と応募した求職者の間でメッセージのやりとりを行い、面接日程の調整を行う
    1. ※飲食店と求職者のやりとりは「飲食店ドットコム」「求人飲食店ドットコム」のそれぞれに用意されたメッセージ機能を通して行われる
    2. ※シンクロ・フードが提供できるのはここまで。この後の面接・採用のフォローなどはサービスとしては提供してない

番外編: スカウト

  1. 求人募集を掲載している飲食店が「スカウト」オプションサービスを購入する
    1. ※購入元は「飲食店ドットコム」内のページまたは営業担当を通じて契約を新たに結ぶ
  2. オプションを購入した飲食店は求職者の一覧から求める人材を探し、スカウトを送信することができる
    1. ※求職者がスカウトを受け取る設定にしていることが前提となる
  3. スカウトした求職者から反応があれば、応募同様メッセージ機能でやり取りを行い、面接日程を調整する

チーム分けと触って欲しい範囲についての決定

提供しているサービスの根幹部分の流れについて理解を深めるため、以下のようにチーム分けと通って欲しいシナリオを決めました。

飲食店チーム

  • 対象: 飲食店ドットコム
  • シナリオ
    • 会員登録
    • シンクロ・フードへの問い合わせ
    • 求人原稿の作成
    • シンクロ・フードと契約を結ぶ
      • シンクロ・フード営業担当が行う業務システムの操作は別途実施する。今回は意識しない。
    • 応募した求職者者とのやりとり
    • 面接日時の設定
    • (できたら)スカウトしてみる

求職者チーム

  • 対象: 求人飲食店ドットコム
  • シナリオ
    • 会員登録
    • プロフィールの登録
    • 飲食店チームが作成した求人募集に応募
    • 応募先の飲食店とやりとり

両サービスともにPC版とスマートフォン版があるため、それぞれのチームでPC版・スマートフォン版両方を操作してユーザー目線でサービスを利用して意見を出し合いました。

今回はオフライン参加が8名、オンライン参加が4名でした。
オフライン参加の方は会議室に集まり、オンライン参加者はGoogleMeetで参加しました。

サービスの体験ステップに入ってからオンライン参加者は個別の通話で行っていたため、リアルタイムに反応や意見を受け取りづらかったり、置いてけぼりになってしまった面が今後活動を続けるとしたときの課題だと思いました。オフライン側でわちゃわちゃしているとオンライン側の雑音になってしまうし、かといってオフライン側をマイクミュートしてしまうのもどうかなぁ…と今でも悩んでます。

こういったチーム活動は多くないのでうまく進むか心配でしたが、それぞれのチームが様々な意見を出しながら盛り上がっていました。新しいメンバーにとって初めて見る機能について質問があったり、昔からある仕様に戸惑う場面もありました。
特に飲食店側は求人掲載までの手順が多かったり初見だと分かりづらい部分もあるため、「原稿作成の手間が多い」「この項目の設定の仕方、お客さんからしたら分かりづらくない?」みたいな声がありました。

取り組みを通じて出た意見・感想

今回の活動を通じて参加した方から頂いた感想や意見を抜粋してお伝えします。

  • 求人サービスは成熟しきっているイメージだったが、「これで大丈夫だろうか?」という部分もあり、まだまだ改善の余地がたくさんあるんだなと思った
  • 入社して間もないため、飲食店側・求職者側の挙動を見ながら一連の流れを理解することができたので非常に良い取り組みだと思った
  • UI面で気になる箇所も見つかったので、企画チームに提案したい
  • 普段触ることがほとんどないところをじっくり触れて、とても勉強になった
  • 飲食店側をしっかり見るのは数年ぶりだったが、ユーザーの使い勝手をより意識させられた会だったと思う
  • 時間の都合でちゃんと触れない部分もあったので、はやめに次の開催を希望する

それぞれが今まで知らなかった機能や仕様について学び、活発なコミュニケーションが取れた活動になったと思います。
一度プログラムから離れてユーザーとしてサービスと向き合い、疑問や改善点を多く見つけることでチームとしてもっと求人サービスを成長させられることを認識できたと思います。
今回は求人募集の掲載から応募、面接日時決定までのやりとりまでを行いましたが、次回以降では今回触れなかったスカウト機能を使ってみたり利用者のペルソナを設定して操作してみるなど操作範囲ややり方を検討していきたいと思います。
また、ユーザー視点でサービスを触っていきましたが、今度はディレクター視点でサービスを見て機能やUIの現状の仕様の理由を考えることで今後のサービス開発に活かせるんじゃないかと考えています。

まとめ

求人サービス開発チームで「飲食店ドットコム」と「求人飲食店ドットコム」の担当サービス体験会を実施しました。
日々の開発業務の中ではサービス全体を通して使用感を確かめる機会が多くないので、担当領域の知識を深めるとともに自分たちの開発しているサービスと向き合うよい機会になったと思います。
今回出た意見や疑問についてはしっかりまとめて企画チームに提案してサービスをより良くしていけたらと考えています。
次回を希望される声もありますので、やり方や進め方を模索しながらチーム活動の一環として定着していきたいと思います。

突撃!隣のキーボード 2024年

こんにちは。 開発部の四之宮です。

エンジニアブログ界で定番となっているであろう「突撃!隣のキーボード」ですが、弊社では記事にしたことがなかったので、この度記事にしてみました。

弊社開発部ではフルリモート制度が導入されており、日本国内であればどこからでもリモートワークで勤務可能となっています。
そのため、自宅の開発環境に投資する社員も増えていますが、まずはキーボードに投資する方も多いのではないでしょうか?

そこで、エンジニアを対象にアンケートの実施と、数名にこだわりについて語っていただきました。

アンケート結果

弊社のエンジニアを対象に、使っているキーボードについてのアンケートを実施しました。
結果は下記の通りになりました。

使用しているキーボード

使用しているキーボードの種類について、ノートPCのもの(外部キーボードを使用していない)、市販、自作 から選択してもらったところ、下記のようになりました。

使用しているキーボードの割合

あまり偏りがない結果でした。
筆者としては、ノートPCのものをそのまま使用している人が多く、少しびっくりしました。 (大部分のエンジニアは MacBook Pro を使用しています)

キー配列

キー配列について、JIS、US、その他 から選択してもらったところ、下記のようになりました。
キーマップを変更している場合は、近いものがあればそれを選択してもらっています。

キー配列の割合

こちらは、JIS と US でほぼ半々という結果になりました。

外部キーボードの形状

外部キーボードを使用している方を対象に、形状について、スタンダード、分割、その他 から選択してもらったところ、下記のようになりました。

外部キーボードの形状の割合

スタンダードが少し多いですが、分割を使っている人もかなり多いといえるのではないでしょうか?

外部キーボードの種類

外部キーボードを使用している人に、どんなキーボードを使っているのかを聞いてみました。
複数名いる場合のみ、括弧内に人数を記載しています。
市販では REALFORCE と keychron が、自作では Corne が人気でした。

市販

  • keychron(6名)
  • REALFORCE(4名)
  • HHKB(2名)
  • Magic Keyboard
  • Majestouch Xacro M10SP
  • Majestouch 2
  • Logicool MX Keys

自作

  • Corne(5名)
  • Helix
  • Lily58
  • NIU mini
  • TheEndpoint
  • Killer Whale

使用しているキーボードについて

ここからは使用しているキーボードについて、何人かにより詳しく語っていただいたものになります。
奇しくも自作勢だけになってしまいましたが、個性的なものも多く読み応えがあるものになっていると思います。

大久保

使用しているキーボード

Corne V4 Cherry

そのキーボードを選んだ理由

もともとは、Ergodox EZ→Iris→Corne V1 という感じでキーボードを移り変わり、特に不満もなく Conre V1 を使っていたのですが、Corne V4 が発売された、という話を聞いてなんとなく購入し、そこから気に入って使っています。

推しポイント

Conre V4 は、自作キーボードの終着地点、と言っても過言ではないキーボードだと思っています。
僕はかなり初期の頃から自作キーボードを触っていると思うのですが、Conre V4 を触って、「遂にここまで来たか…」と感動してしまいました。
自作キーボードに興味がある人に対して、一切の迷いなくおすすめできるキーボードだと思います。

半田付けがいらないという点も初心者向きですが、なにより「ケース」が良いです。
自作キーボードは、ケースのクオリティはあまり求められていない(というか自分も求めていなかった)のですが、Corne V4 はケースの密閉度が非常に高く、それによる「高級キーボード感」が、自作キーボードっぽさを打ち消してくれます。
持ち運びもいままではしていなかったのですが、このケースによる安心感があるため、持ち運びもしています。

滑り止めのポッチをはめる部分が凹んでいるのも、非常に良いです。なんならここが一番良いです。

V4 と V1 の違い

ケースの完成度の違いがわかるんじゃないかと思います…!

購入検討者へメッセージ

自作キーボードに対して、少しでも興味があったら即買った方が良いです。2台買った方が良いです。
というか、これはもはや自作キーボードではなく、組み立て式市販キーボードです。
今は品切れ中なので、発売したらすぐ買いましょう。

四之宮

使用しているキーボード

Helix rev3 LP と Corne V4 Chocolate

そのキーボードを選んだ理由

自作キーボードに沼ったのは、首や肩まわりに激痛がでるようになったのがきっかけです。
ペインクリニックに通いブロック注射をするほどひどい時期がありました。
分割キーボードにしてから痛みは徐々に改善していき、今では全く病院に行く必要がなくなりました。

最初は市販の分割キーボードから入ったのですが、「親指に enter キーある方がよくない?」とか「親指で押すキーもっと欲しい」などと思い始め、自作キーボードに入っていきました。
その後、いくつか試した結果、Helix の配列やキー数が自分にはあっていたようで数年間使っています。

そんな中、会社の先輩(上記の大久保さん)が Corne V4 を大絶賛していたので、興味を持ち購入しました。
数字行無しキーボードは初だったり、キーの数が変わったり、と大変な部分も多いですが徐々に慣れてきています。こちらもいいキーボードでした。
現状はまだ Helix の方が自分には合っているなと思っているのですが、しばらくは使い込んでみようと思っています。

推しポイント

キースイッチに kailh×COSMOX Wind Engine を採用しているため、ロープロファイルでありながら打鍵感がいいです。
Lofree Ghost を使用していたのですが、同じような打鍵感でより軽いものがあると知りこちらを愛用しています。
昔からキーの高さはロープロファイルが好きだけど打鍵感が好きではないため、ロープロファイルの使用を諦めていたので、Lofree FLOW の登場はとてもうれしかったです。
更に、キーキャップに静音化リングをつけたり、フォームを使ったり、マットを敷いたり、とロープロファイルながらも打鍵感や打鍵音をよくしようとしています。

あとは、qmk の LT 系の処理には満足できなかったので、process_record_user を使って、単推し/ 複合押し / 長押し の動きを自分好みにしました。
また、夏も近いので、Helix は涼し気なキーキャップにしてみました。

購入検討者へメッセージ

自作キーボードは沼ると散財するので注意が必要です。
Helix に落ち着くまでは、自作キーボードを結構転々としていたので結構散財しました。
キーボードに限った話しではありませんが、自分で設計しない限り100点のものが見つかることはほぼない、80点でほぼ最高点、というのを心がけるのが重要だと思います。

大庭

使用しているキーボード

Corneにアルミニウムケースを付けています!

普段は、キーボードの間にMagic Trackpadを置いています。
また、分離型のパームレストを使用しています。

そのキーボードを選んだ理由

Macのキーボードをずっと使っていたのですが、数字キーを押す際に手が大きく動くためホームポジションを見失ってしまうところと、「B」キーを押す際に左右どちらの手で押すか毎回変わるところが嫌で、縦3キーのcolumn staggered配列のCorneに辿り着きました。
この配列だと、どのキーも対応する指だけ動かせば良く、全てのキーがいずれか1つの指と対応するので、非常に気に入っています。

アルミニウムケースをつけている理由は特にないです。強いて言うなら、防御力が高くかっこいいからです。

推しポイント

キーの打鍵感には非常にこだわっています。
具体的には、「なめらかさ」と「ぐらつき」と「軽さ」です。
長い間、NovelKeys Box Creamのスプリングを35gに換えたものを使用していたのですが、HHKB Studioのキースイッチはさらに「静音性」があるので気に入っています。

購入検討者へメッセージ

最初から今の構成にたどり着いたように書いていますが、キーボードは3代目でキースイッチはそこそこの回数入れ替えています。
改善の日々で楽しいですが、お金が飛んでいきます。ただ、キーボードはずっと触っているものなので、小さな改善を大きく実感することができます。

熊谷

使用しているキーボード

Killer Whale

そのキーボードを選んだ理由

初めて自作キーボードを使い始めた理由は、一般的なキーボードの運指距離に不満があったからです。最初は右も左も分からなかったので使用者が身近にいたIrisを購入しました。親指側のキーが若干遠くて押しづらいこと以外には大きな不満も無かったのでつい最近までずっと使い続けていました。

tech.synchro-food.co.jp

今回紹介しているKiller Whaleに乗り換えたのは、とある競走馬擬人化スマホゲームがきっかけでした。そのゲーム内のキャラクターがトラックボール付きの分割キーボードを使っているのを見て、「マウス操作もキーボード上で完結したら全く手を動かさなくていいじゃん!」という天啓を得て、なんやかんやあってこのモデルを選びました。

キーの配置が直線的なIrisと違い弧を描いているので最初は小指付近のタイプミスが多かったですが、慣れると前よりも快適になったので概ね満足しています。ただトラックボールは精度的に完全にマウスの代替とはならず、結局普通のマウスも併用しています。(とはいえ役立つ場面もあるので利便性は確実に向上しているのを実感しています)

推しポイント

トラックボール以外にもホイール、ジョイスティック、トグルスイッチなどギミック増々で気分がアガります。 本当は打鍵感とか静音化とか突き詰めたい気持ちはあるのですが、手間や費用を考えるとなかなか行動に移せず‥‥(写真を見るとわかりますが、キーキャップの刻印や配色も適当です)

あとキーマッピングは細かい調整を重ねて日々改善を続けています。 例えばショートカットキーで多用するCtrlキーは組み合わせるキーとの距離を考慮して小指側と親指側の2つを配置したり、マウス操作時にEnterキーが押せると便利なシーンが多いのでメイン使いの右手側とは別に左手の別レイヤーにもEnterキーを用意したりなどです。

購入検討者へメッセージ

自作キーボードは確実にQoLを上げてくれますが、二度と普通のキーボードは使えない体になります。 今は快適でも将来もし使用中のキーボードが故障した時、その時も同じモデル(もしくは代替となり得るもの)が果たして入手可能なのかという不安と日々闘い続けることになります。

覚悟が出来た方から扉を開け次のステージへお進みください。

大津

使用しているキーボード

トラックボールを「人差し指側」または「親指側」に設置できる分割キーボードです
しかも左右1個ずつ設置可能!

そのキーボードを選んだ理由

マウスとキーボードの移動が面倒に感じ始めたので、トラックボール付きのキーボードを探していました。
そんな中で、左手にも右手にもトラックボールを設置できて、人差し指側と親指側で設置場所を選べるという選択肢の多さに惚れてTheEndpointを選びました。
実際、購入当初は左手人差し指側(白い端子が少し見えている部分)に設置していましたが、キー配置や操作性を考えているうちに、右手親指側に移動させました。
(実は追加のトラックボールユニットも購入したのですが、付属のネジを失くしてしまったので左手側にはまだつけられていません)

推しポイント

いわゆる50%キーボードですが、親指キーや人差し指キーが豊富なので、配列を色々試す事ができます。(色々試した結果不要になったキーはスイッチごと外しています)
また、トラックボールを使う際に不安になるのが親指の疲労ですが、このキーボードはトラックボールの設置場所が豊富なので、場所を変えたり左右で交互に使ったりなど自分にあった対策ができます。

購入検討者へメッセージ

「50%キーボードに挑戦してみたいけどキー数が少ないと不安」であったり、「トラックボール付きキーボードが欲しいけどトラックボールの場所がしっくりこなかったらどうしよう」という思いを抱えている方はぜひ購入してみてください!

まとめ

いかがだったでしょうか?
弊社エンジニアのキーボードについて、詳しい状況をお届けできたのではないでしょうか?
あまり偏りがなく、多種多様な結果になっているのかなと思います。
自作勢も結構な数がいるということで、新入社員や中途社員の方も、毎年何人かは自作に足を踏み入れています。

また、以前デスク環境に関する記事を公開していますので、キーボードだけではなくもう少し広い環境に興味がある場合は、下記からご確認いただければと思います。
tech.synchro-food.co.jp

Railsアプリケーションで使われているモデルをRSpecを使って自動的に機能ごと一覧化する

こんにちは。開発部の竹内です。
飲食店ドットコムモビマルといったサービスで Ruby on Rails での開発に携わっています。

Railsアプリケーションで使われているモデルをRSpecを使って自動的に機能ごと一覧化する対応を行いましたので、それについてお話ししたいと思います。

背景

コードを修正するにあたり、既存の機能に影響がないか・整合性があるかを調べるのは重要です。ですが私が所属する会員企画開発チームでは影響範囲の確認が漏れてバグや仕様上の不整合が発生することが多く、課題となっていました。
そのため機能ごとにどのモデルが使用されているかが分かるドキュメントを整備し、影響範囲の調査やサービスの理解に役立てようという提案がありました。ドキュメントは人力で作成していたのですが、作成に時間がかかりメンテナンスも大変であることから、自動的に出力する仕組みを作りたいと思いました。弊社には業務時間の10%でサービス改善のためにエンジニアの好きなことをできる制度があるので利用することにしました。

方針

Ruby の言語の特性上、静的に解析を行うのは難しそうです。
そのため動的に情報を得る方針を取りました。
弊社が運営しているモビマルでは Request Spec がほぼ全機能で書かれているのでこれを利用できないかと考えました。
カバレッジツールやプロファイラでも使用されている TracePoint を用いればメソッド呼び出しをフックできるので、これを記録すればどのモデルのメソッドが呼ばれているかが分かります。
また、どの機能が実行されているかは Active Support Instrumentation によって分かるためこれも利用することにしました。

実装

まず、TracePoint を使ってメソッドが呼ばれたクラスを記録するクラスを定義します。

# TracePoint を使ってメソッド呼び出すを記録するためのクラス
class Tracer
  attr_reader :used_classes

  # @param [Proc] filter_proc
  #  TracePoint で記録するメソッド呼び出しを絞り込むためのブロック
  #  引数に TracePoint を受け取り、true を返した場合のみ記録する
  def initialize(&filter_proc)
    @used_classes = Set.new
    @filter_proc = filter_proc || proc { true }
  end

  def trace(&block)
    tr = TracePoint.new(:call, :c_call) do |tp|
      next unless @filter_proc.call(tp)

      @used_classes << tp.self.class
      @used_classes << tp.self if tp.self.is_a?(Class) # クラスメソッドが呼ばれた場合
    rescue NoMethodError, ArgumentError # rubocop:disable Lint/SuppressedException
      # tp.self.class 取得時エラーになることがあるので、それは無視する
    end
    tr.enable(&block)
    self
  end
end

Tracer#trace をブロック付きで呼び出すと、ブロック中で呼ばれたメソッドのレシーバのクラス、またはレシーバがクラスの場合(クラスメソッドが呼ばれた場合)はレシーバ自身を @used_classes に記録します。
コンストラクタにブロックを渡すことができ、それによりクラスを記録するかどうかを判定します。こちらは後ほど説明します。

これを使ってアクションごとに使用されているモデルを取得します。

# frozen_string_literal: true

require 'csv'

MODEL_ANALYZER_TMP_FILE = 'coverage/model_analyzer_tmp.txt'
MODEL_ANALYZER_RESULT_FILE = 'coverage/model_analyzer.tsv'
MODEL_ANALYZER_EXCLUDED_CLASS_NAMES = Set.new([
  'ApplicationRecord',
])

RSpec.configure do |config|
  # テストスイート実行開始時、一時ファイルを削除する。存在しなければ何もしない
  config.before(:suite) do
    FileUtils.rm_f(MODEL_ANALYZER_TMP_FILE)
  end

  config.around do |ex|
    # controller の action が呼ばれた時にその controller と action を記録する
    controller = nil
    action = nil
    subscriber = ActiveSupport::Notifications.subscribe('process_action.action_controller') do |_, _, _, _, payload|
      controller = payload[:controller]
      action = payload[:action]
    end
    tracer = Tracer.new { RequestDurationMiddleware.in_request? }
    tracer.trace { ex.run }
    ActiveSupport::Notifications.unsubscribe(subscriber)

    # 使われたクラスを行ごとの JSON 形式で 一時ファイルに出力する
    # ディレクトリが存在しない場合は作成する
    FileUtils.mkdir_p(File.dirname(MODEL_ANALYZER_TMP_FILE))
    File.open(MODEL_ANALYZER_TMP_FILE, 'a') do |f|
      break unless controller && action

      f.puts JSON.generate({
        controller: controller,
        action: action,
        classes: tracer.used_classes.map { |klass|
          next if klass.name.nil? || MODEL_ANALYZER_EXCLUDED_CLASS_NAMES.include?(klass.name)

          source_location =
            begin
              Object.const_source_location(klass.name).first
            rescue NameError
              nil
            end

          # models 下で定義されたものが対象
          next unless source_location && source_location.start_with?('/mobimaru/rails/app/models/')

          klass.name
        }.compact,
      })
    end
  end
end

RSpec の config.around で、各ケース実行時に処理を追加します。
Active Support Instrumentation によりテスト対象のコントローラとアクションを取得します。
Tracer#trace に渡したブロック内でケースを実行し、使用されたクラスを記録します。
Tracer.new に渡しているブロック内の RequestDurationMiddleware.in_request? は実行タイミングがリクエスト中かを判定します。詳しくは後述します。
ケース実行後は、coverage/model_analyzer_tmp.txt にコントローラ・アクション・使用クラスの情報を行ごとの JSON 形式で追加します。この時 /mobimaru/rails/app/models/ 下以外のクラスやトップの ApplicationRecord は除外しています。

RequestDurationMiddleware の定義は以下となります。

# frozen_string_literal: true

# リクエスト中かを保持するためのミドルウェア
class RequestDurationMiddleware
  class << self
    def in_request?
      Thread.current[:request_duration_middleware_in_request]
    end
  end

  def initialize(app)
    @app = app
  end

  def call(env)
    self.in_request = true
    @app.call(env)
  ensure
    self.in_request = false
  end

  def in_request=(value)
    Thread.current[:request_duration_middleware_in_request] = value
  end
end

Rails.application.config.middleware.use RequestDurationMiddleware

Rack ミドルウェアでリクエスト中かを判定します。
RSpec のケース実行中には準備段階でのデータ作成などがあり、機能内で使われないモデルのメソッドが呼ばれることがあるので、除外するために行なっています。

全てのケース実行後は結果をファイルに出力します。

# ... 省略

RSpec.configure do |config|
  # ... 省略

  # テストスイート終了時、request spec で使用されたモデル一覧を集計する
  config.after(:suite) do
    controller_and_actions = {}

    File.open(MODEL_ANALYZER_TMP_FILE) do |f|
      f.each_line do |line|
        used = JSON.parse(line)
        key = [used['controller'], used['action']]
        (controller_and_actions[key] ||= Set.new).merge(used['classes'])
      end
    end

    # controller_and_actions から、| controller | action | used_classes(カンマ区切り) | の形式のTSVを作成する
    # controller と action の昇順でソートする
    File.open(MODEL_ANALYZER_RESULT_FILE, 'w') do |f|
      # ヘッダを出力
      f.puts(%w[controller action used_classes].to_csv(col_sep: "\t"))

      controller_and_actions.sort_by(&:first).each do |(controller, action), used_classes|
        f.puts([controller, action, used_classes.to_a.sort.join(',')].to_csv(col_sep: "\t"))
      end
    end
  end
end

一時ファイルに保存した内容を読み取った後、コントローラ・アクションごとに集計し、TSVで出力します。

実行

spec_helper.rbrequest_spec_model_analyzer_helper.rb を require し、RSpec を実行すると TSV が出力されます(一部抜粋)。

controller    action  used_classes
# ...
Home::MagazinesController   index   Contact,Magazine,MagazineCategory
# ...
Home::NewsController    show    Contact,News,NewsTarget,NewsTargetManage
# ...

まとめ

Request Spec を用い、機能ごとに使われているモデルを一覧化することができました。
これを使ってチーム内でドキュメントを作成するのに役立てています。
普段の Web 開発では馴染みの薄い TracePoint や Active Support Instrumentation を使うことができ、作っていて面白い取り組みになりました。
シンクロ・フードでは他にも自動化など改善の余地がある箇所がたくさんあるので、関わってみたい!という方をお待ちしています。

Aurora MySQL v3 (MySQL 8.0 互換) へのアップグレード対応

はじめまして、SRE チームの村山です。

今回、弊社の主要サービスで使用している Aurora MySQL のアップグレードを行いましたので、対応内容や得られた知見についてご紹介したいと思います。

背景

弊社で使用している Aurora は、2022 年 10 月に Aurora MySQL v1 (MySQL 5.6 互換) から Aurora MySQL v2 (MySQL 5.7 互換) へとアップグレードしております。その際の対応については、以下の記事をご覧ください。

tech.synchro-food.co.jp

その後も v2 を使用していましたが、2024 年 10 月 31 日 をもって v2 の標準サポートが終了になるため、Aurora MySQL v3 (MySQL 8.0 互換) へのアップグレードが必要になりました。

docs.aws.amazon.com

変更点の影響調査

アップグレードに向けて、まずはドキュメントを中心に変更点と影響範囲の調査を進めました。 これは SRE チームではなく、Web サービスの基盤部分を横断的に見るアプリケーション基盤チームを中心に実施して頂きました。

この中で対応が必要だと判断し、実施した主な内容を以下に記載します。

デフォルトの COLLATION が utf8mb4_0900_ai_ci になる

MySQL 8.0 では、utf8mb4 のデフォルトの COLLATION が utf8mb4_general_ci から utf8mb4_0900_ai_ci に変更になりました。

Each character set has a default collation. For example, the default collations for utf8mb4 and latin1 are utf8mb4_0900_ai_ci and latin1_swedish_ci, respectively.

これまで DB のテーブル定義 (マイグレーションファイル) には明示的に COLLATION を設定していなかったので、今後新規にテーブルを作成する際は COLLATE=utf8mb4_general_ci を設定するように開発ルールを変更しました。
DB アップグレードのタイミングに合わせ、既存のテーブルも COLLATE を設定するように変更しています。

FLOAT, DOUBLE, DECIMAL タイプのカラムに対して UNSIGNED 属性が非推奨になる

MySQL 8.0.17 で非推奨になったため、ALTER TABLE で対象のカラムから UNSIGNED 属性を削除するようにしました。対象のカラムが多いため、稼働中の DB には ALTER TABLE を適用せず、後述するクローンの DB にだけ適用するようにしています。詳しくは「アップグレード方法」の項に記載します。

As of MySQL 8.0.17, the UNSIGNED attribute is deprecated for columns of type FLOAT, DOUBLE, and DECIMAL (and any synonyms);

SQL_CALC_FOUND_ROWS, FOUND_ROWS() が非推奨になる

MySQL 8.0.17 で非推奨になったため、使用箇所を修正しました。アップグレード前に修正できる内容でしたので、事前に対応しています。

The SQL_CALC_FOUND_ROWS query modifier and accompanying FOUND_ROWS() function are deprecated as of MySQL 8.0.17;

utf8mb3 が非推奨になる

MySQL 8.0 で非推奨になったため、ALTER TABLE で対象のテーブル・カラムを utf8mb4 に変換しました。こちらも後述するクローンの DB にだけ適用するようにしています。

The utf8mb3 character set is deprecated. utf8mb3 remains supported for the lifetimes of the MySQL 8.0.x and following LTS release series, as well as in MySQL 8.0.

予約語利用箇所の対処

MySQL 8.0 より RANK や LEAD の単語が予約語に追加されました。

Reserved words are permitted as identifiers if you quote them as described in Section 11.2, “Schema Object Names”:

既存のテーブルに同名のカラムがあることを下記クエリで確認しましたが、アプリのライブラリにより引用符で囲む処理をするため、対応は不要の想定でした。

select table_schema, table_name, column_name 
from information_schema.columns 
where table_schema not in ('information_schema', 'mysql', 'sys') 
  and column_name in (select WORD from information_schema.KEYWORDS where reserved = 1)
and TABLE_NAME NOT IN ('ar_internal_metadata');

しかし、後の段階で一部のアプリではその処理がされず、エラーになることが判明したため、最終的にはカラム名を変更する対応をとりました。

GROUP BY 句での ASC/DESC の廃止

MySQL 8.0 より GROUP BY 句で ASC/DESC をつけることができなくなりました。後述する「テスト」の段階で、各サービスの開発担当者にて使用箇所を修正して頂きました。(昔からある一部の機能で利用されていました)

The deprecated ASC or DESC qualifiers for GROUP BY clauses are removed. Queries that previously relied on GROUP BY sorting may produce results that differ from previous MySQL versions. To produce a given sort order, provide an ORDER BY clause.

上記のほか、アップグレードにより変更されたパラメータグループの設定値の調査や検討なども行いました。

アップグレード方法

アップグレードの方法は、前回のメジャーバージョンアップ時と同じく、binlog レプリケーションを使った Blue-Green Deployment を採用しました。採用の理由は、実績があり手順が確立できていること、リリース当日のメンテナンス作業の時間を短くできること、切り戻しを容易にできることです。

具体的な手順は以下の記事を参考に検討しました。

aws.amazon.com

最初に検討した手順の大まかな流れは以下のとおりです。
※ テストで不備がみつかり修正したため、実際の手順とは異なります。詳しくは「アップグレード手順のテスト」の項に記載します

検討時のアップグレード手順

  1. 既存の Aurora MySQL v2 からクローンを作成する
  2. クローンを v3 へインプレースアップグレードする
  3. v2 から v3 へレプリケーションする
  4. v3 へ ALTER TABLE を実行する
  5. v2 から v3 へのレプリケーションを停止する
  6. アプリケーションの接続先を v3 へ切り替える
  7. v3 から v2 へレプリケーションする

手順 1 ~ 4 は事前に実施し、5 ~ 7 をリリース当日のメンテナンス作業時に実施します。そうすることで、リリース当日の作業時間を短くすることができます。
「変更点の影響調査」の項に記載した ALTER TABLE は、手順 4 のタイミングで v3 にだけ実行します。この時点でアプリケーションは v2 へ接続しているため、ALTER TABLE を安全に適用できます。
手順 7 は、アップグレード後に v2 へ切り戻せるようにするため実施します。

なお、RDS がサポートする Blue-Green Deployment の方法も候補にありましたが、切り戻しの容易さから前述の方法をとることにしました。

docs.aws.amazon.com

テスト

開発環境にてアップグレード手順のテストと、アップグレード後のアプリケーションのテストを行いました。

アップグレード手順のテスト

「アップグレード方法」の項で述べた手順で進めましたが、手順 4 の ALTER TABLE 実行後、v2 から v3 へのレプリケーションでエラーが発生してしまいました。

Column 17 of table 'テーブル名' cannot be converted from type 'varchar(765(bytes))' to type 'varchar(1020(bytes) utf8mb4)'

utf8mb4 へ変換する ALTER TABLE を v3 側だけに実行していたため、データ型の不一致が原因のようです。これを許容してレプリケーションできるようにするため、v3 側のパラメータグループで replica_type_conversions を設定するようにしました。
今回は source (v2) よりも target (v3) 側の型が大きいため、非不可逆変換を許可する ALL_NON_LOSSY の値を設定します。設定値の詳細は以下をご参照ください。

設定後、v2 から v3 へのレプリケーションがエラーにならないことを確認できました。
ただここで、手順 7 の v3 から v2 へのレプリケーションにおいても同じ問題に遭遇することが予想できました。型の大小関係が逆になるため、不可逆変換を許可する ALL_LOSSY を設定する必要があります。しかし、値が切り捨てられデータの不整合が生じる可能性があるため、他の方法を検討することにしました。

考えた方法は、v2 切り戻し用のクラスターを別に作成し、クラスター 3 台でレプリケーションする構成です。

実際のアップグレード手順

具体的な手順は以下になります。

  1. 既存の Aurora MySQL v2 からクローンを 2 つ作成する (v3 用と切り戻し用)
  2. 片方のクローンを v3 へインプレースアップグレードする
  3. v3 から切り戻し用の v2 へレプリケーションする
  4. v2 から v3 へレプリケーションする
  5. v3 へ ALTER TABLE を実行する
  6. v2 から v3 へのレプリケーションを停止する
  7. アプリケーションの接続先を v3 へ切り替える

切り戻し用の v2 には、v3 からのレプリケーションにより ALTER TABLE が反映されているため、型の不一致が起こりません。そのため、ALL_LOSSY を設定することなくレプリケーションすることができました。
また副産物として、v3 から切り戻し用の v2 へのレプリケーションを事前に実施できるため、リリース当日の作業を 1 つ減らすこともできています。

アプリケーションのテスト

上記の手順で開発環境を v3 へアップグレードした後、各サービスの開発担当者にてアプリケーションのテストを実施して頂きました。テストコードや、前回のバージョンアップ時に作成した実行計画を比較する機能を活用してテストをしています。

テストにより修正した内容の 1 つに、パラメータグループの optimizer_switch の変更があります。
実行結果が変わってしまうクエリが見つかったため、実行計画を確認するなどして調査を進めました。試行錯誤の結果、セミジョイン最適化の duplicateweedout の動きが原因ではないかと考え、開発環境で optimizer_switch の duplicateweedout を off にしたところ、問題が解消しました。
バグレポートをみると、duplicateweedout 含めセミジョイン関連のバグがいくつか報告されていました。ただ今回のテストでは、セミジョインに起因する他の問題は起こらなかったので、duplicateweedout だけ off にする方針にしています。

その他にも問題のあった箇所は、各開発者にて修正をして頂きました。

リリース

テストが完了した後、リリースへ向けた対応を進めました。
まずリリースの前週に「アップグレード手順のテスト」の項に記載した手順の 5 までを実施します。v3 へのインプレースアップグレードは約 15 分、ALTER TABLE の実行は約 15 分で完了し、作業全体は 70 分程で完了しました。

そしてリリース当日のメンテナンス中に手順 6, 7 を実施しました。正確にはレプリケーションの同期確認やアプリのデプロイ作業も含むため、メンテナンス時間は 2 時間設けていましたが、ほぼ予定時間内に作業を完了することができました。
ちなみにレプリケーションの同期確認では、各 DB のレコード件数が一致することの確認をしています。テーブルごとに SELECT COUNT をしているのですが、処理を並列化することにより 10 分程で完了しました。 直列で行なうと 1 時間以上かかることがわかっていたため、作業時間の短縮に繋がりました。

リリース後の修正

v3 へのアップグレード後、v2 へ切り戻すほどの致命的なエラーはありませんでしたが、いくつか問題が発生しました。
ひとつは、パフォーマンスが極端に劣化した SQL の問題です。実行時間が著しく伸び、DB の負荷も増大したため急ぎ修正する必要がありました。
その主な原因は、「テスト」の項でも述べたセミジョインによるものでした。
MySQL 8.0 では内部のオプティマイザに変更が多く、サブクエリを使う SQL において v2 ではセミジョインが使われなくても、v3 だとセミジョインが使われるケースが多くあります。
オプティマイザの不具合なのか、セミジョインを使うと速度が大きく劣化するようなケースにもかかわらず、セミジョインを利用してしまうようなこともありました。
具体的な例を示します。以下は同一の SQL について、セミジョインを on(デフォルト) または off にしたときの、EXPLAIN FORMAT=TREE の実行結果になります。(最初の 2 行のみ抜粋)

optimizer_switch: semijoin=on のケース

-> Aggregate: avg((db_1.table_1.column_1 / (case when (((to_days(db_1.table_2.column_1) - to_days(db_1.table_2.column_2)) / 30) < 1) then 1 else round(((to_days(db_1.table_2.column_1) - to_days(db_1.table_2.column_2)) / 30),0) end)))  (cost=7839673608.28 rows=6050893371)
    -> Nested loop semijoin  (cost=7234584271.20 rows=6050893371)

optimizer_switch: semijoin=off のケース

-> Aggregate: avg((db_1.table_1.column_1 / (case when (((to_days(db_1.table_2.column_1) - to_days(db_1.table_2.column_2)) / 30) < 1) then 1 else round(((to_days(db_1.table_2.column_1) - to_days(db_1.table_2.column_2)) / 30),0) end)))  (cost=302837.05 rows=618503)
    -> Nested loop inner join  (cost=240986.72 rows=618503)

セミジョインを利用する場合だと、rows=6050893371 という内部テーブルができてしまっています。実際にこれが原因で、SQL の実行時間が著しく伸びてしまっていました。
このようなケースに対処するためには、SQL を書き換えたりして局所的にセミジョインを回避する必要がありました。オプティマイズヒントを使って無効化する方法も把握していましたが、フレームワーク経由で SQL が実行される都合上、SQL を書き換えることで対応し、解決することができました。

他には、バッチ処理で以下のエラーが発生した問題がありました。

The table '/rdsdbdata/tmp/#sqlxxx_xxx' is full

これは MySQL 8.0 から一時テーブルの動作が変更されており、それに起因するものでした。リソースの制限値を超えてしまったことによるエラーのため、パラメータグループから temptable_max_ram と temptable_max_mmap の値を調整することで解決することができました。

docs.aws.amazon.com

その他にもいくつかの問題が発生しましたが、内容に応じて各サービスの開発者や SRE チームにて修正を行っております。

リリース後のパフォーマンス

v3 へのアップグレード後、主要サービスの全体的なレスポンスタイムは大きな変化がありませんでした。
先に述べたパフォーマンスが劣化する SQL の影響があり、リリース日は悪化していましたが、修正によりその後は元に近い値となっています。
現状、v3 の仕様や追加機能にあわせたチューニングはしていないので、大きく改善することもなく変化は少なかったのかなと考えています。

レスポンスタイムの状況

まとめ

各チームの開発者とチーム横断で協力して対応した結果、無事に Aurora MySQL v3 へのアップグレードを完了することができました。
本記事が Aurora アップグレードへ取り組む方にとって、少しでもお役に立てれば幸いです。