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

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

OpenSearch の導入による検索システム改善のための認証・認可設計と負荷検証結果

SRE チームの @fohte です。普段は音楽ゲームを嗜んでいます。

シンクロ・フードでは、弊社が提供する求人飲食店ドットコムの求人検索にて、MySQL から OpenSearch を使った全文検索に切り替えました。
本記事では、その OpenSearch を導入する上で障壁となったことや、それらの解決策について紹介します。

なお、本記事では、インフラ面で課題となった以下のテーマについて扱います。

  • 認証・認可の設計
  • 負荷検証によるスペックや設定の検討

アプリケーション面での MySQL と OpenSearch のデータ同期や検索の工夫については、以下の記事をご覧ください。

tech.synchro-food.co.jp

OpenSearch とは

OpenSearch は、オープンソースの全文検索エンジンで、Elasticsearch からフォークしたものです。
MySQL をはじめとする RDB に比べると、大量のデータを高速かつ柔軟に検索できることが期待できます。

弊社のインフラ環境では AWS をメインで利用しており、OpenSearch のインフラとして Amazon OpenSearch Service を採用しました。

Amazon OpenSearch Service は AWS が提供するフルマネージドサービスです。
冗長化やリソースの最適化といったスケーリング、バージョン更新などによるデプロイの自動化ができ、運用コストを大幅に削減されることが期待できます。

OpenSearch 導入の背景

OpenSearch の導入以前の求人検索では、MySQL で検索条件を SQL に盛り込んで検索していました。
しかし、求人の掲載数が増えるにつれて、レスポンスタイムの増加や MySQL の負荷の増大が目立つようになりました。
そこで、大量のデータを高速かつ柔軟に検索できる全文検索エンジンを導入することとなり、OpenSearch を採用しました。

認証・認可の設計

シンクロ・フードの SRE チームでは、最小権限の原則に従って権限制御をするというポリシーを策定しています。
それに伴い、OpenSearch でも、以下のように権限を最小化することを設計のゴールとしました。

  • アプリケーションからは特定の index のみの read/write 権限を与える
  • アプリケーション開発者には、テスト環境でのみ権限を与え、本番環境では何も権限を与えない
  • インフラ運用者にはフル権限 (admin 権限) を与える

Amazon OpenSearch Service での認証・認可方式

Amazon OpenSearch Service のデフォルトでは、IAM によるドメインアクセスポリシーで制御します。

また、FGAC (Fine-grained access control、きめ細やかなアクセスコントロール) と呼ばれる設定 1 が存在します。
この設定を有効化すると、OpenSearch 自体に (IAM とは異なる) ロールを作成し、OpenSearch 側で認可できます。

FGAC を有効化しない場合、OpenSearch 側でロールを作成できず、ドメインアクセスポリシーだけで制御する必要があります。

しかし、ドメインアクセスポリシーは以下の制約があり、うまく権限制御が行えませんでした。

  • 後述するダッシュボードへのログインの許可・拒否は制御できるが、ログイン後の操作の権限制御ができない
  • OpenSearch を VPC 内に入れる時、ドメインポリシーで IP ベースでの制御ができない

これらを実現できる手段が FGAC であったため、今回は FGAC を有効化しています。

Cognito を利用してダッシュボードにログインする

Amazon OpenSearch Service では、Kibana からフォークされたダッシュボード (OpenSearch-Dashboards) が利用できます。

ダッシュボードでは、ブラウザー上で任意の API リクエストを発行できたり、クラスターや index の情報を閲覧・編集できます。

そのため、インフラ運用者にのみ編集権限を与え、アプリケーション開発者には閲覧権限のみを与える等の制御が必要です。

上述の通り、FGAC を有効化すると OpenSearch にロールを作成して認可ができます。
社内のユーザーがブラウザーからアクセスした際、そのロールにマッピングするため、Cognito を利用しています。

具体的な認証・認可のフローは以下の図の通りです。

flowchart TD

server_admins[インフラ運用者]
non_server_admins[アプリ開発者]
server_admins & non_server_admins -- "ダッシュボードへのアクセス" --> cognito_user_pool


subgraph AWS
  cognito_admin["Cognito インフラ運用者用ロール (IAM)"]
  cognito_default["Cognito アプリ開発者用ロール (IAM)"]

  subgraph opensearch-domain["OpenSearch ドメイン"]
    opensearch[(OpenSearch)]

    admin_opensearch_role["管理者ロール"]
  end

  subgraph Cognito
    cognito_user_pool[Cognito User Pool]
    cognito_id_pool[Cognito Identity Pool]

    cognito_id_pool --> is_admin{{インフラ運用者か?}} -- Yes --> cognito_admin
    is_admin -- No --> cognito_default
  end

   cognito_admin --> admin_opensearch_role -- "フル権限" --> opensearch
end

cognito_user_pool --> cognito_id_pool

cognito_default --> reject(("拒否"))

SigV4 署名を用いてアプリケーションから認証する

一方で、アプリケーションからの認証は、Cognito を介さずに SigV4 署名 を用いて直接 OpenSearch にアクセスしています。

弊社のアプリケーションは AWS の EC2 あるいは ECS で動かしており、IAM ロールでの認証が可能だったため、Cognito は介していません。

以下に示す図は、アプリケーションからの認証・認可のフローです。

flowchart TD

subgraph AWS
  subgraph ecs_task[ECS タスク]
    app[アプリケーション]
    ecs_task_role["ECS タスクロール (IAM)"]
    app --- ecs_task_role
  end

  subgraph opensearch-domain["OpenSearch ドメイン"]
    opensearch[(OpenSearch)]
    app_opensearch_role["アプリケーション用ロール<br>(OpenSearch)"]
  end
  ecs_task_role --> app_opensearch_role -- "限定された権限" --> opensearch
end

このように、IAM ロールを OpenSearch のロールにマッピングしています。
そのロールで、特定のリソースのみアプリケーションからアクセスできるように制御しています。

IAM 認証では、具体的には OpenSearch への API リクエスト時に SigV4 で署名しています。
今回は Ruby on Rails を利用しているアプリケーションのため、faraday_middleware-aws-sigv4 gem を使い、以下のように実装しています。

client = OpenSearch::Client.new(url: ENV['OPENSEARCH_URL']) do |f|
  f.request(
    :aws_sigv4,
    service: 'es',
    region: 'ap-northeast-1',

    # ECS を利用しているため ECS のタスクロールで認証する
    credentials_provider: Aws::ECSCredentials.new,
  )
