PHPerKaigi 2021 と「全員インフルエンサー」という開発チームバリュー

f:id:kazuhei0108:20210118230904p:plain

こんにちは。エンジニアの鈴木(@yamotuki)です。

昨年はPHPカンファレンス2020にチーム全員でプロポーザルを出し、4人が登壇することになりました。
今回は PHPerKaigi 2021 にまた全員でプロポーザルを出した話です。この記事を書いている時点ではまだ採択されたか分かりませんので、もし採択されたら追記します。

追記:
5人がプロポーザルを出して、3人が20分枠、2人がLTで発表することになりました。紹介タイトルの先頭に[採択]または[不採択]をつけました。
頑張って発表準備し為になる発表をしたいと思います。お楽しみに!

開発チームのバリューについて

M&Aクラウドの開発チームには行動指針として以下の三つがあります。

これらの詳細はまた別の記事で紹介されると思いますが、今回は全員インフルエンサーについて書きます。

経緯

以前から各種勉強会で活発に発表し、コミュニティの発展に貢献していた久保田さん(@kubotak_public)が弊社に2019年10月にジョインされました。

CTOの荒井はかねてからチーム作りの重要性を感じており、久保田さんジョインと同時期にこのブログを始めることになりました。

運用

エンジニアは情報発信が大事、と近年のWeb界隈ではよく言われていると思います。目的はスキルアップだったり、プレゼンス向上だったり、採用だったり、チーム技術交換だったり、色々あると思います。

しかし、実情としては10人に1人、会社によっては100人に1人くらいしかブログを書いたり登壇したりすることはないのではないでしょうか。 弊社ではより強いチームにするために「全員」が情報発信を積極的に行っていくことに決めました。

それが開発チームのバリューとして2019年11月の開発合宿(という名の長時間ミーティング)で「全員インフルエンサー」として言語化されました。

この行動指針に則って「全員毎週 qiita 記事投稿」(現在は不定期)や、PHPカンファレンス 2020への「全員プロポーザル宣言」が実現されました。 まだエンジニア5人のチームですが、これが10人100人になった時にもこの行動指針を続けられたなら唯一無二のチームになれると信じています。

PHPerKaigi 2021への全員プロポーザル

次の大きな祭りはなんだ?と問われれば、PHPer なら PHPerKaigi 2021 が思い浮かぶのではないでしょうか。 今回はCTOが明示的に「全員プロポーザル宣言」を出さなくても、当たり前のように全員が出すことになりました。ちなみに"沈黙"をしてひよっているのが私です。

f:id:yamotuki:20210115174429p:plain
全員プロポーザル宣言(自発)

さて、どのようなプロポーザルになったのか、紹介していきたいと思います。

[採択]モックの泥沼から脱却するために、あえてDBにつないでテストしている話

fortee.jp

CTOの荒井さんによる、前回PHPカンファレンスで話せなかったリベンジです。 テストを美しく、効率的に書くための工夫について紹介されています。 モックの泥沼に入るとテストのどこが何を表しているか分かりにくいですよね。 モック地獄から救ってくれた荒井さんに清き一票を。

[採択]Laravel のメール認証の内部実装を掘り下げる

fortee.jp

Vimマスターの濱田さんによるプロポーザルです。 今回はフレームワークの内部実装を掘り下げていこうという渋い内容になってます。 プロダクトが立ち上がった時には要らなくても、成長すればじきに必要になってくるメール認証。 案外幅広い開発者に関係するところだと思うので、見たい人はぜひ清き一票を。

[不採択]Casbinを用いたアクセス制御入門

fortee.jp

このプロジェクトを通して設計作業を学びたい!という積極性No.1の津崎さんによるプロポーザルです。 権限管理は自分で丸々実装すると大変かと思いますが、幅広い言語で使用可能な Casbin というライブラリを使うと比較的簡単にできるそうです。 弊社のプロダクトではサービスの管理ツールでCasbinによるアクセス制御を導入しているところです。 こちらも会社成長すると"あるある"の機能なので、ご期待ください。

追記:
このプロポーザルは残念ながら不採択でしたが、このブログが公開された後に出した以下の発表で採択されました。

fortee.jp

[採択]ある日突然、Laravel Queue Workerが壊れた

fortee.jp

「全員インフルエンサー」の始祖、久保田さんによる発表です。 年末に開発チームが襲われた Worker の障害の根本対応の想定される工数は、チーム全員でやっても1週間(辛い)。 そこで「レバレッジ指向」の久保田さんが華麗な解決策を編み出し半日で解決してしまった話です。 その発想の源が垣間見れるのか。楽しみですね。

[採択]プログラマ三大美徳を実現するデプロイフローを目指して

fortee.jp

私の発表です。 デプロイめんどくさいです。昔はもっと面倒でした。 フローなんてあってなかったようなプロダクト初期から、ミスなく楽するために徐々に改善してきた軌跡を共有します。

終わりに

いいなと思ったら、清き一票をお願いします!

弊社では一緒に「全員インフルエンサー」を実現してくれるチームメンバー募集してます。

www.wantedly.com

新年の挨拶2021

