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

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

求人飲食店ドットコムの求人検索に 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を使って、社内独自のコーディング規約のチェックを自動化した話をしました。

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

AWS Organizations で複数の AWS アカウントを良い感じに運用する

SRE チームの @fohte です。

今回は、社内インフラ環境の AWS 移行を進めるにあたって課題となった、AWS の権限分割を目的とした AWS Organizations を用いた複数 AWS アカウントの設計や運用の話をします。

AWS Organizations とは

AWS Organizations は、複数の AWS アカウントを一元管理するサービスです。

AWS Organizations には、以下のようなメリットがあります。

  • AWS アカウントの作成・管理が容易になる
  • AWS アカウントごとにポリシーを設定できる
  • 請求が一元管理できる

複数の AWS アカウントを簡単に管理できるようになるため、IAM だけの権限制御と比べ権限管理が圧倒的に楽になります。 Organizations 下であれば AWS アカウントの作成もコマンド一発で行えるようになる 1 ため、気軽に AWS アカウントを分割できるようになります。

また、請求情報もアカウントごとに分離されるため、ネットワークの通信量やリソースの利用料金などがアカウントごとに把握できるようになります。

AWS Organizations 導入前の解決したかった課題

今回 AWS Organizations を導入した最大の目的は、AWS の権限分割です。

最近まで取り組んでいた「社内テスト環境や社内ステージング環境のクラウド移行 (= AWS 移行) プロジェクト」2 の中で、AWS の権限を環境単位で分割したいという要件がありました。 具体的には、「テスト環境は開発者が自由に触れるようにしたいけど、ステージング環境や本番環境は一部制限したい」といった要件です。

実は AWS Organizations 導入前も、本番環境用アカウントと本番以外の環境用アカウントの 2 つのアカウントで運用していました。 しかし、これらのアカウントをテスト環境やステージング環境を構築することは、以下の観点から難しいと判断しました。

  • 本番以外の環境用アカウントは主に開発環境用や AWS の検証環境といった用途で利用している (この上に構築するとどれが何のためのリソースなのか分かりにくくなる)
  • 本番環境用アカウントに構築すると「テスト環境は開発者が自由に触れるようにしたい」という要件が満たせない

そこで、これらの課題を解決できるものとして、AWS Organizations を用いた複数 AWS アカウント運用を選択しました。

AWS アカウント設計

AWS Organizations の導入にあたって、初期段階で悩むことになるのがアカウントの分割単位です。

紆余曲折を経て、現在は以下のようなアカウント構成にしています。

f:id:Fohte:20191203162618p:plain
AWS アカウントの全体像

各アカウントの役割

AWS Organizations Master アカウント

このアカウントではアプリケーション用のリソースは作らず、AWS Organizations の子アカウント管理や CloudTrail での Organizations 下アカウントの監査といった Organizations の管理を行います。

Custodian アカウント

AWS Organizations で複数アカウント運用をするにあたって特に重要となる、IAM ユーザーを一元管理するためのアカウントです。

AWS アカウントが複数個ある場合、アカウントごとに IAM ユーザーを作るのは大変ですし、権限管理も煩雑になります。

そこで、IAM ユーザーはこのアカウントのみ作成するようにし、他アカウントに用意した IAM ロールに Assume Role で切り替えて目的のアカウントを切り替えるようにしています。 これについての詳細は後述します。

アプリケーション用アカウント

前述した「テスト環境は開発者が自由に触れるようにしたいけど、ステージング環境や本番環境は一部制限したい」という要件を満たすために、環境別に用意しているアカウントです。

テスト環境用アカウントでは開発者にも強い権限を設定し、ステージング環境や本番環境用アカウントは弱い権限に設定しています。

また、開発環境用アカウント (Developer Tools) には、開発環境で利用する S3 バケットなどを置いています。

環境間共通アカウント

アプリケーション用アカウントは環境ごとに分離していますが、AMI や ECR リポジトリなどは環境間の差異をなるべく減らすためにも環境間で共有したく、そのようなリソースの置き場として共通アカウントを用意しています。