end

負荷検証によるスペックの検討

弊社では OpenSearch の利用が初めてだったため、必要なリソース量が肌感覚として不明でした。
そこで負荷検証を行い、スペックを検討することにしました。

負荷検証の方法

今回の負荷検証の目的とゴールとして、大きく以下の 2 つの観点がありました。

  • OpenSearch 側の負荷が高くなることによって、アプリケーションのレスポンスタイムが劣化しない
  • index 更新時の限界性能を把握し、アプリケーション側で更新間隔の調整等を行う

これをもとに、実際の運用を想定した並列かつ高頻度なリクエストや、限界性能を確かめるリクエストで検証しました。

なお前提として、今回の検索システムでは index に格納するドキュメントは以下の仕様となっています。

  • 40,000 件のドキュメント
  • ドキュメントには検索対象のデータを入れている
    • 1 ドキュメントあたりのデータ量は平均 10 kB

検証内容は以下の通りです。

  • 検索リクエストの投入 (最大秒間 100 リクエスト x 10 秒間)
  • _bulk API による更新リクエストの投入 (最大 250 ドキュメント x 10 並列)

以上の検証により、大きなものとして 2 つの課題が見つかりました。

メモリ不足による GC でレスポンスが遅延する

検証中、リクエスト量が増加すると、1 秒前後で返していたレスポンスが瞬間的に 30 秒前後まで劣化することがありました。
このとき OpenSearch のログ 2 を確認すると、以下のように GC が走ったログがいくつか出力されていました。

[2022-12-07T09:15:13,391][WARN ][o.o.m.j.JvmGcMonitorService] [c69ee8a1e7009c34e215e585444667c8] [gc][young][1320808][1808] duration [3.2s], collections [1]__PATH__[4.1s], total [3.2s]__PATH__[12.4m], memory [952.3mb]->[504.8mb]__PATH__[2gb], all_pools {[young] [464mb]->[1mb]__PATH__[0b]}{[old] [485.3mb]->[499.8mb]__PATH__[2gb]}{[survivor] [3mb]->[4mb]__PATH__[0b]}
[2022-12-07T09:15:14,200][WARN ][o.o.m.j.JvmGcMonitorService] [c69ee8a1e7009c34e215e585444667c8] [gc][1320808] overhead, spent [3.2s] collecting in the last [4.1s]

これのログとリクエスト時刻のタイミングを照らし合わせると、この GC が起因してレスポンスタイムが遅延していると推測できました。
つまり、メモリ不足が根本的な要因と考えられるため、インスタンスタイプを変更し、メモリ量を増やしました。

再検証してみると GC の発生頻度が減り、この問題は解消できました。

余談: Auto-Tune による JVM ヒープサイズの変更の検討

今回はメモリの増強という手段を取りましたが、Amazon OpenSearch Service では Auto-Tune という機能があります。
これは、JVM ヒープサイズの変更をはじめとする、各種リソースに関する設定を自動的に変更する機能です。

docs.aws.amazon.com

例えば、Amazon OpenSearch Service での JVM に割り当てられているヒープサイズは、デフォルトでインスタンスのメモリの 50 % です。
Auto-Tune によりこのヒープサイズは変更できるため、GC の発生頻度を減らすことが期待できます。

ただし、ドキュメントに記載されている通り、JVM ヒープサイズの変更は無停止ではできません。
メンテナンスウィンドウとして設定した時間帯に Blue/Green デプロイメントされ、設定が変更されます。
つまり、負荷に応じた動的なヒープサイズ変更は期待できませんでした。

このことから、今回は Auto-Tune を使わず、単にメモリを増やす方針で GC の問題を解決することにしました。

refresh_interval は短くしないほうが良い

refresh_interval は、ドキュメントの追加や更新を反映するために、OpenSearch が自動的に refresh する間隔の設定です。

今回直面した課題では、サービスの要件として「極力早く反映してほしい」というものがありました。
OpenSearch のデフォルトでは refresh_interval は 1 秒となっており、今回も 1 秒で設定していたため、その要件自体は満たせます。
しかし、AWS のナレッジセンターでも、パフォーマンス向上のためには refresh_interval を 60 秒以上にすることを解決の一案として挙げています。

aws.amazon.com

実際、refresh_interval を 1 秒 or 60 秒に設定し検証したところ、60 秒のほうが全体的にレスポンスタイムが速く、極端に遅くなることも少ないことが分かりました。

refresh_interval を変えたときのレスポンスタイムの違い

以上の検証やナレッジセンターを参照すると、refresh_interval は短いほど負荷が高いため、極力長くしたほうが良いといえます。

ただ、今回は以下の理由により、refresh_interval は 1 秒のままにしています。

  • 前述したようにスペックを上げ、大きなパフォーマンス劣化に繋がらなくなったため
  • 要件として検索の反映ラグは少ないほうがよいため

今回の学びとして、検索までの反映のラグの少なさとレスポンスタイムの遅延のトレードオフを理解し、refresh_interval を調整する必要があるという知見を得ました。

最後に

今回は、OpenSearch を導入する上で障壁となった認証・認可の設計や、負荷検証時に発覚した課題とその解決策について紹介しました。

今回の OpenSearch 導入での知見を活かし、弊社が提供する求人検索以外のサービスでの検索システムにも OpenSearch を導入する予定です。
検索がより快適になるよう、SRE チームとアプリケーション開発チームが協同して導入を進めていきます。


  1. 実体はおそらく OpenSearch の security plugin と思われます。
  2. Amazon OpenSearch Service ではエラーログやスローログなどが見られますが、そのうちのエラーログ (ES_APPLICATION_LOGS) に出力されていました。

CI を Drone から GitHub Actions に移行しました

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

シンクロ・フードではCIにDroneを使用していましたが、2022年の11月ごろにGitHub Actionsへの移行を完了しました。
今回はその経緯やDroneとの比較についてお話します。

なぜ移行したのか

シンクロ・フードでは2018年ごろからDroneを使用していました。
導入時の記事があるので良かったらご覧ください。

tech.synchro-food.co.jp

その後、2020年9月ごろにGitHub Enterprise Server(以下GHES)でもGitHub Actionsが使えるようになりました。
(シンクロ・フードではGHESというオンプレ版を使っています。GitHub Enterprise Cloudと比べて機能追加が少し遅い特徴があります。)