あけましておめでとうございます。M&Aクラウドのかずへいです。

去年から大変な時期が続いておりますが、M&Aクラウドは順調に成長しておりまして、 仲間の増加に合わせて、この度オフィスを八丁堀から新宿御苑に移転することとなりました。

f:id:kazuhei0108:20210112134651j:plain
新オフィスのエントランス

せっかくオフィスを移転したのですが、今はほとんどのメンバーはリモートワークをしております。

緊急事態宣言も出ておりますが、より柔軟に、仕事の進め方、コミュニケーションの仕方を変えながら、 いつかまたオフィスでみんなで集まってわいわいする時を思い描きながら頑張っていきたいと思います。

今年の抱負

私の今年の抱負は「発進・発信」です。

今年はM&Aクラウド開発チームとしても飛躍の年にしたいと思っていまして、そのためにはたくさんの新しいことにチャレンジし、 その取り組みを外部に出していくことでチーム全体の増強につなげていきたいと思っています。

また、メンバーの人数が増え、コミュニケーションが少なくなると、メンバー間のずれが起きやすくなりますが、 それを防ぐためには、常に理想を発信し、定着させていくことが必要です。

このブログを始めるときに自分が思い描いたループを意識して、ガンガン回していけたらと思います。

f:id:kazuhei0108:20191024121803p:plain
取り組みを発信して採用につなげる図

それでは本年もよろしくお願いいたいします。

【Laravel】Laravelのスケジューラー経由で実行しているコマンドのログを詳細に取る

こんにちは、M&Aクラウドの荒井です。

以前に弊社の津崎がElasticBeanstalkのワーカー環境の導入について紹介させていただきました。

tech.macloud.jp

弊社ではその後様々な定期実行のスケジュールを移行し、安定稼働に至っています!

ワーカー環境で実行されるコマンドが増えるにつれて、コマンドの実行状況をより詳細に確認したいというニーズが出てきました。

以下でどのように対応したのかを説明します。

Workerサーバーでスケジュールを実行する

弊社ではlaravel-aws-workerを利用しているので、Laravelのスケジュールの機能でElasticBeanstalk Workerからスケジュールを実行することができます。

GitHub - dusterio/laravel-aws-worker: Run Laravel (or Lumen) tasks and queue listeners inside of AWS Elastic Beanstalk workers

app/Console/Kernel.phpに以下の様に書いていくことでコマンド実行の設定ができます

<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{

    /**
     * Define the application's command schedule.
     *
     * @param \Illuminate\Console\Scheduling\Schedule $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {

        $schedule->command('some-command')
            ->weeklyOn(1 /* 月曜日 */, '10:00')
            ->withoutOverlapping()
            ->onOneServer()
            ->runInBackground();
     }
}

?>

このようにして、月曜の10時にsome-commandを実行するという設定ができます。

課題

ここで問題になるのは、このコマンド実行はHTTPリクエスト経由の実行ではなくconsoleからのphpの実行だということです。Webサーバー経由での実行ではないので、Apacheアクセスログ等は出ません。当たり前といえば当たり前なのですが…。標準出力にコマンドを実行したときのoutputが出るだけです。

しかし、これでは実際にコマンドがどのように実行されているのか細かく見ることができません。よって以下のような解決策を取っていきました。

解決策

実行時の標準出力相当のものをログに書き出す

コマンドのコードには、ログへの出力をするコードは書かれていませんでしたが、コマンドを人間が実行するためのテキスト等を出力するコードはちゃんと書かれていたのでこれを応用することにしました。command実行時の出力を同時にログファイルにも出力します。

Illuminate/Console/Command.phpを継承して以下のようなLoggableCommand.phpクラスを定義します。

<?php
declare(strict_types=1);

namespace App\Console\Commands\Base;

use Illuminate\Console\Command;
use Illuminate\Log\LogManager;

abstract class LoggableCommand extends Command
{
    public function info($string, $verbosity = null)
    {
        parent::info($string, $verbosity);
        $this->getLogger()->info($string);
    }

    public function warn($string, $verbosity = null)
    {
        parent::warn($string, $verbosity);
        $this->getLogger()->warning($string);
    }

    public function error($string, $verbosity = null)
    {
        parent::error($string, $verbosity);
        $this->getLogger()->error($string);
    }

    /** コマンド実行時までLoggerが生成されないので、ログに必要なときに都度生成 */
    protected function getLogger()
    {
        $application = $this->getLaravel();
        return $application->make(LogManager::class)->channel('command_log');
    }
}

?>

このクラスを継承することにより、コマンドを実行するとログファイルにコマンドの標準出力に出ているものと同じものがLogのformatにwrapされて書き出されます。 ログは以下のようにcommand_logのchannelに出力されます。

time:2020-11-24 21:37:15 level:INFO  message:START some-command params:[]

Exceptionをキャッチする

コマンド実行時に発生するExceptionは何も設定しないとWebサーバー経由で起動されているPHPのerror_logと同じ箇所に出力されてしまいますが、先程のエラーと同様にcommand_logのchannelにコマンドログは統一したいです。

