M&Aクラウドで実践している不具合対応フローをご紹介します

f:id:tsukahara1991:20210709173755j:plain
不具合対応中

こんにちは、塚原(@AkitoTsukahara)です。

サービス開発を続けていると対策していても大なり小なり不具合が発生してしまいます。そのため、早期に不具合を発見して、少ない被害に抑えることが大切になります。(もちろん、不具合が発生しないことが一番!)

弊社では不具合を早期発見できるように2つの仕組みを用意しています。
1つは以前もご紹介したRollbarを利用したアラート検知する仕組みです。
Rollbarについて詳しく知りたい方は、こちらの記事をご覧ください。 tech.macloud.jp

もう1つは、非エンジニアでも不具合をSlackから報告できる仕組みです。今回はこちらの仕組みについて、ご紹介します。
弊社では非エンジニアでも不具合を報告できる「バグかもしれない報告」というSlackチャンネルを用意しています。ここでエンジニアは報告を受け取り、不具合を解消していきます。

不具合と言っても、サービスが停止してしまうような大規模障害もあれば、一部のユーザにのみ影響する不具合もあります。 緊急度に応じて対応方針が変わってくるのですが、その都度チーム間で対応方針を相談することでコミュニケーションコストが高くなり、障害対応の初動が遅れてしまう問題がありました。

そのため弊社では、不具合発生時の対応フローをまとめ、スムーズな対応ができるようにしています。

対応フロー

下の図は実際に弊社で共有されている対応フローになります。

f:id:tsukahara1991:20210712004022p:plain
不具合フロー

ポイントになるステップを補足させていただきます🙋‍♂️

「バグかもしれない報告」をSlackで受け取る

Slackのワークフロー機能を活用し、一定のフォーマットに沿って報告をしています。

f:id:tsukahara1991:20210711233414p:plain
バグかもしれない

ユーザ影響の調査を行う

不具合の影響度によって、早急に対応が必要なものかどうかをエンジニアが判断します。 影響度のポイントは「後戻りのできないユーザ影響なのかどうか?」
もう少し具体例を加えて説明させていただくと、

  1. サイト外に影響して取り返しのつかないもの
    ・ユーザへ重要な通知が届かない
    ・データベースに登録されるデータが欠損する etc

  2. ユーザ行動に影響する
    ・入力フォームの不具合で申し込みが進まない
    ・ログインできない etc

上記の2つが主な指標になります。 この判断が難しい場合は、他のエンジニアに相談するようにしています。

ユーザ影響高の場合

この場合は即日対応を行います。通常差し込みタスクが発生した場合はPMに対応すべきか確認するルールになっていますが、ユーザ影響高の場合は、エンジニア判断で不具合修正を進めて良いことになっています。エンジニア側で影響度を見極めて対応できることで、不具合発生時の初動を早めることが可能になっています。

まずはフローに沿って、早急にPMに不具合に関する情報共有を行い、報告を行ったエンジニアは取り掛かり中のタスクを一旦そのままにして不具合の対応に入ります。

ユーザ影響高ではない場合

この場合は対応タイミングをPMと確認することが必要になります。 弊社は2週間を1スプリントとして、毎週火曜と木曜をリリースタイミングにしています。 報告された不具合の程度によって、次のリリースまでに行うのか?現在のスプリント中に行うのか判断していきます。 ユーザ影響あるものは「2nd Priority」の方針に従って極力早いタイミングでの対応が奨励されています。

2nd Priorityについてはこちら。 tech.macloud.jp

報告者への対応完了報告

不具合が解消されたことを報告します。 同じ障害が起きない安心感を与えたり、似たような不具合が再発した時に再度報告してもらうことができます。

このような形で弊社では不具合の共有を行い、対応フローを整備しておくことで初動までのコミュニケーションコストを抑えて、スムーズな対応を可能にしています。このフローではコミュニケーションを減らすのではなく、不具合対応までの初動を早めることを目的としています。不具合発生時は常に報告・共有が重要なので単にコミュニケーションを減らせば良い訳でないので注意したいですね。

弊社では、一緒にサービス開発をしてくれる仲間を募集しています。 興味のある方はぜひご応募ください。

www.wantedly.com

M&Aクラウド初主催のエンジニア向け勉強会を開催しました

f:id:kubotak:20210630143334p:plain

こんにちは久保田(@kubotak_public)です

先日の2021/06/23にNuxt道場というオンライン勉強会を開催しました。

macloud.connpass.com

この勉強会は弊社でも扱っているフロントエンドのフレームワークであるNuxt.jsをテーマとした勉強会で、 株式会社Hajimariの三宅様、株式会社レアジョブの田原様を師範(登壇者)としてお迎えいたしました。

Nuxt道場はYoutubeLiveで配信したため、アーカイブとして現在でも視聴することができます。 Nuxt道場って何?という方はぜひご視聴ください。

www.youtube.com

今回の勉強会はM&Aクラウドでは初となる弊社主催によるエンジニア向け勉強会でした。 弊社主催で勉強会をすることによって次の効果を期待しています。

弊社を知ってもらう・興味を持ってもらう

弊社のようなベンチャー企業知名度が低く、エンジニア採用も難しいです。 そのため、一人でも多くのエンジニアに弊社を知ってもらうための広報活動の一環でもあります。

社外へ発信する・社内だけでは得られない知見を外から得る

広報活動だけでなく、社内のレベルを底上げするためにも自分たちが発信すること、また他社の事例や知らなかったことを知る機会を作ることが大事です。 弊社のエンジニアには「全員インフルエンサー」というバリューがあるのでこれを体現するためにも発表できる場を自分たちで作っていくことも大切です。

tech.macloud.jp

最後に

一回で終わらず、シリーズとして続けていければと思いますので登壇したいという方がいらっしゃいましたらぜひ久保田までお声がけください。 また、Nuxt道場に限らず弊社で扱っている技術であるLaravel道場やAWS道場などを開催しても良いかもしれません。

見えないエラーを見れるようにする & 効率化のため Rollbar を導入した話

こんにちは。エンジニアの鈴木(yamotuki)です。 今回は本番や開発環境でエラーが起こったときに、効率よく対応ができるように Rollbar というツールを導入した話です。

Rollbar とは

公式はこちら