Droneは課題を感じている部分もあり、GitHub Actionsの方が以下のような点で優れていたため移行しました。

GitHub Actionsのワークフロー構文の方が表現力が高い

CIを主に実行しているリポジトリはMonorepoで運用1しているので、各アプリケーションごとに自動テストを実行する必要がありました。
DroneではMatrix Buildsという記法でディレクトリごとに実行するワークフローを制御していましたが、GitHub Actionsではディレクトリごとにトリガーを設定することで読みやすい構造にできる点が魅力でした。

以下にDroneとGitHub Actionsのサンプルコードを記載します。(簡略化のため実際は必要な記述を省略していますがご了承ください)

Droneの場合

この後詳しく解説しますが、まずは全体のコードをご覧ください。

# 1. matrix(並列実行)を定義する
matrix:
  include:
    - SUB_REPOSITORY: ''
      RUBY_VERSION: 2.4.1
      APPLY_SEED: false
    - SUB_REPOSITORY: apps/rails-application1
      RUBY_VERSION: 2.6.0
      APPLY_SEED: true
    - SUB_REPOSITORY: apps/rails-application2
      RUBY_VERSION: 2.7.1
      APPLY_SEED: false
    - SUB_REPOSITORY: apps/rails-application3
      RUBY_VERSION: 2.7.1
      APPLY_SEED: false

# 2. あとでYAMLのエイリアスとして使えるようにアンカーを定義する
references:
  if-root-matrix: &if-root-matrix
    matrix:
      SUB_REPOSITORY: ''
  if-app-matrix: &if-app-matrix
    matrix:
      exclude: SUB_REPOSITORY: ''

# 3. CIで実行するジョブを定義する
pipeline:
  run-tests:
    commands:
      - bundle exec rspec
    when:
      <<: *if-rails-app-matrix
  
  lint:
    commands:
      - yarn run lint
    when:
      << *if-root-matrix

  apply-seed-each-app:
    commands:
      - bundle exec rails db:seed
    when:
      matrix:
        APPLY_SEED: true

1. matrix(並列実行)を定義する

以下の部分ではMonorepo内のアプリケーションのディレクトリをSUB_REPOSITORYとして定義しています。
SUB_REPOSITORY: ''はMonorepoのルート階層を意味していて、全アプリケーションに適用する処理はこのときに実行するようにしています。
RUBY_VERSIONAPPLY_SEEDは各アプリケーションごとのCI実行に必要な情報を定義しています。
この場合だと、ルート階層での実行+アプリケーション3つそれぞれで実行ということになります。

matrix:
  include:
    - SUB_REPOSITORY: ''
      RUBY_VERSION: 2.4.1
      APPLY_SEED: false
    - SUB_REPOSITORY: apps/rails-application1
      RUBY_VERSION: 2.6.0
      APPLY_SEED: true
    - SUB_REPOSITORY: apps/rails-application2
      RUBY_VERSION: 2.7.1
      APPLY_SEED: false
    - SUB_REPOSITORY: apps/rails-application3
      RUBY_VERSION: 2.7.1
      APPLY_SEED: false

2. あとでYAMLのエイリアスとして使えるようにアンカーを定義する

以下の部分はGitHub Actionsでいうところのon.*.pathsの設定にあたるもので、Monorepoのルート階層で実行するかアプリケーションごとの階層で実行するかを判別する設定です。これをyamlのアンカーとして定義してテストやlintの処理でエイリアスとして参照します。

references:
  if-root-matrix: &if-root-matrix
    matrix:
      SUB_REPOSITORY: ''
  if-app-matrix: &if-app-matrix
    matrix:
      exclude: SUB_REPOSITORY: ''

3. CIで実行するジョブを定義する

以下は実際にCIで実行するテストやlintの設定です。
lintはMonorepo内の全アプリケーション一律で同じものを適用しているのでルート階層で実行しています。
apply-seed-each-appというのは、特定のアプリケーションのみで実行したい処理の設定で、APPLY_SEEDをtrueにしているアプリケーションでのみ実行されます。

pipeline:
  run-tests:
    commands:
      - bundle exec rspec
    when:
      <<: *if-rails-app-matrix
  
  lint:
    commands:
      - yarn run lint
    when:
      << *if-root-matrix

  apply-seed-each-app:
    commands:
      - bundle exec rails db:seed
    when:
      matrix:
        APPLY_SEED: true

Matrix Buildsは各アプリケーションごとに何を実行するのかが一見しただけではわからないですし、アプリケーションごとに独自の処理が増えるとより複雑になってしまうという点がつらかったです。

GitHub Actionsの場合

今度はGitHub Actionsのサンプルコードです。
Monorepoのルート階層で実行する処理はroot.ymlに、各アプリケーションごとに実行する処理は個別にymlファイルを用意します。今回はrails-application1の設定ファイルを例としてrails-application1.ymlに記載します。

GitHub Actionsのコードについては詳しい説明は割愛しますが、ひと目見ただけでも各アプリケーションで実行する内容がなんとなく読み取れるのではないでしょうか。

name: CI

on: push

jobs:
  lint:
    steps:
      - name: Run Lint
        run: yarn run lint
name: rails-application1

on:
  push:
    paths:
      - 'apps/rails-application1'
      
jobs:
  run-tests:
    steps:
      - name: RSpec
        run: bundle exec rspec
  apply-seed:
    steps:
      - name: Apply seed
        run: bundle exec rails db:seed

コミュニティが大きくドキュメントや知見が豊富

他にもGitHub Actionsの方が公式のドキュメントが豊富で、知見がインターネット上に多いという点が魅力でした。
また自作のワークフローが多く公開されているという点も良いです。

GitHubとの連携機能が充実している

GitHubが提供しているだけあり、GitHubとの連携機能が豊富です。

  • GitHub上のPull RequestからCIの実行結果やログをスムーズに確認できる。
  • ワークフローのトリガーが豊富。
  • GitHubのトークンを発行する手間なく参照できる。

移行方法

GHESではCIが実行されるインスタンスを自前でホスティングする必要があることからはじめにインフラ構築をしました。
インフラ周りのお話についてはまた別途記事を公開する予定のためここでは割愛させていただきます。

その後、Droneで実行しているRuboCopやRSpecなどをGitHub Actionsに移行しました。
DroneからGitHub Actionsへの書き換えは技術的に難しいところはありませんが、GitHub Actionsの構文に慣れていなかったので勉強しながら書き換えていきました。