これにはapp/Exception/Handler.phpをカスタマイズすることで対応します。

<?php

namespace App\Exceptions;

class Handler extends ExceptionHandler
{

    public function renderForConsole($output, Exception $e)
    {
        // コンソールコマンドはスケジューラーから実行される前提
        $logger = $this->container->make(LogManager::class)->channel('command_log');
        $logger->error("{$e->getMessage()} | {$e->getTraceAsString()}");
        parent::renderForConsole($output, $e);
    }

}

?>

renderForConsoleを実装することによってConsoleでのエラーのみこちらの関数が呼ばれるようにできます。

まとめ

Laravelのスケジューラー経由で実行されるコマンドのログを詳細に取る方法を説明しました。

  • コマンドの標準出力に出力するとともにログに出力することで簡単にログへの書き込みが実装できました。
  • app/Exception/Handler.phpをカスタマイズすることでExceptionが発生した場合にもログ出力することができました。

これでスケジューラーから実行されるコマンドの内容も詳細にログに記録することができます。

最後に

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

www.wantedly.com

www.wantedly.com

Nuxt.js化計画vol.4

こんにちは、こんばんは、kubotak(@kubotak_public)です。

早いものでシリーズも第4回となりました。 前回の記事は以下です

tech.macloud.jp

今回はログイン周りの仕様変更があり、それに伴いNuxt.js化を行いました。

新規登録の仕様変更

新規登録に関して仕様変更しました。
今までは会社を売りたい・資金調達したいユーザーは弊社サービスに登録する際に会社情報の入力を行う必要がありました。
この問題として、例えば「直近では売却意欲は低いがとりあえず最新情報の通知だけ受け取っておこう」といったカジュアルなニーズに答えることができません。

新たに仮登録というワンステップ置いた状態が追加されました。
まず会員登録すると仮登録という状態のアカウントが発行されます。
仮登録はメールアドレスとパスワード、またはFacebook認証で登録が可能となり、登録する際の敷居が下がりました。
続いて本登録(今までの会社情報入力フロー)を行うことで弊社サービスの機能をすべて利用可能になります。

登録・ログイン周りの変更

今まではLaravelで作られていた登録周りのフロントエンドを今回の改修でNuxt.js化しました。

以前から紹介しているようにフォームの各パーツはそれぞれコンポーネント化しているのである程度の形までデザイナーを介さずに実装することが出来ました。
また、これらのインプットパーツはVeeValidateを利用したインタラクティブなバリデーションが行なわれます。

ログインフォーム

登録・ログインのPOST

弊社のログインの仕組みはLaravelの標準のAuthを利用したCookieによるログイン判定となります。
これを提供するためにはブラウザにLaravelで発行したCookieを付与する必要があります。 LaravelとNuxt.jsで分離されていると、このCookieの付与ができません。

そのため、Nuxt.jsのフォームからは通常のPOSTフォームを設置しLaravelのPOSTエンドポイントに遷移して、そこでCookieを付与するようにしています。
つまり、新規登録やログイン時はSPAではなく通常のページ遷移となります。

f:id:kubotak:20201112110347p:plain

サーバーエラーやバリデーションエラーの挙動

これまで実装してきたパターンではフォームからの情報はHTTP APIにより通信し、その結果をNuxt.jsでハンドリングしていました。
しかし、登録・ログイン周りでは先述の通り従来のページ遷移となるため、サーバー側(Laravel)のエラーをフロント側(Nuxt.js)に通知する必要があります。
従来のLaravelのアプリケーションであればセッションフラッシュを利用してページを戻した先でバリデーションエラーを表示できるのですが、アプリケーションが分断されているのでこの方法は利用できません。
そのため、弊社ではエラーをCookieとして扱って状態を引き継ぐ実装をしています。
Laravel側ではエラーになった場合はCookieにエラー情報を入れて元のページに戻します。
Nuxt側ではエラーのCookieがブラウザにある場合はエラーのトーストを表示してそのCookieを削除します。これでセッションフラッシュで行っていたような挙動を再現しています。

f:id:kubotak:20201112110401p:plain

最後に

シリーズでお届けしているNuxt.js化についてはPHP Conference 2020の私のセッションで発表させていただきますのでぜひご視聴ください。 fortee.jp

また、一緒にフロントエンドを開発してくれる仲間も募集中です。
興味がある方は以下よりご応募ください。 www.wantedly.com

A/B テストの基盤を構築した話

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

先日M&Aクラウドのサイト上で A/B テストを本格的に導入していこうという話になり、そのシステム基盤を構築する機会がありました。 今回は A/B テストの基盤構築に至った経緯とどのように実装したのかについて紹介したいと思います。

A/B テストの基盤構築に至った経緯

M&Aクラウドの開発チームでは事業上の目標到達に向けた施策実施とその効果測定を継続的に行っています。

tech.macloud.jp

A/B テストの導入によって一度に複数の案を検証することが可能となるのに加え、外的要因に左右されにくいデータが得られるため、より効果的に検証サイクルを回すことができると考えています。