JavaScriptPHPで発生したエラー詳細をSlack通知してくれます。
通知や画面詳細ページは後述してあります。

導入目的

導入目的としては、フロントエンドのエラー検知とバックエンドサーバのエラー対応の簡素化の二つがあります。

1. フロントエンドのエラー検知

Universalモード(SSRCSRでシームレスに動作する仕組み)で動いているNuxt.jsによるアプリケーションのエラー検知の仕組みが欲しい、というのが一番最初の導入のきっかけでした。 SSRで動いている部分については、なんとかしてログ出力をしてエラーを検知することをできるかもしれません。しかし、それぞれのクライアントで動いているJavaScriptについてはコンソールログにだけ出ている状態で、エラーが起こっていることを知ることができない、というのが一般的なお困りごとかと思います。
Rollbarを使用すれば、エラー発生時のSSR/CSRを問わない統一的な仕組みとしてエラー通知を行うことができます。

2. バックエンドサーバのエラー対応の簡素化

弊社では AWS CloudWatch によりCloudFrontやEC2で発生した5xx系のアラートを検知してSlack通知していますが、以下のように情報量が少ないのが悩みの種でした。問題調査をするためにAWSコンソールを開いて、該当のアクセスログや、対応するエラーログを確認する必要があり、ちょっとしたエラー確認でも手間がかかっていました。

f:id:yamotuki:20210615170011p:plain
調査に時間がかかるアクセスログの5xx検知

導入結果

フロントエンドのエラーを検知して対処できた

振り返って見てみると、この記事にかけるような面白いエラーはログ保存期間の間には起こっていませんでした。 導入当初には広範囲のユーザ影響が出てしまう致命的なエラーが何度か起こっていましたが、逐次対応してきた結果、最近ではエラー頻度はぐっと減っているようです。

バックエンドサーバのエラー対応が効率化された

Rollbar を使うと以下のように何が起こったのか詳細がSlack通知され、さらに詳細を見たければリンクをクリックするだけでいいので劇的に調査が楽になります。

f:id:yamotuki:20210615165935p:plain
RollbarからSlack通知

詳細画面に行くと以下のような情報を見ることができます

  • 何回、いつ、影響したユーザ(IP)数
    • 障害対応においては、どれくらい深刻な障害なのか?というのは重要な情報なので、これがログをいちいち漁って見なくてもまとまっているのが良い
  • スタックトレースを引数つきで見れる
    • 「最終的にエラーが起こっている場所は分かったけど、途中でなんでこうなった?」みたいなケースも簡単に追跡することができます

f:id:yamotuki:20210617190317p:plain
詳細ビュー

導入でハマった点&工夫した点

Nuxt.js

nuxt-rollbar-module を用いています。細かい導入方法についてはドキュメントを参照してください。
導入に際して工夫した点だけ共有します。

  • 問題点
    • local で rollbar 通知をすると回数制限を無駄にしてしまう。TOKENを入れないとrollbar通知するためのコードが無視されるので、localでうまく動いているか判断できない。
  • 解決策
    • plugins に dummy-rollbar.ts を作成し、rollbar を呼んでいるところは local では console.log にマッピングするようにしました。
import { Context } from '@nuxt/types'

export default (ctx: Context, inject: (key: string, value: any) => void) => {
  const rollbar = ctx.$rollbar ?? {
    debug: (...e: any) => console.log(...e),
    info: (...e: any) => console.log(...e),
    warn: (...e: any) => console.log(...e),
    warning: (...e: any) => console.log(...e),
    error: (...e: any) => console.error(...e),
    critical: (...e: any) => console.error(...e)
  }
  // ref: https://github.com/gaelreyrol/nuxt-rollbar-module/blob/develop/lib/templates/rollbar-client.js#L46-L47
  ctx.$rollbar = rollbar
  inject('rollbar', rollbar)
}

plugins の差し込みについては公式ドキュメントを参考にしてください。

Laravel

基本はドキュメントを読んで導入かと思いますが、ハマったところだけ共有しておきます。

config/logging.php で以下のような設定を入れて、error log と rollbar の両方に通知するようにしてあります。