2020年10月ごろにActionsを導入し、Droneからの移行が完全に完了したのは2022年の11月ごろでした。2

所感

GitHub Actionsに移行してしばらく経ちましたが、やはりDroneよりも資料が豊富で開発しやすいと感じています。
また、GitHub Actionsでは複数のリポジトリでワークフローを再利用3しやすい点も便利だと思いました。
Droneでは共通化したい処理をDockerイメージにすることで共通化していたため、より手軽になったと思います。

一方でGitHub Actionsそのものというわけではないですが、GHESによる課題も少しありました。
というのもGHESはGitHub Enterprise Cloudと比べて機能追加が少し遅いです。当時はbundle installyarn installの結果をキャッシュするactions/cacheがGHESでは使えなかったため自作する必要がありました。(現在はGHESでも使えるようになっています)

まとめ

DroneからGitHub Actionsへの移行について紹介しました。
GitHub Actionsへの移行によって、よりCIの開発がしやすい環境になったと言えます。

シンクロ・フードではCIチームというCIに関する開発をするチームを設置していて、各開発者が所属するメインのチームと兼任する形で所属しています。
CIチームのメンバーも徐々に増えてきているため、今後はよりCI環境の改善を加速させていきたいと思います。


最後に、エンジニア募集のお知らせです! シンクロ・フードでは、まだまだ技術的な課題やチャレンジが残っています。 ぜひ、一緒に改善していきましょう!

また、飲食店ドットコム、求人飲食店ドットコムを始めとしたサービスの開発・改善も、どんどん進めております。 ご興味のある方は、以下の採用サイトからご連絡ください!

www.synchro-food.co.jp


  1. Monorepoへの移行についてエンジニアブログのこちらの記事にまとめています。こちらも良ければぜひご覧ください。
  2. 2年ほどかかっていますがCIの開発に割り当てられる時間があまり多くないというところによるものです。また、その期間は移行だけでなく並行してCIの速度改善などの取り組みをしていました。
  3. Composite Action

求人飲食店ドットコムの求人検索に OpenSearch を導入した話

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

シンクロ・フードでは弊社のサービスである求人飲食店ドットコムの求人検索にOpenSearchを導入しました。
どのように導入したか、また、導入した結果を紹介します。

opensearch.org

はじめに

今回、求人検索にOpenSearchを導入した目的は検索の高速化です。
また、今後求人の掲載数が増加したときにも検索性能を劣化させないことも求められていました。
そのため、MySQLよりもOpenSearchの方が大量のデータ検索に向いていることも採用した理由にあります。

同様の検索エンジンとしては、OpenSearch以外にもApache SolrやElasticsearch等が選択肢としてありました。
しかし、今回は主に運用の負荷を減らすため、AWSのマネージドサービスも提供されているOpenSearchを採用しました。

以前の求人検索

求人の検索に使用される情報はMySQLに保存されており、それに対して検索が行われていました。
そのため、OpenSearchで検索を行える状態にするためにはMySQLからOpenSearchへデータを同期する必要がありました。
また、これらのデータは複数のアプリケーションから更新されるため、このことを念頭に、OpenSearchへのデータの同期方法を考えました。

MySQLからOpenSearchへのデータの同期方法

データの同期を行うシステムの構成図は以下のようになっています。

データ同期システム構成図

各アプリケーションではデータが更新される箇所で更新が行われたレコードの主キーをSNSへ送信します。
rakeタスクではSQSをポーリングし、受け取った主キーを元にMySQLからインデックスの作成に必要な情報を取得しOpenSearchに対して更新を行います。

当初は定期的に更新を実行することを考えていましたが、よりリアルタイムに更新を行うためにこの方法を採用しました。
また、この方法ではデータの更新が行われるアプリケーションではメッセージをSNSへ送信するだけであるため、定期的に更新する場合と同様に更新処理の実装は一箇所で済み、実際の更新処理について各アプリケーションは意識しなくていいというメリットがあります。

OpenSearchによる検索

求人の検索は各種条件を元にOpenSearchに対して行います。
OpenSearch からは一致したものの主キーを取得し、MySQLから実際の表示に必要なデータを取得しています。

また、既存のフリーワード検索ではLIKEによる部分一致を行っており、この検索をOpenSearchで再現する必要がありました。
これはN-gram Tokenizerを使用し、検索クエリに match_phrase句(通常使用される match 句と異なり、文字列の出現する順番が考慮される)を使用することで実現できました。

また、icu_normalizerという文字の正規化を行うフィルターを使用することで、既存ではできなかった丸数字(①、…)と数字(1、…)、半角カナ(ア、…)と全角カナ(ア、…)等で区別せず検索できるようになりました。

テスト中に発生した問題

インデックスの更新に関して、ローカルでの開発中には起きず、テスト環境だけで起きた問題が主に2つありました。

1つ目はアプリケーションで行った変更が反映されていない古いデータでOpenSearchのインデックスが更新される問題でした。
具体的には、rakeタスクでデータを再取得するときに古いデータが取得され、そのデータで更新がされている状態でした。
この原因としてはトランザクションの中でSNSへのメッセージ送信を行っていることでした。
これはアプリケーションで自動的に張られるトランザクションについて把握できていなかったためにそのような実装にしてしまっていました。
この問題が確認できていないときは、たまたま、rakeタスクで更新処理が起きるまでの間にコミットされていて更新が成功している状態でした。トランザクションを明示的に張り、コミット後にSNSへメッセージを送信するように修正しました。

2つ目は、更新の負荷によって429エラーが起きていた問題です。
429エラーは、OpenSearchに対するリクエストが増え、処理が追いつかない場合に起きるようです。
この問題はローカルでOpenSearchのDockerイメージを利用して開発をしているときには確認できず、テスト環境でAmazon OpenSearch Serviceを利用して初めて確認できました。
今回は偶発的に(多数の更新が重なり)エラーが起きた場合には更新処理のリトライで対処をしました。
rake タスクでエラーが起きた場合に SQS へメッセージを戻して、再度処理を行っています。
その他ではバッチ処理で大量の更新をする場合もエラーが起きるため、こちらは個別に対処しました。
ここでのバッチ処理ではリアルタイム性は比較的求められていなかったため、更新を小分けに行いスリープを挟むことで負荷を軽減させています。

aws.amazon.com