さて A/B テストを実施する方法として、 Google Optimize などのツールを利用することも選択肢として挙げられます。 しかしクライアントサイドでページを差し替えるパターンではページフリックのような UX 低下を招く可能性があること、振り分けたユーザー集団が特異性を持たないように振り分けロジックを自分たちで開発・保守したいというニーズもありました。 このような理由により、ユーザーの振り分けやページの差し替えといったロジック基盤を自前で開発することになりました。

設計と実装

A/B テスト基盤の開発において工夫した主なポイントは次のようなものです。

  • セッションの特定、および偏りのないユーザー振り分け
  • Laravel と Nuxt.js の両方で同じように機能すること
  • A/B テストの開始と終了のイベントの集計

これらについて順番に解説します。

セッションの特定、および偏りのないユーザー振り分け

あるユーザーに対して行ったページの出し分けとアクションの結果を紐付けるために、そのセッションを一意に特定する必要があります。 これを実現するために A/B テスト用の Cookie を発行して、ユーザー(クライアントのブラウザ)ごとに一意な ID を割り当てています。

テストパターンの振り分けを行う際は、まずこのユーザー ID ClinetId とテスト名 TestName を元に 0 から 1 までのランダムな実数値を出力し、その値を元に以下のようなロジックでテストパターン Varinat のどれかに振り分けています。

<?php
function chooseVariant(
    TestName $testName,
    ClientId $clientId,
    array $variantList
): Variant {
    // TestName と ClientId を元に 0 から 1 までの実数値に変換する。
    $frequencyPercentage = FrequencyPercentage::makeFromString(
        $testName->rawValue() . $clientId->rawValue()
    );

    $max = new VariantPercentage(0);

    foreach ($variantList as $variant) {
        /** @var Variant $variant */
        $min = new VariantPercentage($max->rawValue());
        $max = $max->add($variant->getPercentage());

        // どの Variant を何%の確率で出現させるかあらかじめ定義してある。
        // 例えば VariantA と VariantB にそれぞれ50%を設定した場合、
        // 数値が 0-0.5 なら VariantA に、 0.5-1 なら VarinatB に振り分けられる。
        if (
            $frequencyPercentage->isGreaterThanOrEqualTo($min)
            && $frequencyPercentage->isLessThanOrEqualTo($max)
        ) {
            return $variant;
        }
    }

    throw new LogicException('A/B テストのパターンを一意に決定できませんでした。');
}

任意の文字列を 0 から 1 までの数値に変換するロジックの実装(上記コードの FrequencyPercentage::makeFromString の中身)に関しては Qiita に記事を投稿しているので、是非のぞいてみてください。

qiita.com

Laravel と Nuxt.js の両方で同じように機能すること

M&Aクラウドは大部分が Laravel で実装されていますが、一部のフロントエンドは Nuxt.js への移行が進んでいます。 Nuxt.js 移行の詳細に関しては次の記事をご覧いただければと思います。

tech.macloud.jp

tech.macloud.jp

tech.macloud.jp

プロジェクトは今も進行中のため、現時点ではフロントエンドに Laravel と Nuxt.js が混在する状態となっています。 したがって例えば出し分け対象のページを Laravel 、ユーザーアクション後に訪れるページを Nuxt.js が生成しているといったケースも考えられ、このようなケースでも A/B テストが機能するように配慮する必要がありました。 このためどちらのシステムが発行した Cookie であるかに依らず、それぞれのシステムで同じように Cookie を取り扱えるような工夫をしています。 こちらの実装の詳細に関する記事も Qiita に投稿しているので、是非のぞいてみてください。

qiita.com

A/B テストの開始と終了のイベントの集計

A/B テストの結果の集計は Google Tag Manager (以下 GTM と表記)と Google Analytics (以下 GA と表記)を用いて行っています。

GTM の方には特定のイベントを受け取ったらそのデータを GA のイベントデータの形式にフォーマットして送信するように設定しておきます。 クライアントサイドの JS からは以下のようにして GTM にイベントを飛ばすことができます。

<script>
  window.dataLayer.push({
    event: 'ABTesting',
    testCaseDefinition: testName,
    valiant: variant,
    testState: 'start'
  })
</script>

開始(対象ページへのアクセス)または終了(アクションの実行)のタイミングで GTM にイベントを飛ばすことで、 A/B テストの試行回数とアクションの結果を GA 上で確認することができるようになります。

まとめ

M&Aクラウドにおける A/B テストの基盤作成の方法について紹介しました。 A/B テストを自前で実装することを検討している方の参考になれば幸いです。

PHPカンファレンス2020に弊社のエンジニアが4名登壇します🎉

こんにちは、M&Aクラウドの津崎(@820zacky)です。

弊社エンジニアの4名がPHPカンファレンスというイベントに登壇することとなりました🎉

今回は、登壇が決定するまでの流れと、どんな発表をするか?というところを書きます。

PHPカンファレンス2020とは?

phpcon.php.gr.jp

PHPカンファレンスは日本最大のPHPのカンファレンスです。

今年開催されるPHPカンファレンス2020は、12月12日にオンラインにて開催されます。

PHPカンファレンスでは、登壇したい人がプロポーザルを提出し、それを運営者が選定することでスピーカーを決めています。