<?php

    'channels' => [
        'errorlog_and_rollbar' => [
            'driver' => 'stack',
            'channels' => ['errorlog', 'rollbar'],
            'ignore_exceptions' => true,
        ],
        'errorlog' => [
            'driver' => 'errorlog',
            'level' => 'debug',
        ],
        'rollbar' => [
            'driver' => 'monolog',
            'handler' => \App\Providers\Rollbar\MonologHandler::class,
            'access_token' => env('ROLLBAR_TOKEN'),
            'level' => 'error',
            // rollbarでマスクしたいパラメータを指定
            'scrubFields' => [
                // user
                'password',
            // 以下略

rollbar の handler として指定している MonologHandler クラスの雛形はこちらです。

雛形の namespace が間違っている問題

こちらのライブラリへのPRが出されているように、namespaceが間違っていていました。

ignore_exceptions の問題

雛形のコードを少し書き換えて使っていたのですが、とある変数の初期化がされておらず無言エラーになり、本番通知がうまくされていませんでした。”無言”である理由はignore_exceptionsがtrueになっているからでした。この設定の意図としては、片方の channel でエラーが起こっても、握りつぶしてもう片方の channel には通知が行って欲しいという意図かと思われます。localで一時的に試しにfalseにしたところ、すぐにどこでExceptionを吐いているか特定し、修正できました。

開発環境にも入れておく

上記の通り、本番環境で迅速にエラー対応するために効果的なのですが、開発環境にも入れておくことで、エラーをユーザに見せる前に修正できることがあります。
開発環境に入れるときに通知先Slackチャンネルの分離に一工夫があったので共有します。

  • 問題点
    • Project ごとで通知先が一箇所しか選べない。Projectはトップレベルの階層で、その下にEnvironmentがあります。Projectの中の Rollbar の Items(エラーの一つ一つ)には Environment が紐づいているので、それによって通知先を分離したいと思っていましたが、できませんでした
  • 解決策
    • Project を開発環境、本番環境で分離しました。Environment が情報として冗長になってしまいますが、Slack通知先を分離することができました。

終わりに

www.wantedly.com

フロントエンドのエンジニアに届かないエラーを可視化することで大きな問題でもすぐに対応できるようになりました。エラー対応を効率化すると、神経をすり減らす障害対応を少し楽にすることができました。

弊社では生産性向上をして、ユーザにより多くの価値を一緒に届ける気持ちを持ったエンジニアを募集しています。

AWS LambdaからECS Fargateへの移行

こんにちは、久保田(@kubotak_public)です

今回は弊社で運用しているサービスであるM&Aクラウドのフロントエンドの実行環境をAWS LambdaからECS Fargateへ移行した話です。 まずは弊社のサービスが動いている環境は次のようになっていました。

f:id:kubotak:20210609141326p:plain

フロントエンドにNuxt.js(JavaScript)、バックエンドにLaravel(PHP)を利用しています。 LaravelはAWS ElasticBeanstalkで作成されたEC2インスタンス上で動作しています。

そしてフロントエンドのNuxt.jsはAWS Lambda上で動作し、API Gatewayを利用してHTTPによりアクセスできるようになっていました。 もともとはLaravelの環境だけでしたが、Nuxt.jsでフロントエンドをリプレースしたページが混在しているのが現状です。 これらはCloudFrontによって各環境にリクエストがルーティングされています。

Nuxt.jsへの移行に関しては別途シリーズでお届けしていますのでよろしければご参照ください。

tech.macloud.jp

なぜ移行するのか

このNuxt.jsの実行環境であるAWS Lambdaでは次のようなメリットがありました。

  • 運用が楽
  • 費用が安い

弊社の運用においてはLambdaのコールドスタートに関しても特に問題ではありませんでした。 一年程はLambdaによる運用にも特に問題にはなっていませんでした。

しかし、Nuxt.jsのアプリの肥大化により状況が変わってしまいました。 AWS Lambdaではデプロイできるサイズがzipの状態で50MB、展開した状態で250MBの制限があり、Nuxt.jsとTypeScriptのバージョンアップを行った際にこの制限に引っかかる様になってしまいました。

そこで、容量の制限に余裕がある環境への移行が求められ、Amazon ECSのFargateが候補にあがりました。

Amazon ECSとは

Amazon Elastic Container Service(Amazon ECS)は完全マネージド型コンテナオーケストレーションサービスであり、コンテナ化されたアプリケーションを簡単にデプロイ、管理、スケールするのに役立ちます。

AWS Fargateとは

FargateはECSやEKS上で動作するコンテナ向けサーバーレスコンピューティングエンジンです。 Amazon EC2 インスタンスでサーバーまたはクラスターを管理する必要なくコンテナを実行することができます。

サーバー管理にコストを掛けずに運用したいという考えからAWS Lambdaを利用していましたので、同じようにサーバー管理をマネージドしてくれるECS Fargateを採用しました。

AWS LambdaへのデプロイからAWS Fargateへのデプロイに切り替え

まずはFargateで動作させるためにDockerコンテナ化と、そのコンテナをホスティングする必要があります。 Dockerfileは非常にシンプルです。

FROM node:12.14.0-alpine

RUN mkdir -p /var/www/workspace
WORKDIR /var/www/workspace
COPY ./.nuxt /var/www/workspace/.nuxt
COPY ./dist /var/www/workspace/dist
COPY ./node_modules /var/www/workspace/node_modules
COPY ./.env /var/www/workspace/.env

EXPOSE 80

ENTRYPOINT ["node", "dist/server/server.js"]

※一部改変しています。

ベースとなるnode.jsのDockerイメージを利用してビルドしたアプリケーションがコンテナ内に配置されるだけです。

Dockerイメージのpush

DockerイメージはAmazon Elastic Container Registry(Amazon ECR)にホスティングします。 弊社ではアプリケーションのデプロイのCI/CDとしてCircleCIを利用しています。 CircleCIでECRへpush(Dockerイメージをホスティング)するには次のプラグインを利用しています。

version: 2.1
orbs:
  aws-ecr: circleci/aws-ecr@7.0.0

定義は次のようになります。

- aws-ecr/build-and-push-image:
          account-url: AWS_ECR_ACCOUNT_URL
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          create-repo: true
          region: AWS_REGION
          repo: repository-name
          skip-when-tags-exist: true
          tag: "${CIRCLE_SHA1}"

これでDockerfileを利用してDockerイメージの作成とECRへのpushをCI上で行ってくれます。

Fargateへのデプロイ

AWS LambdaへのデプロイはServerless Frameworkを利用していたので、同じ仕組みでFargeteへのデプロイを行いました。

Serverless Pluginを追加してFargateへのデプロイを追加します。 検索すると次のプラグインが上位に出ます。

GitHub - honerlaw/serverless-fargate-plugin: Serverless plugin to deploy fargate tasks to an ECS cluster.

しかし、こちらのプラグインは保守がされてなく、弊社の環境ではデプロイも失敗しました。 そこでこのプラグインをForkしているものを使っています。

github.com

※一部subnetに関する拡張を行いたかったのでコントリビュートしました
※このプラグインの作者が別途新規でプラグインを作成中ですのでそちらもチェックされると良いかもしれません。

デプロイして新環境で動いていることが確認できたらAWS Lambdaで動いている環境から切り替えます。 この切り替えは前段であるCloudFrontからの向き先を変更するだけです。

f:id:kubotak:20210609141837p:plain

移行してみて

AWS Lambdaと異なり、Fargateでは稼働している台数のハンドリングや利用しているCPUやメモリのリソースの把握が重要になってくるかと思います。 しかし、Fargateのオートスケールの設定などはわかりやすく、スケーリング自体もスムーズな印象です。 API Gatewayが不要になり、代わりにロードバランサーが追加された点などもあり、アーキテクチャとしては複雑になったという印象があります。 デプロイ時間に関してはECRへのイメージpushやECSがローリングデプロイをする関係上遅くはなってしまいましたが、現時点では運用して間もないので過不足は特に感じていません。 引き続き監視を行い、アプリケーションの成長に合わせてインフラもスケールするように改善していきたいと思います。

最後に

弊社のフロントエンドエンジニアは今回のようにAWS環境を利用した改善・保守も行います。
アプリケーションコードにとどまらず、インフラ環境も扱いたいフロントエンドエンジニアの方は一緒にアプリケーションを成長させていきましょう。

www.wantedly.com

MeCabを使ってテキストクレンジングをする

f:id:kazuhei0108:20210607110723j:plain

こんにちは、M&Aクラウドのかずへいです。

弊社のサービス「M&Aクラウド」では、ビジネスキーワードと呼ばれるキーワードを使って買収・出資企業様と売却・資金調達企業様のマッチングを行っております。

f:id:kazuhei0108:20210607112917p:plain
ビジネスキーワードを入力している様子

このビジネスキーワードというものは、ユーザー様が自由に入れられるものなのですが、その分表記ブレが発生したり、キーワードとして使えない文章だったりといった事が起きていました。

今回は簡易的に、キーワードではないものを判別して除外する、という条件で進めることにしましたので、MeCabを使って品詞からキーワードではないものを判別し除外していくことにします。

MeCabとは

MeCabオープンソース形態素解析エンジンです。形態素解析とは、自然言語で書かれた文を言語上で意味を持つ最小単位(=形態素)に分け、それぞれの品詞や変化などを判別することです。文章を名詞、動詞、助詞、助動詞などの品詞に分解し、種類を判別します。Mecabは日本語の形態素解析エンジンとしてはかなりメジャーなものだと思います。

mecabのインストール

弊社ではWEBサーバーにAWS Elastic Beanstalkを利用しているため、その設定ファイルを書いていきます。ebextensions/*以下に配置しました。 実際のコードは以下を御覧ください。

qiita.com

MeCab自体のインストールと、MeCabから使う辞書データのインストールという2つをインストールします。

実際に使ってみる

MeCabをコマンドとして実行します。

f:id:kazuhei0108:20210607111017p:plain

このように、テキストを品詞分解した上でそれぞれが何の品詞かを判別してくれます。上記のように文章をMeCabに渡すと助詞や助動詞が判別されます。これらがある場合、そのテキストはキーワードではなく文章だと判別できます。 MeCab: 品詞 ID にありますが、MeCabの品詞分類の結果はかなり細かいです。実際ここまで細かく品詞を分類して使うことってあんまりないんじゃないでしょうか?

今回は助詞、助動詞、記号を含む場合、文章であると判定し除外するという処理にしました。

はまりどころ

こちらの記事に紹介されているのですが、記号がなぜかサ変接続の名詞として判定されてしまうという問題がありました。

qiita.com

サ変接続の名詞というのは「該当」のような後ろにするをつけると「該当する」となり動詞になることができる名詞なのですが、これだけだと名詞ではあるので、除外したくはありません。一方、「#」のような明らかに記号のものがなぜかサ変接続の名詞というふうに誤判定されてしまいます。

今回は色々試したところ、半角の記号は全角に変換してからMeCabにわたすと記号として認識される事がわかりましたので、これで対応することができました。

まとめ

普段WEBアプリケーションを書いていると、こういったPHPからコマンドを実行するというような処理はあまりやらないので面白かったです。あと、WEBサーバーにMeCabが入ったことによって、今回のキーワードのクレンジングだけでなく、ユーザーの入力値のバリデーションなど様々なことに応用できそうだと考えています。

最後に

M&Aクラウドではデザイナーやエンジニアを募集しています! 是非お気軽にご連絡ください!

www.wantedly.com

www.wantedly.com

www.wantedly.com

Feature toggles を複数の環境へ即時反映する仕組みを開発しました

f:id:hamakou108:20210528145639p:plain

こんにちは。エンジニアの濱田( @hamakou108 )です。

数ヶ月前にM&Aクラウドの feature toggles の仕組みを刷新したので、今回はその内容について紹介したいと思います。

Feature toggles とは?

Feature toggles はアプリケーションのビルド時または実行中に機能の切り替えを行う仕組みです。 Feature toggles はいわゆる feature branches の戦略とは対照的に、メインのブランチに頻繁にコードを統合します。 こうすることで CI/CD による早期のフィードバックと開発中の個別機能へのアクセスを両立します。

詳細については以下のページが参考になるかと思います。

刷新前の feature toggles の運用方法と問題点

M&Aクラウドフレームワークとして Laravel と Nuxt.js を利用しており、それぞれが別の環境で稼働しています。 各環境用の環境変数はファイルで管理しており、そこに feature toggles の値を追加するような形で運用を行っていました。

しかしこの運用方法には幾つか問題がありました。

環境変数の更新からリリースまでのタイムラグ

各環境に環境変数を反映するために、わざわざファイルを更新して CircleCI のワークフローを実行する必要がありました。 この一連の作業に30分から1時間程度の時間が掛かり、リリースのタイミングのコントロールが難しい状況になっていました。 バグが混入していた場合の切り戻しでも同様の作業を行う必要があるため、 MTTR の悪化の要因の一つにもなっていました。

各環境の切り替えタイミングの差異

Laravel と Nuxt.js の動作環境が異なることから、デプロイフローもそれぞれ分かれています。 このためフロントエンドとバックエンドで feature toggle の切り替えタイミングに差異が生じてしまい、それに起因してエラーが発生することが何度かありました。

Feature toggles の機能の刷新

上述した問題点を解消するべく、以下の課題を克服する新たな feature toggles 機能を開発するミニプロジェクトが立ち上がりました。

  1. Feature toggles の切り替えタイミングをコントロール可能であること
  2. フロントエンドとバックエンドで同じタイミングで feature toggle を切り替えられること

ここからは設計上のポイントについて3つ紹介します。

コード中にマスタを定義し、 RDB で on/off を管理

環境変数による feature toggles の管理は廃止し、次のような設計を行いました。

まず RDB に feature toggles 用のテーブルを作成し、 toggle の on/off をレコードとして管理できるようにします。 レコードを追加・更新すれば toggle の on/off が即時に切り替わるようにする算段です。

もう一つの工夫として FeatureToggleName という Enum 型のクラスを用意し、プロパティとして各 toggle の名前を持たせています。

<?php

class FeatureToggleName extends BaseEnum
{
    // 新規作成する際に定数を追加
    const FOO = '2021_05_27_foo';
    const BAR = '2021_05_28_bar';
    const BAZ = '2021_05_29_baz';

    public function __construct($value)
    {
        parent::__construct($value);
        if (!$this->isValidValue()) {
            throw new InvalidArgumentException('引数の形式は YYYY_MM_DD_name でなければなりません');
        }
    }

    private function isValidValue(): bool
    {
        return (bool) preg_match('/\d{4}_\d{2}_\d{2}_.+$/', $this->rawValue());
    }
}

アプリケーション内では FeatureToggleName のプロパティを feature toggles のマスタとし、ここに定義されていない toggle は存在しないものとして扱うように設計しています。 こうすることで不要になったレコードがテーブル内に残存していても、プロパティを削除しておけば UI から不要な toggle を排除できます。

また新たに追加する toggle の名前が過去に使っていたフラグの名前とたまたま重複していた場合、残存していた過去のレコードによって意図せずにフラグが on になってしまう危険があります。 これを避けるため、(現実的には)重複しないようなフラグ名を設定することをコンストラクタで強制しています。

管理画面から GUI でフラグ切り替え

M&Aクラウド社内のメンバーがアクセスできる管理画面に feature toggles 管理用のページを追加し、フラグの一覧表示および更新ができるようにしました 1

管理画面のフラグ管理ページの UI
コード中で定義されている feature toggles と ON/OFF の状態が表示され、個別に編集できる

即時に反映されるのは勿論のこと、アプリケーション経由で更新することで監査ログも残るようになり、ガバナンスの面でも強固な仕組みとなりました。 監査ログの機能に関しては別の記事としてまとめられていますので、興味のある方はこちらもご覧ください。

tech.macloud.jp

フロントエンド向け API

フロントエンド向けの feature toggles 取得 API を作成しました。 Nuxt.js の Router の middleware として設定することで各ページを開いた際に API が呼び出され、 Vuex 経由で feature toggles が取得できるようになりました。

const nuxtConfig: Configuration = {
  // ...

  router: {
    middleware: 'fetch-feature-toggle'
  },

  // ...
}

Feature toggles の刷新後

上述した課題を克服し、先日リリースした「かんたんM&A診断」では新しい feature toggles の仕組みを使って即時リリースすることができました!

macloud.jp

その後、開発時に想定していませんでしたが、この仕組みにはデータとロジックを同時に移行する際にメリットが得られることに気づきました。 Feature toggles も RDB で管理されているため、同一トランザクション内で移行したいデータと feature toggles のレコードを更新することで、データ移行の間にユーザーの操作が発生してデータに不整合が発生するといったケースを防止できます 2

一方で新たな課題も浮かび上がってきました。 機能のリリース後に不要なコードを削除する際、 FeatureToggleName のプロパティを先に消してしまうとそのフラグが off として扱われるようになるため、リリースした機能が巻き戻ってしまいます。 そのため削除の際は

  1. フラグを使って条件分岐を行うコードを削除する
  2. FeatureToggleName のプロパティを削除する

という手順を踏まなくてはなりません。 この順序を気にしなくても良いように改善することが今後の課題です。

最後に

M&Aクラウドではデザイナーやエンジニアを募集しています! 是非お気軽にご連絡ください!

www.wantedly.com

www.wantedly.com

www.wantedly.com


  1. 開発チーム内では feature toggles のことを feature flags や機能フラグのように呼んでいます。

  2. トランザクション数や移行するデータの量が多い場合はロックの影響を無視できなくなるため、この仕組みが万能というわけではありません。

生産性を落とす原因を可視化するためのサイクルタイム計測と施策について

はじめに

こんにちは。エンジニアの鈴木(@yamotuki)です。 今回はエンジニアの生産性向上の施策として、Jiraを用いた計測手法と、やってみた施策について書こうと思います。

背景

ストーリーポイントの計測

”ストーリーポイントとは?”についての詳細は他の記事にお任せします。

弊社ではスプリントを採用しており、毎スプリントで消化しているストーリーポイントを計測しています。 エンジニアが特定の一つのタスクに詰まってしまって、なかなか終わらせることができなければストーリーポイントの合計は当然低くなってしまいます。 スプリントで消化したストーリーポイントが高いからといって、優先度の低かったり不要なタスクをやっていたら生産性というのは向上しないものですが、消化したストーリーポイントが低い場合には何らかの問題が隠れているものです。

ただ、スプリントが終わった時の消化ストーリーポイントの合計というのは、結果の値でしかなく、細かくどこでエンジニアが詰まったのか?というのは一見して分析することができません。

サイクルタイムの計測

そこで、”サイクルタイム”を計測することにしました。 サイクルタイムは Jira ですと管理チャートで確認することができます。

サイクルタイムとは何か、については以下にGoogle翻訳による翻訳を引用します。

サイクルタイムは、問題の作業に費やされた時間です。通常、問題の作業が開始されてから作業が完了するまでにかかる時間ですが、問題の作業に費やされたその他の時間も含まれます。たとえば、問題が再開され、作業が行われ、再度完了した場合、この余分な作業の時間がサイクルタイムに追加されます。

例えば、「仕様決めに時間がかかったのか?」「作業に時間がかかったのか?」「レビューで時間がかかったのか」などを細かく追っていくことができます。

施策を始める前

Github IssueからJiraに移行してから測定は勝手にされていたので、遡って各種数値を確認することができました。 測定と生産性向上施策を始める前の状態は以下の図の通りでした。 一つのタスクを着手から完了まで3日近くかかっています。赤い線が表示期間の中での平均で、青い線が前後のタスクとの移動平均です。

f:id:yamotuki:20210518131944p:plain
施策をやる前の状態

タスク完了まで時間がかかるのには以下のような問題点があると考えました。

  • チェック待ち状態が長くなる要因
    • プルリクを出した後にレビュアが二人チェック。議論が全てGithub上で行われるので、ラリーが多い
    • 仕様がふわっとした状態で実装を始めて、レビューの段階で「ここPdMと相談しておいた方がいいんじゃない?」みたいな話が頻繁に行われ、手戻りが多い
  • 作業中状態が長くなる要因
    • 一人が複数タスクを際限なく取ることができるので、複数タスクを平行で進めることによって結果として全てのタスクに時間がかかる。3ポイントのタスクを二つ持つとどちらもなかなか終わらない。(補足: 現在は適当なタスクからの相対的な値としてストーリーポイントを見積もっていますが、過去には3ポイントは ”2日以内に終わる” という風に見積もっていました。弊社における3ポイントはこれくらいの大きさです。
    • 各エンジニアがハマっても、他の人が適切に助けることができない
    • 一つの大きなタスクを分割せずやろうとする。ざっくり8ポイントとかだと、いつ終わるかの見積もりも立てづらい

施策

DevOpsハンドブックには「それぞれのプロセスを短くしてフィードバックサイクルを早くすることで生産性が上がる」(と私は解釈)と書かれていました。そこで、できるだけそれぞれのプロセスを小さなバッチサイズにするような施策を試してみることにしました。

試した施策

以下のような施策を試してみました。特に改善効果があった施策は太文字にしておきます。

  • チェック待ちが長くなる原因の対処
    • レビューを一回やって修正項目が出たらレビュアとペアプロをやって一気にマージまで持っていく。文章でのラリーを避ける
    • Jiraのステータスで、"作業中"の他に”仕様受け入れ中”を作る。PMとして仕様が固まったら”仕様策定済み”になり、エンジニアが細かい仕様を詰めているステータスとして”仕様受け入れ中”を使用。エンジニアとして明確になったと思ったら"作業中"に移動する。 
    • 実装者とレビュアを三人チームとして固定。タスクを取る人はレビュアになる人にもできるだけ共有しておく。実装前に不明点をできるだけ潰して手戻りを防ぐ
  • 作業中状態が長くなる原因の対処
    • 並列で取れるタスクの量をストーリーポイント4ポイント以内にする。3ポイントのタスクを取ったら1ポイントの小粒なタスクしか取ってはいけない。
    • Slackの#developers_chattingチャンネルに今やっていること、困っていることを投稿する。他の人は余裕があれば支援(口出しともいう)をする
    • 詰まったら積極的にレビュアになる人に通話を繋いでペアプロ作業をする。
    • タスクを1~3ポイントのタスクに積極的に分割して、順次終わらせる

一番効果があった施策

一番効果があったのは、”タスクを一人で抱え込まず、チームで一気に終わらせる” ためのペアプロでした。ペアプロを通して、タスクは一人のものではなくてチームで解決するものなのだという意識が浸透したのも良かった点かなと個人的には考えています。
ペアプロの方法としては、Aroundというミーティングツールを使ってslack上でミーティングルームを立ち上げて、他の人が入るという形をよく使っています。

f:id:yamotuki:20210518173221p:plain
Aroundを使っている風景

他にもPhpStormでリアルタイム共同編集できる機能なども出てきているので、活用していきたいと思っています。

施策の結果

サイクルタイムを見ながら施策を投入していくことで、以下のように一つのタスクが終わるまでの平均時間が改善しました。タスクを小さく分割する施策も入れているので平均時間が減るのは当たり前と言えば当たり前ですが、一人でずっと抱えて巨大プルリクを作って大きく手戻り、というのは減ったと思います。

👇 1ヶ月以上かかるプロジェクトをみんなでやっていたパターン。コミュニケーションコストが低く、平均として低く抑えられている。

f:id:yamotuki:20210518163833p:plain
1ヶ月以上かかるプロジェクトをやっていた場合の管理チャート

👇 細かい改善タスクをバラバラやっていた期間のパターン。コミュニケーションコストが高いが、beforeの2.9日からafterとして2.2日くらいまでよくなっています。 期間の最後ではちょっと上がってしまっていますが、時間のかかるインフラタスクをやっていたり、他部署の人にデータ渡す系のタスクなど、完了まである程度時間がかかる原因が分かりやすいタスクが多かったです。

f:id:yamotuki:20210518164151p:plain
細かいタスクをやっていた時期

終わりに

もっと早く、もっと楽に、ユーザに価値を届けられる組織をこれからも目指していきます。 生産性の高いエンジニア組織作りに興味がある方などいらっしゃれば声をかけていただけるとありがたいです。

www.wantedly.com

コード自動生成とDIを活用したレバレッジ指向なメールプレビュー機能を実装しました

はじめに

こんにちは、エンジニアの津崎(@820zacky)です。 今回は、先日実装したLaravelでのメールのプレビュー表示、テスト送信機能についてご紹介します。

「メールプレビューはあると便利そうだけど、 メールごとにプレビューのための処理を追加する必要があるんじゃない? すでにたくさんの種類のメールがあるし、メールを追加するのも大変になるからやりたくないよ」 と思ったエンジニアのあなた。

まさにその通りです。私たちも同じ気持ちだったので、最小限の労力でメールプレビューができる仕組みを目指して機能を作りました。

私たちが開発・運用しているマッチングプラットフォームでは、ユーザへの通知やメールマガジンなど様々な場面でメールを送信しており、現在110種類ものメールが存在します。

これらのメールは、開発時にPdMへ内容の確認を行ったり、営業などの他部署から内容の問い合わせを受けることがあります。 プレビュー機能を実装する前は、メールの内容を確認するには、開発環境で実際にメール送信の条件を作り、メール送信を行うことで確認を行っていました。

開発環境ではMailtrapというメールのサンドボックスサービスを利用しており、開発環境から送信されたメールはMailtrap上のメールボックスにて表示の確認ができるようになっております。

Mailtrap - Email Testing Tool #1

f:id:zacky2:20210514180659p:plain
Mailtrap上の受信箱にて受信したメールを開いているスクリーンショット

Mailtrapにより、送信されたメールについては簡単に確認と共有ができるのですが、メールによってはバッチを実行しないと送信されなかったり、メール送信の条件が難しいものがあり、メール送信することが手間のかかる作業となっていました。 そこで、マッチングプラットフォームの管理画面に、メールの内容を簡単に確認するための機能としてメールプレビュー機能を実装しました。 また、メールクライアントによる表示の崩れがないか確認できるよう、メールの送信機能も併せて実装しています。 スマートフォンの実機でのメール表示や、Outlookなど癖の強いメールクライアントなどもあるため、凝ったデザインのメールを作る際は必須の機能です。

どんな機能か?

社内のメンバのみアクセスできる管理画面から利用できる機能です。 メールの一覧が並んでおり、「プレビュー」と「任意のメールアドレスに向けたメール送信」ができます。

f:id:zacky2:20210514184109p:plain
メールプレビュー機能のメール一覧画面

f:id:zacky2:20210514204030p:plain
メールのプレビュー画面

実装の詳細

実装の詳細についてはQiitaの記事をご覧ください。

qiita.com

メール一覧の取得

メールプレビュー機能を作るにあたって、プロダクトで使われているメールクラスの一覧が必要です。 しかし、新しいメールを追加するたびに、メールクラスのパスをリストに追加するのは億劫な作業であり、追加の漏れも懸念されます。 メールプレビューのためにメール実装の手間が増えるのは避けたいところです。 そこで、実行するだけでメールクラスの一覧を持つクラスを自動生成するコマンドを作りました。

f:id:zacky2:20210514201820p:plain
メールクラスリスト生成コマンドの実行例

メールクラスの一覧の取得する方法として、特定のディレクトリを探索しメールのクラスが記述されたphpファイルの抽出を行っています。 メールクラスはMailableを継承したクラスであるため、ReflectionClassを使い、再起的にIlluminate\Mail\Mailableを継承しているかチェックすることで判別できます。

ファイル探索と継承のチェック処理はページ表示のたびに行うと処理に時間がかかってしまうと想定できたため、コマンド化し手動実行するようにしました。

DIを活用したメールプレビューの仕組み

LaravelのControllerクラスは、メール(Mailable)のインスタンスを返却するとHTMLとしてレンダリングしてくれます。 そのため、Mailableクラスのインスタンスを作ることができれば簡単にプレビューを行うことができます。

しかし、メールのインスタンスは簡単に作ることはできません。 なぜなら、メールは送信先が可変であったり、本文が可変であるものがほとんどであり、可変な部分はインスタンス生成の引数として渡してあげる必要があるためです。

<?php
use Illuminate\Mail\Mailable;

class HogeMail extends Mailable
{
    private User $user;

    // 例として引数にUserを渡している。インスタンス化するにはUserのインスタンスが必要。
    public function __construct(User $user)
    {
        $this->user = $user;
    }
    
    public function build()
    {
        return $this
            ->subject( $user->getName() . "さんこんにちは!") // 対象のユーザによってタイトルを変える
            ->to($user->getEmail())  // 対象のユーザによって送信先を変える
            ->markdown('emails.hoge_mail');  // 対象のユーザによって本文を変える (Markdownの外部ファイルにて記述)
    }
}

実際のプロダクトでは100種類以上のメールが存在するため、それぞれのメールについて引数に渡す値を作り、Mailableクラスのインスタンスを生成するのは手間のかかる作業です。 メールを増やすたびに毎度メールプレビューのための処理を追加しなければならないのは、開発チームのバリュー「レバレッジ指向」と乖離します。

tech.macloud.jp

そこで、LaravelのDI機能を使って、メールに渡す引数についてDIするように実装を行いました。DIを活用すれば、同じ種類の引数を使っているメールであれば、処理を追加しなくてもメールのインスタンス化ができ、プレビューを表示することができます。

メールの引数のインスタンス化(ダミーの作成)については、すでにUnitテスト用にダミーのインスタンスを生成する実装が存在していたため簡単に作ることができました。

しかしながら、全てのメールについて引数を作ることは大変です。そこで、最初の実装ではいくつかのメールだけがプレビューできれば良く、段階的にプレビューできる対象を増やしていくことにしました。 メールで使われる引数はたくさんの種類がありますが、共通している引数も多いです。 よく使われるいくつかの引数についてDIを行うことで 30% 程度のメールについてプレビューできるようになりました。

f:id:zacky2:20210514192654p:plain
DIできてないメールはインスタンス化できないためプレビューできない

プレビュー未対応のメールについては、要望があった時に対応予定です。DIを追加するだけなので大きな手間はかかりません。

まとめ

今回、最小限の労力でメールのプレビューが実装できる仕組みを作りました。 一つはファイル探索とRefrectionを利用したメールクラスリストの自動生成、もう一つは、DIを利用したメールインスタンスの生成です。 「最小限の労力で大きな成果を出す」という私たちのバリューの一つである「レバレッジ指向」が発揮されたいい機能開発ができたと思います。

最後に

M&Aクラウドではエンジニアを絶賛募集中ですので、興味がありましたら是非以下からご応募ください!

www.wantedly.com

www.wantedly.com

最適なメンテナンス運用方法に辿り着くまでの試行錯誤

f:id:tsukahara1991:20210428181312j:plain こんにちは。エンジニアの塚原(@AkitoTsukahara)です

ECサイトや自社サービスを運用していると、サービスを一時停止しなければならないケースがあります。そのためにはサービス形態やインフラ構成に適したメンテナンス運用方法を事前に用意しておく必要があります。ほとんど利用することはありませんが、サービス信頼に関わる仕組みになります。
M&Aクラウドでは2通りの方法でメンテナンスページを表示する仕組みを準備しております。
今回の記事では運用方法とその運用に至った経緯をご紹介させていただきます。

メンテナンスに切り替える方法

切り替え手法 操作ページ メリット デメリット
1 管理ページからメンテナンスに切り替え 管理ページ(プラットフォーム独自のもの) 切り替えが簡単 バックエンドサーバーが停止したら利用できない
2 メンテナンス用のCloudFrontに切り替え AWSコンソール サーバー稼働状態に依存しない 切り替えに必要な操作が多い

1の「管理ページからメンテナンスに切り替え」は、フラグを切り替えるだけでメンテナンスページに切り替えられるようになっています。 プラットフォームの管理ページからフラグの切り替え機能を用意しているため、運用コストを低く抑えることができています。 通常の切り替えではこちらを採用しています。

もしもプラットフォームサーバーが停止してしまったら

1の方法は操作が簡単なのですが、万が一プラットフォームが稼働しているサーバーが停止してしまうと利用することができません。この不測の事態に備えて、稼働しているサーバよりも手前でメンテナンスに切り替える必要がありました。

f:id:tsukahara1991:20210428171103p:plain
インフラ構成

上の図はプラットフォームのインフラ構成になっています。この構成を考慮してCloudFrontを活用してメンテナンスに切り替える方法を模索しました。

CloudFrontを活用したメンテナンス切り替えを模索する

バックエンドサーバーを必要としないメンテナンスページの運用方法については、AWSサポートセンターにご質問させていただきました。M&Aクラウドのインフラ構成を考慮して、4つも運用方法をご提案いただきました。いつも丁寧な対応ありがとうございます!🙇‍♂️

4つの運用方法

  • CloudFrontのカスタムエラーレスポンス機能を利用する
  • メンテナンス用のCloudFrontディストリビューションへの切り替え
  • Lambda@Edge を使った静的ウェブサイトコンテンツの提供
  • CloudFront 以外のリソースへの切り替え

4つの運用方法と実際に試して得られた知見をメリットとデメリットに分けて簡単にまとめさせていただきます。

CloudFrontのカスタムエラーレスポンス機能を利用する

  • 運用方法
    CloudFrontのカスタムエラーレスポンス機能でHTTP Error Codeを受け取った際に、指定したページにレスポンスさせる

  • メリット

    • CloudFrontの更新だけで対応することができる
    • レスポンスを上書きすることができる(404エラーを503エラーにしたり)
  • デメリット
    • システムからのHTTPレスポンスを受け取らないとカスタムエラーレスポンスが働かないため、システムからのレスポンスが遅れた場合に何も表示されない時間が発生する
    • メンテナンス切り替えの度にカスタムエラーレスポンスを設定する必要がある

参考
カスタムエラーレスポンスの生成

メンテナンス用のCloudFrontディストリビューションへの切り替え

  • 運用方法
    メンテナンス用のCloudFrontディストリビューションを作成しておき、Route53のレコードを変更する

  • メリット

    • 稼働中のCloudFrontの設定を変更しないため、メンテナンス後の復旧は容易
  • デメリット
    • CNAMEの削除・登録(CloudFront)、レコード変更(Route53)と複数のアプリの設定変更が必要
    • CNAME切り替えが反映されるまでに少々時間がかかる可能性がある

Lambda@Edge を使った静的ウェブサイトコンテンツの提供

  • 運用方法
    Lambda@Edgeを適用させることで、メンテナンスページにリダイレクトさせる。

  • メリット

    • 任意のレスポンスにすることができる(メンテナンスページを表示して、503エラーを返す)
  • デメリット
    • CloudFrontのビヘイビア1つ1つに「Lambda Function Associations」を適用する必要がある(ビヘイビアが複数あるとその数だけ変更が大変に)
    • 新しくビヘイビアを追加するたびにLambda@Edgeの設定が必要になる

参考
チュートリアル: シンプルな Lambda@Edge 関数の作成 レスポンスの生成 - 例: 静的コンテンツの提供 (生成されたレスポンス)

「CloudFront 以外のリソースへの切り替え」

  • 運用方法
    切り替え先のリソースにCloudFront以外のサービス (例: ロードバランサー、EC2、ドメイン名と同じ名前を持つ S3バケット 等) を使用し、Route53のレコードを変更する

  • メリット

    • 稼働中のCloudFrontの設定を変更しないため、メンテナンス後の復旧は容易
  • デメリット

結果的に採用した運用方法

4つの運用方法を試した結果、「カスタムエラーレスポンス機能」と「メンテナンス用ディストリビューションへの切り替え」の良いとこ取りしたハイブリットな運用方法となりました。 メンテナンス時の処理フローは以下の図のようになっております。

f:id:tsukahara1991:20210430124618p:plain
メンテナンス表示までの処理フロー

ポイント

  • CloudFrontはどのようなURLを受けても、S3のindex.htmlをリクエストする
  • S3にはindex.htmlを用意しない(必ず404を返却する)
  • カスタムエラーレスポンスでレスポンスを503にする(SEOを考慮)

懸念していたディストリビューション切り替え時のダウンタイムですが、2の運用を実施するのはサーバー停止してしまっている時なので、多少のダウンタイムは考慮しないことになりました。

色々と試行錯誤しましたが、プラットフォームに適したメンテナンス運用方法をまとめることができました。 「もっと良い方法があるよ!」、「もっと詳しく知りたい!」という方はお気軽にご意見いただけると嬉しいです。
@AkitoTsukahara

最後に

M&Aクラウドではエンジニアを絶賛募集中ですので、興味がありましたら是非以下からご応募ください!

www.wantedly.com

www.wantedly.com

Nuxt.js化計画vol.6

f:id:kubotak:20200317182842j:plain
Nuxt.js化計画vol.6

第6弾です。

Nuxt.js化計画の概要についてはvol.1を参照ください。
また、過去のシリーズも通してリンクしているのでぜひ御覧ください。

tech.macloud.jp

今回はリプレースではなく、新規機能に伴う新しいページをNuxt.jsで構築しました。
かんたんM&A診断という機能です。

macloud.jp

7つの設問に答えることで売却したい会社・事業に対してマッチする買い手企業をレコメンドするという機能です。

今回はNuxt.js側では初の試みとしてABテストを行っています。
ABテストについては次の記事で紹介しています。

tech.macloud.jp

かんたんM&A診断のLPでデザインを異なるパターンでABテストを行います。
ちなみにこのLPはジョインして日の浅い塚原さんがコーディング・実装してくれました。即戦力!

f:id:kubotak:20210422110220p:plain
LPパターン

設問は以前売却・調達情報の案件登録ページでも実装したようにSPAとしてクライアント側でスムーズに遷移します。

f:id:kubotak:20210422174615g:plain
設問の遷移

f:id:kubotak:20210422174631g:plain
キーワードサジェスト

設問2で表示されているキーワード登録ではサジェスト機能が働いてるのがわかるかと思います。
データベースに登録されているキーワードから検索してGoogle検索窓のようにサジェストでキーワード候補を表示します。

サジェストの機能はvue-simple-suggestを利用しています。
シンプルの名の通り必要な機能(API)が一通り揃っていて使いやすく、ポリフィルも用意されているのでIE11+でも動作します。
弊社プラットフォームの利用者の性質上、まだまだIEは切り離せないのでありがたいです。

f:id:kubotak:20210422151204p:plain
結果のOGP

設問を完了すると結果ページが表示されます。
このページはSNSシェアされることを想定していますので、それぞれ結果に応じてOGPで利用される画像が異なります。

Nuxt.jsはユニバーサルモードで運用しているのでこのように動的なSSRが必要な機能も作れるので強力です。

最後に

M&Aクラウドではエンジニアを絶賛募集中ですので、興味がありましたら是非以下からご応募ください!
Nuxt.jsで開発したい方は是非どうぞ

www.wantedly.com

www.wantedly.com