導入した結果

1月12日に無事にリリースが完了しました。
以下はフリーワード検索と業態(イタリアン)の検索のレスポンスタイムの推移です。

レスポンスタイム

リリースの前後で、フリーワード検索で、平均510ms減り、業態(イタリアン)の検索では平均171ms増えました。
フリーワード検索では、テスト環境でも同様にフリーワード検索が速くなり、それ以外では既存と同等もしくは遅くなっているという結果になっており想定内ではありました。

この原因は単純な一致の検索だとMySQLで十分に速いというのとOpenSearchではアプリケーションとはREST APIでやり取りをするためMySQLよりも通信に時間がかかるためだと考えています。
これを改善するため、今後OpenSearchに対する複数のリクエストをまとめる等の対策を行っていきます。

まとめ

求人検索の高速化を目的にOpenSearchを導入しました。
更新方法をリアルタイムに近づけるためにSNS/SQSを使用したり、既存のLIKE検索を再現するために工夫したりしました。また、テスト環境で起きた問題に対処しました。

今回の目的である検索の高速化については、フリーワード検索ではレスポンスが速くなり、大きかったMySQLへの負荷も軽減できました。しかし、他の検索条件ではまだ効果が出ていません。
これは今後リクエストをまとめる等の対策で改善していきたいと考えています。

OpenSearchについては今回初めて導入したということで、分かっていないことも多いです。
今後、知見を深めより活用していきたいと思います。


最後に、エンジニア募集のお知らせです!
シンクロ・フードでは、まだまだ技術的な課題やチャレンジが残っています。
ぜひ、一緒に改善していきましょう!

また、飲食店ドットコム、求人飲食店ドットコムを始めとしたサービスの開発・改善も、どんどん進めております。
ご興味のある方は、以下の採用サイトからご連絡ください! www.synchro-food.co.jp

Redash と Google スプレッドシートを連携してデータ活用をちょっとだけ改善した話

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

1年ほど前からMリーグ視聴きっかけで、麻雀に再度はまり毎朝早起きして雀魂で麻雀を打ってから仕事を始めるという健康的な生活を送っています。

今回はシンクロ・フードでのデータ活用周りでちょっとだけ改善したことの紹介をしたいと思います。

はじめに

シンクロ・フードでは 2016 年に Redash を導入することで以下のことを実現しました。

  • サービス指標の可視化
  • サービスの重要指標と連動したSlack通知
  • データ抽出作業の自動化

redash.io

導入前はエンジニアがディレクターから依頼されて SQL を作成することが多かったのですが、導入してからはディレクターが SQL を作成してデータを抽出するようになり、スムーズにデータ分析ができるようになりました。

(複雑な SQL でデータ抽出する場合はエンジニアに作成依頼が来ることもあります)

Redash ではサービスのデータを記録している Aurora (個人情報を潰した状態)とログ情報やネイティブアプリのイベントを記録している BigQuery からデータを抽出できるようになっており、サービスの分析に活用しています。

わきあがる要望

Redash を導入したことでデータ分析活動が活発に行われるようになりましたが、それに伴いこういうことができないかという要望もあがるようになってきました。 (非常に良いことですよね)

上がった要望としては以下のようなものがありました。

  1. daily、weekly、monthly のその時点での数値を見たい
  2. 複数の指標を一つの表やグラフでまとめて見たい
  3. 数値を使って分析する際の CSV ダウンロードを自動化したい

1. daily、weekly、monthly のその時点での数値を見たい

シンクロ・フードが運営している飲食店専門の求人サイトである求人@飲食店.COM を例に出して説明します。

求職者の応募数については応募日時を持っているため、そのようなデータを抽出することは簡単ですが、掲載店舗数になると掲載期間としてデータを持っているため、抽出する SQL が複雑化して時間もかかるようになってしまいます。

(SQL によっては実行時間が長すぎて結果が返ってこないことも...)

求人@飲食店.COM では掲載期間中は複数の職種を募集することができ、職種の追加、取り下げもできるようになっていますが、その時の操作自体を記録していないと、後から見た場合にその時点で掲載されていたかどうかの判断がつかなくなり、その時点の掲載していた職種数を抽出できない問題があります。 データによっては記録していて抽出できるものと記録していなくて抽出できないものがある状態でした。

2. 複数の指標を一つの表やグラフでまとめて見たい

Redash ではダッシュボード機能があり関連している指標をまとめて見ることができますが、あくまで 1 つの指標単位での表やグラフのため、関連している指標をまとめて見て分析したいという要望があがりました。

3. 数値を使って分析する際の CSV ダウンロードを自動化したい

Redash では SQL の実行結果を JSON や CSV 形式でダウンロードする機能があり、ダウンロードした数値を利用してシミュレーションなどをしていました。 ただ、確認したいときに都度ダウンロードして数値を反映させることが手間なため、自動化できないかという要望があがりました。

Google スプレッドシートとの連携による改善

上記のような要望を満たす方法がないかと検討した結果、Redash の SQL の実行結果を定期的にスプレッドシートに転記するようにすることで今回の 3 つの要望については解決できると考えました。

2 と 3 の要望については、Google スプレッドシートに転記するだけで、あとは Google スプレッドシート側で参照設定を設定するだけで複数の指標を 1 つの表やグラフとして表示できるようになります。

(1 つのスプレッドシートに、複数の自動転記用のシートを作成して各指標を転記して、表示用のシート側で参照設定する形で実現しています。)

1 の要望については、定期的に転記するだけだと毎日上書きされていってしまうため、Google Apps Script(GAS)を利用して Redash からの転記後に更に別のシートに転記することで、その時点の状態が分かる情報を持っていないデータに関しても、時点情報を記録することができるようになります。

システム化

方針は決まりましたがセキュリティの問題があり、Redash と Google スプレッドシートだけでは連携することはできません。 そのため、連携するためのシステムを作成することにしました。 社内で利用する簡易的なシステムということで CTO の大久保がその時に興味を持っていた Racket で実装しました。

racket-lang.org

システム構成は、以下の図になります。

Redash 連携システム構成図

連携プログラムが実行された後の処理の流れは以下になります。

  1. 設定用のスプレッドシートから Redash の API の URL 、転記先のスプレッドシートのURL、転記先のシート名の3項目を取得する
  2. Redash の API を実行して JSON 形式で SQL の実行結果を取得する
  3. 転記先のスプレッドシートの対象のシートに転記する