今年は、9月16日から10月5日にかけて募集され、10月18日に採択が発表されました。

いいチームの作り方 と 全員プロポーザル宣言

https://cdn-ak.f.st-hatena.com/images/fotolife/k/kazuhei0108/20191025/20191025190323.png

今回、弊社では全エンジニアがプロポーザルの提出を行いました。 これは、我々エンジニアチームの「いいチームの作り方」に基づいて、「発信」に力を入れているためです。 我々が考える「いいチームの作り方」については、ブログ記事に説明があるのでよろしければご覧ください。

tech.macloud.jp

そういう背景があり、CTOの命に依り「全員プロポーザル宣言*1」が発令されました。

プロポーザル紹介

弊社のエンジニアが提出したプロポーザルを紹介します。

[採択]再コンパイル不要! core dump さえ吐ければ gdb デバッグできます

弊社インフラの神、鈴木さん( @yamotuki ) による発表です。

segment fault怖いですか? 私は怖いです。 でも大丈夫。鈴木さんが悪魔の倒し方を教えてくれます。 悪魔との格闘の末、銀の弾丸を手にした鈴木さん。彼の目にsegment faultはどう映るか。

fortee.jp

[採択]Laravelで運用しているサービスをNuxt.jsにリプレイスする

Nuxt.jsへのリプレイスを先導する弊社のフロントエンドモンスター久保田さん ( @kubotak_public ) による発表です。

リプレイス大臣がNuxt.jsへのリプレイスの奇蹟を爆笑を交えてお届けします。

fortee.jp

[採択]PHPer のための Vim 実践入門

弊社のタイピングマスター濱田さん( @hamakou108 ) による発表です。

弊社でもっともタイピングが早い男が愛用するVim。 みんながちょっと憧れて、みんながちょっと怖がってる。 そんなVimを愛し、Vimに愛された濱田さんによる、VimIDE的なことを実現する方法の紹介です!

fortee.jp

[採択]めざせブレークポイントマスター

私(津崎 @820zacky)の発表です。

あなたはXdebugしてますか?

Xdebugを使いこなすために便利な、ブレークポイントの知られざる機能を発見したので共有します。

デバッグでみなさんを幸せにしたい気持ちで溢れたやさしいプロポーザルです。

fortee.jp

[不採択]LaravelとAWSでサービスとともに設計を成長させる

弊社で一番テーブルフットボールが強いエンジニア、CTOのかずへいさん( @kazuhei__ ) によるプロポーザルです。

弊社サービスについて、成長に伴ってどのように設計が変わっていったのか熱く語ってくれるはずでしたが、残念ながら不採択となってしまいました。

fortee.jp

[不採択]テストを設計する

こちらもCTOによるプロポーザルです。

弊社サービスのテスト設計については、エンジニア内で多くの時間を使って議論しました。テストについて考え尽くした男の発表に乞うご期待。と言いたいところでしたが、こちらも残念ながら不採択でした。

fortee.jp

終わりに

弊社では全エンジニアが頑張ってプロポーザルを出しました。 5人中4人も登壇できるのは中々の快挙ではないかなと思います。

弊社では、PHP以外にもLaravelやVueのイベントなんかにも積極的に参加していますので、 弊社のエンジニアを見かけた際はお気軽にお声がけください。

また、弊社ではエンジニアの採用を行なっています。 ベンチャー企業でのWeb開発に興味のある方は、ぜひカジュアルにご応募ください🤝

www.wantedly.com

www.wantedly.com

*1:全エンジニアを対象とし、プロポーザル最低限一個、絶対に出しましょうというキャンペーン

SEOの観点からの高速化の取り組み(募集詳細ページの高速化編)

こんにちは。エンジニアの鈴木(@yamotuki)です。

以前、こちらの記事 で速度改善の前の測定のためにSpeedCurveを導入したという話を書きました。 今回はSEOの観点から Google Search Console で警告が出たページについて速度改善を試みた話を書いていきます。

ページ速度とSEOの関係

Google Search Console のなかの「ウェブに関する主な指標」というところでヘルプに書かれている指標は以下の3種類でした。以下の3つはCore Web Vitals とも呼ばれています。

LCP(Largest Contentful Paint): ページが最大の視認可能な要素をレンダリングするまでの時間
FID(First Input Delay: 初回入力遅延): ユーザーの操作に対してページがレスポンスを開始するまでの時間
CLS(Cumulative Layout Shift): ページの読み込み中にページの UI 要素がどの程度移動するかを示します。

ページ速度でいうとLCPとFIDが関わりますが、今回の記事での話はLCPの値について焦点を当てて対応したところについてです。

この指標のいずれかに問題があると「不良」や「改善が必要」となるようです。 具体的には、LCPだと閾値が4.0秒以上で不良、2.5秒以上で「改善が必要」と表示されていました。

具体的に速度の問題がなくなるとどれくらい検索順位が上がるかは明確には分かりませんがこちらのドキュメントgoogleの考え方が示されています。

取り組みの結果

先に、今回の記事で取り上げる改善だけでなく、一連の改善を行なった結果について記載しておきます。