共通アカウントは目的ごとに作成しており、Operator Tools アカウントに AMI、CD アカウント内に ECR リポジトリや CodeBuild プロジェクトを置いています。

開発者個人用アカウント

開発者が AWS を学習したり検証するための、個別に独立した AWS アカウントを用意しています。 必要に応じて作成しており、開発者はこのアカウントを自由に利用できるようにしています。

Assume Role で簡単にアカウントを切り替える

Assume Role は、任意の IAM ロールを一時的に引き受ける (= assume) ための API です。 「引き受ける」というと想像がつきにくいかもしれませんが、「IAM ロールを移動する (切り替える)」といったイメージです。

以下に示す図のように、Custodian アカウントの IAM ユーザーにユーザー設定のための最小限の権限と Assume Role だけの権限を持たせ、他アカウントでの操作は常に Assume Role で引き受けたロール経由で行うようにしています。

f:id:Fohte:20191204084947p:plain
各アカウントに Assume Role する図

これにより、IAM ユーザーをアカウントごとに用意する必要なく、アカウントの IAM ロールごとに権限制御ができるようになり、権限の分離と簡潔な運用が実現できています。

MFA (多段階認証) を必須化してセキュリティを強固にする

万が一 Custodian アカウントの IAM ユーザーの認証情報が流出してしまう事故が発生したとき、その認証情報だけで他のアカウントのロールに Assume Role できてしまうのは非常に危険です。

その対策として、Assume Role の実行には、AuthyGoogle Authenticator 等を用いた MFA を必須にしています。

具体的には、以下のような IAM ポリシーを全ての IAM ユーザーに設定しています。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::*:role/<role-name>",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    }
  ]
}

AWS マネジメントコンソールで Assume Role する

マネジメントコンソールの場合、ヘッダーのユーザーメニュー内にある「スイッチロール」から Assume Role が可能です。

このスイッチロール機能では、過去にスイッチしたロールが 5 件まで表示されるのですが、切り替え先のロールが 6 件以上になってくると大変です。 そういったときは、AWS Extend Switch Roles というブラウザ拡張機能 (Chrome, Firefox) が非常に便利です。 この拡張機能では切り替え先のロールを ~/.aws/config に似たコードで管理でき、マネジメントコンソール上でプロファイル名でのインクリメンタルサーチも可能です。

AWS CLI で Assume Role する

AWS CLI では、aws sts assume-role コマンドで取得できる一時的な認証情報を利用して各種コマンドを実行する必要があります。

これを毎回行うのは手間なので、何らかの Assume Role のラッパー CLI ツールを使うと便利です。 シンクロ・フード社内では、設定やインストールが簡単なことと、MFA に対応していることから aws-vault を推奨しています。

aws-vault では、以下のようなコマンドで MFA を行いつつ Assume Role をした上でコマンドを実行できます。

$ aws-vault exec testing -- aws s3 ...
Enter token for arn:aws:iam::<account-id>:mfa/<username>: (MFA トークンを入力する)

AWS Organizations 導入後の課題

ここまで設計について紹介しましたが、運用が始まってからいくつか課題が出てきました。

全ては紹介しきれませんが、中でも特に課題だと感じている点は、サービスごとのアカウント分離ができていないことです。

前述したように、現在はサービスごとには分離せず、環境ごとにしか分離していません。 しかし、シンクロ・フードではサービスごとのチーム分割が進んでおり、各チームに権限を委譲していくことが重要なテーマになってきています。

そこで、この課題を解決するために、AWS アカウントの分割を始めとした権限分割に今後取り組んでいく予定です。

まとめ

本記事では、シンクロ・フードでの AWS Organizations を用いた複数 AWS アカウントの設計から、Assume Role を駆使した権限管理について紹介しました。

いくつか課題はありますが、AWS Organizations の運用で大きな問題は起こっておらず、安定した運用が実現できていると感じています。

「AWS アカウントを分割したい」、「分割しているけど権限管理がつらい」といった同じ悩みを持った方々の参考になれば幸いです。


  1. aws organizations create-account で AWS アカウントが作成できます。 (create-account — AWS CLI 1.16.298 Command Reference)

  2. このプロジェクトは無事完結しました。プロジェクトの全容は別途記事を公開する予定です。