まとめ

今回の要望を対応したことで、分析する人の手間が減り、今まで確認することが難しかったデータも簡単に確認できるようになり、データをさらに活用できる状態になりました。

非常に良いことずくめに見えるのですが、1点だけ問題がありました。

最初に想定していた以上に活用された結果、設定用のスプレッドシートの記入で不備があることでエラーが発生して、調査の時間を取られることが増えてしまいました。 また、プログラムが Racket で実装されていることからプログラムを読める人も少なく CTO 自身が対応することも度々...

この問題についても先日、エンジニアの新卒研修の一環でこのシステムを Rails でリプレースすることで解消しています。

(設定でもバリデーションをかけることができるようになりました。)


最後に、エンジニア募集のお知らせです!
シンクロ・フードでは、今回のちょっとした改善に限らず、まだまだ技術的な課題やチャレンジが残っています。
ぜひ、一緒に改善していきましょう!

また、飲食店ドットコムを始めとしたサービスの開発・改善も、どんどん進めております。
ご興味のある方は、以下の採用サイトからご連絡ください! www.synchro-food.co.jp

Aurora MySQL v2 (MySQL 5.7 互換) へのアップグレードとスムーズなテスト

こんにちは、シンクロ・フードの日下です。
最近は、健康のためラジオ体操を毎日の習慣にできるよう取り組んでいます。

シンクロ・フードでは、2022/10/05 の 05:00 から 08:00 の間、サービス停止を伴うサイトメンテナンスを実施し、Aurora MySQL の v1 (MySQL 5.6 互換) から v2 (MySQL 5.7 互換) へのアップグレードを行いました。 本記事では、アップグレードのために行った変更、またスムーズなテストのために工夫したことを記します。

なぜアップグレードしたのか

なぜなら、Aurora MySQL v1 のサポート終了が発表されたからです。

aws.amazon.com

飲食店ドットコムを始め主要なサービスで Aurora MySQL v1 をメインの DB として使用しているため、対応が必要でした。

アプリケーション側の変更

シンクロ・フードでは、サーバーサイドでは以下のフレームワーク・言語を使用しており、それぞれ DB 接続用のライブラリの更新が必要か確認しました。

  • Ruby on Rails (Ruby)
  • Seasar2 (Java)

Ruby on Rails で使用している Mysql2 については、既に MySQL 5.7 と互換性のあるバージョンを使用しているため対応が不要でした。

Seasar2 で使用している MySQL Connector/J は、MySQL 5.7 未対応の 5.1.23 を使用していたためバージョンアップしています。
バージョンアップするバージョンは、MySQL 5.6、5.7 の両方をサポートする 8.0.27 を選択しました。
なぜなら、MySQL 5.6 がサポートされているため、Aurora MySQL v2 へのアップグレードより先にバージョンアップできるからです。
先にバージョンアップを行うことで、Aurora MySQL v2 へのアップグレード当日の手順を減らせます。
また、Aurora MySQL v2 へのアップグレードで問題が発生した場合も、素早く v1 へロールバックできます。

インフラ側の変更

インフラ側の変更で 1 番悩んだ点は、アップグレード方法の検討でした。
検討していた時 ANDPAD 様の以下の記事を読み、アップグレード当日の作業を最小にできる事からシンクロ・フードでも binlog レプリケーションを使った Blue-Green Deployment を採用しました。

tech.andpad.co.jp

CNAME を使ったエンドポイントの指定や、ROW_FORMAT の変更に伴う長い DDL の実行など、とても参考にさせていただきました。
ANDPAD 様、素敵な記事をありがとうございます!

また、具体的な手順については、以下の AWS の記事を参考にしています。

aws.amazon.com

アップグレード後は、Aurora MySQL v2 から v1 への逆レプリケーションを設定して、万が一の際は素早くロールバックできるようにしています。
新しいバージョンから古いバージョンへのレプリケーションは公式にサポートされていませんが、事前にテスト用の環境で問題が発生しないことを確認しました。

テスト

シンクロ・フードでは、社員しかアクセスできないテスト用の環境を用意しており、開発中の機能のテストを行っています。
Aurora MySQL v2 についても、テスト用の環境でアップグレードを行いテストしました。

テストではエラーにならないことの他に、パフォーマンスが劣化しないことを重視しました。
しかし、パフォーマンスのテストと言っても、ただページへアクセスするだけでは Aurora MySQL v1 と比較して劣化しているかどうかの判断は難しいです。
また、影響する範囲が広く、SQL を 1 つ 1 つ確認するのは時間がかかりすぎると考えました。

そこで、HTTP リクエストを実際に送信し、ログから実行された SELECT 文を取得して、Aurora MySQL v1 と v2 で比較するテスト用の機能を作成しました。
以下の図が、機能の概要です。

テスト用の機能

  1. まず、テストしたいアプリケーションに対して HTTP リクエストを送信します。
  2. HTTP リクエストの送信時刻とレスポンスが返ってきた時刻で処理時間を計算し、処理時間中のログ (general log) を Aurora MySQL v2 から取得します。
  3. 取得したログから SELECT 文を抜き出し、Aurora MySQL v1 と v2 に対して実行して結果を取得します。
    SELECT 文の EXPLAIN も実行して、実行計画も同時に取得します。

実際に、テスト用の機能で表示される SELECT 文の比較結果の一例が以下になります (お見せできない部分は黒塗りしています)。

SELECT 文の比較結果

Aurora MySQL v1 と v2 の実行計画を表示しており、差がある箇所は赤色の太文字で強調しています。
また、Aurora MySQL v1 と v2 での SELECT 文の実行時間の差を表示、参考できるようにしました。

EXPLAIN の実行計画を比較することで、DB の負荷などを考慮せずにパフォーマンスが劣化しているか確認しやすくなりました。
さらに、テスト用の環境でも Aurora MySQL v2 から v1 へ逆レプリケーションを実施していたため、同じデータ量で比較ができています。

このテスト用の機能と EXPLAIN の見方を各サービスの開発者へ案内することで、スムーズにテストを行えました。
テスト方法はどんなアップグレードでも悩む点ですが、今回はユニークな方法で上手くテストできたと考えています。

リリース

テストが完了し、2022/10/05 の 05:00 から 08:00 のサイトメンテナンスでアップグレードを行いました。
アップグレードについては、Blue-Green Deployment によって Aurora MySQL v1 から v2 へ接続先を切り替えるだけということもあり、無事メンテナンス時間内で終えることができました。