LCPの改善だけではなく他の指標に対する改善も合わせてチームで取り組みました。その結果、以下のように大幅に指標の改善を行うことができました。

f:id:yamotuki:20201009174908p:plain
Google Search Console での経時での状態

LCP改善への取り組み

取り組む対象の特定

Google Search Console では問題があったページがどこなのか教えてくれます。 今回は、募集詳細ページ()のLCPに問題があることが分かったので取り組むことにしました。

LCPを分解する

LCP は以下の定義でした(再掲)。

LCP(Largest Contentful Paint): ページが最大の視認可能な要素をレンダリングするまでの時間

要するに、ファーストビューの最大の要素が見える状態になるまでの時間です。目立つのでそれが読み込まれると「大体表示されたな」と感じるユーザも多くなるラインです。

今回の対象のページでいうとファーストビューの大きな画像の部分です。

f:id:yamotuki:20201009180322p:plain
ファーストビューの画像

LCPを本当にざっくり分解すると以下のようになりそうです

  • バックエンドの応答を受け取る
  • HTMLの内容からJavaScriptCSS, 画像などダウンロードし始める
  • JavaScriptが差し込むHTMLも含めて解釈する(ダウンロードする時の設定による。詳細については、私はまだ理解が甘いですし長くなるので省きます)
  • まだダウンロード完了していない画像など待つ
  • 表示する

どこが遅かった?

今回のケースだとバックエンドが明らかに遅く、TTFB(Time To First Byte)が3~4秒でした。表示している内容からすると1秒切っても良さそうなものなので、これを改善することにしました。他にも沢山改善ポイントはありますが、速度改善は改善効果の大きいものから行うのが原則です。

f:id:yamotuki:20201009181049p:plain
左のほうが改善前。3~4秒かかっている

対応

そうと分かればバックエンドの改善です。 TTFBを遅くしていた原因は、ページ下部にあった「類似の募集」の項目でした。画面表示に必要な情報を集めるためにSQLを多く発行するN+1問題が発生していました。 今回はちょうど機能改善のタスクもあり、速度改善と合わせて行うことになりました(実際には、バックエンド部分の実装自体は同僚の@kubotakさんが行いました。私は事前の速度測定とマークアップなど担当したタスクでした。) そこまで複雑な処理でもなかったので問題の詳細箇所特定は Laravel Debugbar で適当に挟んで計測したと聞いています。

qiita.com

改善の結果、バックエンドのレスポンスが3~4秒程度だったものが1秒切るくらいになりました。

終わりに

LCPを改善しろ、となって「画像が重いから改善しよう」など場当たり的に対処するとなかなか目標を達成することは難しいと思います。 推測するな計測しろ、の原則に則ってSpeedCurveにはこれからもお世話になっていきます。

弊社では一緒にエンジニア活動してくれる仲間を探しています。

www.wantedly.com

社内勉強会で「JavaScript Promiseの本」を読了しました。

こんにちは!M&Aクラウドの荒井です。

弊社では週に2回社内で勉強会を開いています。

水曜日の夜は「フロントエンド勉強会」、金曜日の夜は「アプリケーション設計勉強会」と題しまして、お題に合う教材を選定し、みんなで輪読しています。

今年の6月はじめ頃から9月末にかけて、フロントエンド勉強会にて、こちらの「JavaScript Promiseの本」を教材にさせていただきました。

azu.github.io

選定の理由

弊社ではPHP+JavaScript+Vue.jsで作られたアプリケーションをTypeScript+Nuxt.jsで置き換えるということを行っています。

PHPで吐き出したページ上でJavaScriptを実行するという形式だと、大半のロジックはPHP側で制御されるので、ユーザーのフォーム投稿といった一部の操作くらいでしかJavaScriptからサーバーにリクエストするということがありません。

しかしNuxt.jsに書き換えるとデータの取得は全てHTTP API経由になるので、APIでログインしているか確認して、ログインしていたらまたリクエストして、というような複雑なAPIリクエストがありえます。

そこで、Nuxt.js化をすすめる上で、メンバー内である程度のPromiseの知識、async、awaitの知識が必要だと考えました。

学びがあったところ

Promiseの作り方

https://azu.github.io/promises-book/#how-to-write-promise

開発をする上では、大体の非同期ライブラリがPromiseで結果を返してくれるようになりつつあるので、Promiseを作る機会よりPromiseを使うことの方が多いとは思います。

しかし、今でも場合によっては自分でPromiseオブジェクトを作りresolve、rejectを呼ぶ機会はあると思うので、それを難なく理解できるようになったことは良かったと思います。

qiita.com

非同期と例外周りの理解

非同期的な処理のcallback関数内での例外はtry構文でcatchできないことや

techblog.yahoo.co.jp

Async Function内で例外が発生したときはRejectedなPromiseが返るなど、非同期処理時の例外の扱いについて詳しくなることができました。

これにより開発時のデバッグがしやすくなったのではないかと思います。

おまけ

勉強会の途中で見つけたタイポを が修正してPRしたところmergeされました😉

github.com