リポジトリを横断したコンポーネント(Sass)管理を行う

シンクロ・フードの四之宮です。
前回ブログを書いてからかなり時間が経ってしまいましたが、その間に自作キーボードデビューしました。
Mint60, レツプリ, Helix, Fortitude60, ErgoDash を作りました。
社内の自作キーボード勢もかなりいるので、いずれ社内のキーボード紹介があるかもしれません?
(僕は作ったことないですが、Iris, Corne, Lily58 勢もいます)

さて、挨拶はこのくらいにして、今回は「リポジトリを横断したコンポーネント(Sass)管理を行う」について書いていきます。
コンポーネントと言い切ってしまっていますが、Utilityなどの普通のclassも可能です。
「リポジトリをまたいで CSS (Sass) を共通化したい」そんな要望を満たすための方法になります。
タイトルだけだとよくわからないと思いますので、弊社の現状をふまえて説明していきたいと思います。

運営サービスと以前の状態

弊社では、複数のサービスを運営しています。
代表的なものとして、飲食店.COM, 求人@飲食店.COM があります。
そして、基本的にサイトによってリポジトリ(プロジェクト)が別になっています。
そのため、飲食店.COMで作成したSassファイルを、求人@飲食店.COMで使用する際は、ファイルコピーで対応していました。

しかし、リポジトリが別でも同じCSSを使用したいということもあるのではないでしょうか?
例えば、Utility系のクラスや、LPのような説明ページ、社内でよく使用するパーツなど、こういったものはリポジトリ毎にコードを書くことになってしまっていました。
また、とあるサイトでコンポーネント化を行うと、他サイトでも同じものを使用したいという要望がよくあがってきました。

更にそういった状況の中、CSS設計やコンポーネントというものを本格的に考え始めたのですが、この状態はとても厳しいものでした。
会社でコンポーネントなどの共通CSSを管理していくには、なにか方法を考える必要がありました。

対応案

これらの問題を考える際、対応案は下記の2つありました。

  • git submodule
  • npm package

検討した結果、 npm package で運用していくことに決めました。

npm package の選定理由

運用のしやすさという観点で、 npm package に軍配が上がりました。
選定理由は下記の通りです。

git submodule で運用する場合は、そのものの仕組みを知る必要がありました。
弊社では git submodule を使用している箇所はなかったので、チームメンバーへの普及や学習といったところのコストが大きいと判断しました。
それに比べ、 npm package での運用は、意識することが少なく、すぐに運用できると判断しました。
開発時も、 yarn install するだけでいいという手軽さです。
あとは、import の方法を伝えればそれで終わりです。
使用側が意識することが少ないというのが、大きなメリットでした。

また、後述しますが、「共通CSSリポジトリも変更しつつ、サービスのアプリケーション側のリポジトリも変更する」という場合もあるかと思います。
これも、npm package であれば、解決することができます。
これも選定の理由の1つでした。

導入までの流れ

共通化するCSSを管理するリポジトリの作成

まずは、共通管理するCSSを新規のGitリポジトリで作成します。
その際、 package.json は下記のような記述にします。
重要なのは repository のところです。
(common-css という名前で作成した例)
必要に応じていろいろ追加してください。
ちなみに、弊社では、このリポジトリだけで Sass のコンパイルが行えるような設定まで行いました。

{
  "name": "common-css",
  "version": "1.0.0",
  "description": "サービス間での共通cssをまとめたもの",
  "main": "index.js",
  "private": true,
  "repository": {
    "type": "git",
    "url": "git+<repository-url>"
  }
}

その後、master にマージしたら、git tag でタグ付けし、git push --tags で tag を push します。
記念すべき v1.0.0 の誕生です。

共通CSSを使用する方法

上記の例だと、common-css を使用するために必要な手順を説明していきます。

まず package.json に下記を追記します。

"devDependencies": {
  "common-css": "git+<repository-url>/common-css#v1.0.0"
}