アップグレードしてから、もうすぐで 1 ヶ月経ちますが現在まで大きな障害は発生していません。

まとめ

無事に Aurora MySQL v1 から v2 へのアップグレードを行えました。
本記事が、アップグレードへ取り組んでいる方や取り組もうとしている方の、お役に立てれば幸いです。


最後に、エンジニア募集のお知らせです!
シンクロ・フードでは、今回の Aurora MySQL アップグレードに限らず、まだまだ技術的な課題やチャレンジが残っています。
ぜひ、一緒に改善していきましょう!

また、飲食店ドットコムを始めとしたサービスの開発・改善も、どんどん進めております。
ご興味のある方は、以下の採用サイトからご連絡ください! www.synchro-food.co.jp

2022年8月時点のシンクロ・フードの技術構成

シンクロ・フードの大久保です。
弊社は、今年も新しい新卒エンジニアメンバーを迎え、研修を終え配属部門で業務を開始する時期となりました。 新卒では継続してメンバーを迎えていくつもりなのですが、中途でのエンジニアも引き続き加わっていただきたいと思っています。

以前、2016年に技術構成を紹介するブログを書いたのですが、あれから構成もかなり変わったので、2022年8月時点での代表的な技術構成をお伝えする記事を書こうかなと思います。 求人票にも書いたりしていますが、ここではより詳しく説明をしたいと思います.

言語・フレームワーク

  • Ruby & Ruby on Rails
  • Java & Seasar2
  • React, TypeScript

現時点で、サーバサイドにてメインで使っているものは、Ruby & Ruby on Rails です。 ただし、弊社は昔からJava系技術を使っていたため、古いシステムについては Java & Seasar2 で稼働しているところもあり、こちらは開発がある毎にRailsにリプレイスするか、大物に関しては計画リプレイスを進めている形です。

3年以内にユーザーが触る主要サービスはリプレイスする計画ですので、絶賛エンジニア募集中です。 リプレイスに対しては前向きですが、それでも Javaコードはまだあるので、正直なところ、Javaも書くと思って頂いたほうが良いかなと思います。

自動テストは、Rails は RSpec、Javaは JUnit ですが、Javaはかなりカバレッジは低いです。自動テストは無いと言って良いくらい…。Railsは、プロジェクトによってカバレッジに差があるのですが、少しずつ上がってきていると思います。

フロントエンドに関しては、Reactを使うことが多いです。現在は TypeScript も使っていますが、古い React コードは TypeScript を使っていないものもありますし、別の型システム(Flow)を使っている箇所もあります。
尚、小さい動きしか必要ではない部分に関しては、jQuery を使っているところもありますし、まれに Stimulus を使っている部分もあります。
学習コストを最適化する目的で、Vue や Angular は使わないことになっています。
(とはいえ、Reactへの統一を決める前に導入された Vue や AngularJS、Knockout.js も残っています…)
SPAや複雑な動きが必要、というときは React、それ以外は jQuery, Stimulus のような使い分けかなと思います。

DB

  • MySQL
  • Solr
  • BigQuery

メインは、MySQLで、AWSのAuroraを使っています。今は 5.6 から 5.7 へのバージョンアップを取り組み中です。Solrはデータ量が多い箇所の検索で一部利用、BigQueryは主に大量の記録データを入れておく場所、として使っています。 さらに、現在 AWS の OpenSearch の導入を進めています。

開発環境

特に希望がなければ MacBook M1 Pro チップ、メモリ32GBのものを貸与します。 開発環境は Rails に関してはほぼ Docker で構築はできてるのですが、スピードを重視して 部分的にはローカルで動かすところがある、という状況です。 おそらく、Rails を Mac で開発している方々と同じ悩み(Docker が遅い問題)を抱えています。 Java は…私は Eclipse で開発していますが、このへんはエンジニアの好みで自由に開発環境を作っている印象です。

インフラ

  • AWS (ECS, EC2, Aurora, Lambda, S3, SQS...etc)
  • さくらクラウド
  • Nginx & unicorn
  • Apache & Tomcat
  • SendGrid & postfix
  • Datadog & nagios
  • Scutum

クラウドはAWSがメインで、一部さくらクラウドを使っています。AWSは様々なサービスを使っているので挙げきれませんが、色々なものを必要に応じて使っています。マルチアカウントで運用していることは、別の記事に記載しております。

tech.synchro-food.co.jp

新しいインフラを構築する際は Terraform, ansible を利用してコードとして記述して、コードレビューを実施した後に本番に適用するようにしています。
まだレガシーなインフラについてはコード管理できていないところもありますが、今後、コード化していきたいと思っています。

Rails は、ECS 上で Nginx, unicorn がコンテナで動作しています。新規のRails アプリケーションについては、Fargate を利用するようにしています。Java 系システムは EC2 上の CentOS にて Apache と Tomcat を動かして動作しています。

メール配信は、 Rails系システムに関しては SendGrid 、Java系システムは postfix 、キャンペーンメールに関しては 自社で制作したJavaアプリケーションとpostfixを組み合わせて送っています。SendGridのキャンペーンメール機能で送らないのは、コストで折り合いがつかなかったためです。自作故に低コスト運用できている状態です。

監視は nagios, Datadog, CloudWatch アラームを利用しています。

WAFは、かなり前から Scutum を使っています。AWS WAF への乗り換えも検討したことがありますが、比較検討の結果 Scutum を使い続けることになりました。Scutumは、価格も良心的ですし防御のクオリティも高くとても良いサービスだと思います。

CI/CD

CI に関しては、現在 GitHubActions, Drone を利用しています。 CircleCI を使っていない理由は、GitHubEnterpriseServer を使っているために CircleCI も Enterprise 版を利用する必要があり、費用面で折り合いがつかずに Drone で CI を構築、そして GitHubActions が Enterprise 版にも来たので昨年導入した、という流れです。 Drone については、以下の記事を御覧ください。

tech.synchro-food.co.jp

尚、現在は Drone は縮退方向で、GitHub Actions が主に使われています。Drone は必要十分に動いていましたが、GitHub Actions のほうが世界的にユーザー数が多く、調査がしやすい点が最大の理由です。

CI では RSpec, Rubocop, prettier, Querly などを動かし、linter 系の結果は reviewdog を利用して GitHub の PullRequest にコメントするようにしています。 CI の改善についてはチームを横断したCIチームを組織し、そのチームにて改善を進める運用となっています。