まとめ

社内で勉強会をやることによって、メンバー内で最低限の知識を共有し、共通言語が生まれることによってコミュニケーションが簡単になります。 また、勉強会はきっかけづくりであり、個別のメンバーがさらに技術を深掘って身につけていってくれればと思います。

社内ハッカソンを通してCS部の業務効率化に挑戦しました

こんにちはkubotak(@kubotak_public)です

連載になってる社内ハッカソンシリーズです。 今回はついに社内優勝したチームの改善についてです。(自慢)

以前の記事をまだ読んでない方はこちら

ハッカソンの大まかな概要は以前の記事から引用

  • 目的は、「エンジニアが他のチームの業務を理解すること & 他のチームがエンジニアの仕事を理解すること」
  • テーマは「チームの業務効率化」
  • 10:00~17:00でヒアリングと実装を行う
  • 各チーム5分で発表

CSチームについて

さて、CSチームですが一般的にはカスタマーサポートかと思われるかもしれませんが、弊社ではカスタマーサクセスの略称の部署です。 CSは弊社サービスのM&Aクラウドの利用者をサポートして円滑にM&Aを行えるようにしています。

今回私kubotakはこのCSチームの業務改善をエンジニアとして行いました。

おことわり

残念ながら今回のハッカソンで行った業務改善の内容が社外秘を多く含む業務のため詳細に紹介することができません。 ですので、技術的視点での解説をしたいと思います。

改善すること

詳細をお話することはできませんが、以下のような作業がありました。

  1. とある部署Aがとある部署Bに依頼をする
  2. 依頼を受けた部署Bはとあるサービスにログインして検索機能を利用、その検索結果をもとに判断
  3. 依頼を出した部署Aに判断した結果を提供する

このとある部署Bの作業を自動化してみようという試みです。
CS部のみならず、他の部署でもこの依頼は行っていました。
つまり、この業務が改善されることはCS部だけでなく、会社全体を効率化できるアイデアということです。

作業を自動化する

ハッカソンでは(2)と(3)について自動化を試みました。
アーキテクチャは以下のようになります。

f:id:kubotak:20200923184249p:plain
アーキテクチャ

特定の操作を行った場合にJobを作成し、非同期的にJobでAWS Lambdaを起動します。
AWS Lambdaでは※Puppeteerを利用して(2)で人力作業していたものを自動化します。
その結果を受け取ってSlackに通知される仕組みです。

※Puppeteer
PuppeteerはHeadlessChromeを利用して機械的にブラウザを操作できるNode.jsライブラリです。

プレゼン時の嘘

実はこの構成はすべて完成していませんでした。
時間の都合上すべてを実装する時間がなかったので、プレゼンで見栄えよく騙せるできる機能のみ実装しました。
できている箇所とできていない箇所は次のようになってました

できている

  • LambdaとPuppeteerによる自動化
  • Slack通知

できていない

  • Job発火

プレゼン時は特定の操作の後、即座にSlack通知が行われるように細工しました。
Slack通知が行われたということは、裏でどんな処理が行われているのか・・・
これをローカル環境のPuppeteerでChromeが立ち上がって自動で処理される様子を見せて全体の流れを説明します。
オーディエンスには、この一連の処理が本当に自動化されたかのように見えていますが、実際にはダミーのSlack通知が行なわれているだけという訳です。

この嘘はバレることなく、無事プレゼンは終了し社内優勝を頂きました。
とはいえ機能自体は完成しているので本番転用も可能です。

本番への投入

若干アーキテクチャは変更になりましたが現在このプログラムは本番運用されています。
変更になったのはSlackからAWS Lambdaを起動するようになり、結果をSlack通知する処理もAWS Lambda側になったところです。
つまり、Laravelを一切介さないアーキテクチャになりました。
完全自動化ではなく、(1)の依頼ベースで起動して自動返答される様になっています。
実はハッカソンの時はこの依頼ベースもなくして完全に自動化できないかという観点で作っていましたが業務上は依頼ベースが良いということでSlackからの起動に変わりました。
AWS Lambda自体がSlackへ返答する仕組みを追加しましたが、機能は独立しているのでAWS Lambdaを起動するものを変えるだけで簡単に本番転用できたのは良かった点でした。

まとめ

CS部は他の部署と違い、所属しているメンバーのITリテラシーが特に高く、「GoogleAppsScriptなどを利用してスプレッドシートに一工夫してみようという改善は自分たちでもできそうなのでこのハッカソンではやらない。」と言ったように手間のかからない改善案は却下となりました。
今回ブラウザを自動で動かす、というのをPuppeteerで実現しましたが一緒になってコードを読んで1行1行の行ってる処理を説明するとすんなり理解してもらえたので一緒になってのコーディングが楽しかったです。 また、JavaScriptで実装したことから同じ言語で記述できるGoogleAppsScriptへの興味も高まったようです。

引き続き、無駄な作業は機械にやらせる。をモットーに業務改善をサポートしていきたいと思います。

社内ハッカソンを通してライティング部の業務効率化に挑戦しました

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

前回の記事に引き続き、社内ハッカソンの模様をレポートしたいと思います。