#v1.0.0 まで記述することで、指定したバージョンを落としてくることができます。
この状態で、 yarn install します。これによって node_modules 下に配置されるはずです。

Sass のビルドの設定を行っている箇所に対して、パス周りの修正をする必要があります。
node_modules 下に入ってきた共通CSSまでのパスを通す必要があります。
使用している環境に応じて適宜設定を行ってください1

theme ファイルの作成

共通CSSとはいえ、使用するサービスで調整したいことはあります。
一番よくあるのは、そのサービスカラーに合わせた色の指定かと思います。
そんなときは、変更したい値を変数管理することで解消できます。

common-css

共通CSS側は下記のような形にします。

_import.scss

//_import.scss

@import "./theme";
@import "./box";

_theme.scss

//_theme.scss

$box_bg-color: #fff !default;

!default をつけていくことで、$box_bg-color に値が設定されていない時のみ、指定した値を割り当てるようにすることができます。

_box.scss

//_box.scss

.box {
  backdround-color: $box_bg-color:
}

使用する側

共通CSSを使用する側は下記のような形にします。

_theme.scss

//_theme.scss

$box_bg-color: #ffffe0;

index.scss(最終的に出力されるファイル)

※設定次第でパスの記述方法が変わるので適宜修正してください

//index.scss

@import "theme";
@import "common-css/_import"; 

$box_bg-color には値がセットされることになります。
そのため、!default が指定された #fff が有効になることはありません。
!default がないと、$box_bg-color の値は上書きされてしまうので注意してください。

このように管理することで、使用する際に、適宜テーマを変更することができるようになります。

共通CSSの更新

master にマージしたら、git tag でタグ付けし、git push --tags で tag を push します。
その際、しっかりとリリースノートを記入しておきます。2

各サイトの開発者はこれを確認しつつ、必要に応じて使用するバージョンを上げます。
package.json の # 以下を書き換えるだけです。

ただ、この運用は一例でしかないので、会社にあった運用をしていただければと思います。

注意点

共通CSSの更新ですが、原則として破壊的ではない変更のみがいいと思います。
バグの場合は仕方がないですが、CSSに対する変更は使用箇所によっては崩れてしまう可能性があります。
そのため、可能なかぎり破壊的変更はしないようにしましょう。

開発中の対応

本来であれば共通CSSを先に開発して、その後別プロジェクトでこれを使用する、というのが理想ですがそうはいかないことも多いかと思います。
そんなときに必要な手順になります。
そこで開発時だけは、手元の共通 CSS リポジトリを参照するように設定するという運用にしました。
これは npm link (yarn を使っている場合は yarn link) コマンドを使います。

弊社では Yarn を使っているので、ここからはそれを前提としたコマンドとなっています。

参照される側(common-css)での作業

common-css 下で下記のコマンドを実行し、他のリポジトリから yarn link できるようにします。

yarn link

参照する側(各プロジェクト)での作業

yarn link common-css

やめたい場合は、下記を実行

yarn unlink common-css
yarn install

まとめ

以上が、リポジトリを横断したコンポーネント(Sass)管理を行う方法になります。
この方法で対応すれば、複数サイトの運営を別リポジトリ(プロジェクト)で管理している場合でも、共通CSSとして管理することが可能になります。
これにより、リポジトリ毎ではなく、会社としてのコンポーネント管理が進み、Sassファイルをゴッソリとコピペする必要がなくなります。

今回はバージョン管理をしっかりと行う方法をメインに紹介しましたが、これが不要で常に最新でいいということであれば、本番ビルドなどでも yarn link を使う方法もあります。
しかし、意図しないときに更新が入ってしまう恐れがあるので注意が必要になります。
運用方法はいくつかあると思いますので、環境に合わせた運用をしていただければと思います。

最後に…この記事は ErgoDashで書きました。


  1. webpack を使用している場合は、sass-loader の includePaths に node_modules を指定する形になります。今のところ遅さというものは気になっていませんが、気になるようでしたら調整してください。

  2. 弊社では GitHub Enterprise を使っているため、Releases 機能を使ってリリースノートを管理しています