CD に関しては サービスによって方法は違ったりするのですが、jenkins を使ってデプロイを管理することが多いです。こちらは別の方法に変えたいなあと思いつつ、まだできていない状況です。

モバイルアプリ

Webアプリだけでなく、モバイルアプリも開発しています。こちらも絶賛エンジニア募集中です。
ネイティブ開発は iOS, Android ともに実施しているのですが、それぞれざくっと説明します。

iOS

Swift5系、Xcode13系 で開発しており、サポートバージョンは 現時点で iOS13以降。二世代前までサポートする方針です。
アーキテクチャは MVC、 View は Storyboard + Xib で作成しており、まだ SwiftUI は使っていません。
その他には色々なライブラリを使っていますが、それは割愛します。

Android

kotlin で書いており、現在 MVC から MVVM に移行中です。
なるべく GoogleやJetpackが提供するものに寄せる形にリファクタリングしようとしており、Rx から Kotlin Coroutine, LiveData/Flow に変えていっています。
こちらもライブラリなどは割愛します。

コミュニケーション

バージョン管理は git 、レビューは GitHubEnterprise、 チャットツールは slack 、ドキュメンテーションは esa 、課題管理は redmine です。
エンジニアはリモート勤務推奨なので、基本的にはテキストコミュニケーション中心ですが、議論的なものはサクッと通話しちゃおう、というノリです。
エンジニアは業務的に必要な際に出社できればOK(通勤圏内に住む必要はあります)、というルールで、週にxx回出社する、のようなルールは存在しません。


以上、大まかに技術構成をまとめてみました。

2016年と比べるとインフラは大きく変わりました。Javaは想定より残っていますね…。 今後も着実に一つ一つ改善を積み上げていきますので、開発を一緒にしてくれる方をお待ちしております。

Railsでの経験がなくても、Webアプリケーション開発の経験があれば大歓迎ですので、シンクロ・フードに興味があれば、気軽に連絡ください!

www.synchro-food.co.jp

社内独自の Rails のコーディング規約のチェックを自動化した話

こんにちは。サービスチームの寸田です。

今回は、Rails アプリケーション開発における社内独自のコーディング規約のチェックを、Querly と reviewdog によって自動化した話をします。

サービス開発で抱える課題

サービス開発をしていると、社内独自のコーディング規約が日々追加・更新されていきます。こうしたルールの追加・変更のたびに社内のエンジニアに周知し、ルールが守られているかどうかをチェックする必要があります。こうした社内独自のコーディング規約は、これまでコードレビューによって人力でチェックしてきました。しかし、エンジニアの人数も増え、開発するサービスも増えてきた中で、社内独自のコーディング規約を守っていくことが難しくなってきました。

そこで弊社では Ruby のコードチェックのツールである Querly と、コードチェックの結果をGitHub 等のホスティングサービス上でレビューコメントとして投稿するツールである reviewdog を使うことで、この課題を解決しました。

Querlyとは

Querly は、Ruby のコードチェックをしてくれるツールです。独自のDSLを使ってルールを記述でき、パターンマッチによってそのルールに違反する箇所に対して警告してくれます。これにより、開発者・レビュワーが社内独自のコーディング規約を認識していなくても、Querly に警告されることでルールを認識することができるようになります。

ルールの記述例

例えば、「default_scope の使用を禁止する」というルールは次のように書くことができます。

rules:
  - id: synchro-food.model.default_scope
    pattern: default_scope
    message: default_scope は原則として禁止です。 scope を使用してください。

実行例

$ bundle exec querly check <チェックしたいファイルのパス>
<違反したファイルパスと行数> default_scope -> { where(demo: false).order(created_at: :desc) } default_scope は原則として禁止です。 scope を使用してください。 (synchro-food.model.default_scope)

RuboCop ではダメなのか?

Ruby のコードチェックツールとしては、RuboCop が有名で、すでに弊社でもコードチェックに RuboCop を使っています。 RuboCop は一般的な Ruby のコーディング規約に準拠しているかどうかをチェックするには役立ちますが、独自のルールを追加するのが容易でなく、社内独自のコーディング規約のチェックを RuboCop で行うのは現実的ではありません。一方 Querly では、独自のルールを RuboCop よりも簡単に追加することができます。

Querly だけでは解決できない課題

Querly の導入で、社内独自のコーディング規約のチェックはできるようになりました。しかし、手元で Querly を実行してもらう必要があるため、実際に社内ルールが守られているかどうかについては担保できません。実際に運用するには、Querly のチェックを自動化する必要があります。

そこで使用するのが reviewdog というツールです。

reviewdogとは

reviewdogは、コードチェックツールの結果を GitHub 等のホスティングサービス上でレビューコメントとして投稿するツールです。GitHubで使った場合、以下のような感じでレビューコメントとして表示してくれます。

f:id:synchro-food:20200612161620p:plain

PR上にレビューコメントとして表示することで、警告が目に見えてわかるようになり、自然とチェック・修正を行えるようになります。

reviewdog は GitHub Actions や Circle CI と連携して使うことができます。各CIサービスでの使い方は公式GitHubをご覧ください。なお、弊社では Drone を使っていますが、Drone でも簡単に連携できます。Drone については過去に書いた記事をご覧ください。

導入してよかったこと

コーディング規約のチェックを自動化したことで、コードレビューの質を担保できるようになりました。また、コーディング規約についてはQuerlyで検証されているので、レビュワーは他のレビューに専念できるようになり、レビュー工数を削減することができるようになりました。

今後の課題

Querly ではすべてのルールを一律にチェックするため、推奨レベルのルールについても常に reviewdog に警告されてしまいます。また、ルールが適用されるまえに書かれたコードについてもチェックするため、既存のコードに対して警告されることがあります。こうした reviewdog からの警告により、PR上の他のコメントが埋もれてしまうことがありました。

これらの課題については、Querly に推奨レベルのルールを設定しない、リファクタデーを設けて既存のコードにルールを適用する、といった方法で対処しています。また、reviewdog からの警告の表示を他のコメントと変える、Github Checks を利用する、といったことも検討しています。

最後に

Querly、reviewdogを使って、社内独自のコーディング規約のチェックを自動化した話をしました。

課題もありますが、工数をかけずに社内のコードの品質を担保できるようになりました。