tech.macloud.jp

社内ハッカソンの概要については前回の記事の説明を引用します。

  • 目的は、「エンジニアが他のチームの業務を理解すること & 他のチームがエンジニアの仕事を理解すること」
  • テーマは「チームの業務効率化」
  • 10:00~17:00でヒアリングと実装を行う
  • 各チーム5分で発表

ライティングチームについて

今回のハッカソンではライティングチームとして、ライティング部のメンバーと共に業務効率化を図るための作品を制作しました。 ライティング部では弊社サービスM&Aクラウドに買収・出資を行いたい企業様の募集掲載ページを作成するため、取材やライティング、募集公開に向けたスケジュール管理などを行っています。

macloud.jp

チーム名は横浜エクセレント。偶然にもチームメンバーの名字の頭文字をすべて含む住所を開始5分で発見し、その住所付近にあるエクセレントなビル名を拝借しました。

チームメンバーの頭文字から横浜エクセレントと命名
チームメンバーの頭文字から横浜エクセレントと命名

ハッカソン当日の流れ

課題の洗い出し

まず最初にライティング部の業務フローを教えていただき、それぞれのフェーズで抱えている課題の洗い出しを行いました。

業務フローの各フェーズに紐づく課題
業務フローの各フェーズに紐づく課題

図中で丸で囲まれた箇所が作業のフェーズを表しており、矢印に従って次のフェーズへと進んでいきます。 実際には担当者の移り変わりなどを考慮したより細かいフェーズが存在するのですが、課題の洗い出しに注力するため単純化しています。

この時間では各フェーズについて一つ一つ議論し、時間が掛かる作業、合理的でない作業、ミスが発生しやすい作業などをマインドマップのようにメモしていきました。 一通り課題が出揃ってから全体を俯瞰し、どのペインが大きいかライティング部のメンバーは意見を出し、自分はエンジニアの立場で制限時間内でどのような解決手段が取れそうか意見を出し、協力しながらフォーカスする課題を絞っていきました。

最終的に対象となったのは一番目のフェーズ「取材日程調整」に関わる課題です。

何を制作するか決める

取材日程調整における課題には以下のようなものがありました。

  • 取材関係者のスケジュールが変わりやすいため、日程調整がしづらい
  • スケジュール調整を目検で行っているため、工数が掛かる
  • 上記などが要因で日程調整が長引きやすいため、掲載までのスピードが落ちたり、掲載に至らなかったりする

課題を分析していくことで細かい問題点が募集掲載数という重要な KPI に影響を与えている可能性があるということが見えてきました。 これらの課題を解決できる以下のような特徴を備えた日程調整システムを作ることを今回のハッカソンでは目標としました。

  • 取材関係者のスケジュールを一括管理すること
  • 日程の調整を効率よくスピーディーに行うこと

制作

大きな目標を掲げたものの、このような日程調整システムを一日でスクラッチで開発するのは目に見えて困難です。 そこで今回はスケコンというサービスをベースにして、弊社のライティング業務にフィットしない部分や不足している部分を Zapier というサービスで調整・補完する方針で制作を進めました。

schecon.com

zapier.com

スケコンでは Google Calender と連携して自動的に参加者の空き時間に合わせて候補日程を決定することができます。

また Zapier は複数のサービス連携を GUI から簡単に行うことができるいわゆる iPaaS です。 スケコン経由で Google Calender に予定が追加されると、それをトリガーにして日程調整の後続作業の一部を自動的に行う Zap ( Zapier 上の一連の自動化タスクの通称)を作成しました。

  1. 日程が確定したことを部内の関係者に通知する必要がある。 -> Slack のライティング部のチャンネルに日程および Google Calender の予定の URL を載せたメッセージを自動送信!
  2. 先方に確定した日程および取材にあたっての注意点などをメールで知らせる必要がある。 -> Gmail 上で日程や注意事項が記された下書きメール 1 を自動作成!
  3. 取材先企業様との契約の進捗管理表を更新する必要がある。 -> Google Sheet の契約管理表の取材先企業の列を探して自動更新!

Zapier は他にも様々なサービスと連携が可能で、例えば契約管理が Salesforce に移行した場合に連携先を Salesforce に変更するといったことも簡単にできるので非常に便利です。

結果とその後

残念ながら最優秀賞を勝ち取ることはできませんでした。 しかし作成した Zap について他の部署から真似してみたいという声が上がるなど評価していただける点もあり、有意義な成果物になったと思います。

また課題の洗い出しの際に挙がったもののハッカソンでは解決できなかった項目の一部は Issue としてまとめられ、後々の開発で実装されました。 ハッカソンの目的の一つ「エンジニアが他のチームの業務を理解すること」をより進んだレベルで達成できたように感じています。

ハッカソンのような普段あまりプロダクトチームに届かない声を拾う機会が事業の成長の芽になりうるのだなということを実感しました。 引き続き One Team のバリューに沿って全社一丸となって成長していきたいと思います!


  1. 取引先企業様に合わせて文面を微調整する可能性があるため、送信は行わずに下書きの作成のみに留めています。