顧客体験を向上させるためのWebサイトのパフォーマンス計測

[PLAID Advent Calendar 2018](https://qiita.com/advent-calendar/2018/plaid) の第8日目はソフトウェアエンジニア兼ハンターの [@algas](https://github.com) が担当します。
筆者は執筆時点でプレイドのパフォーマンスチームの1人として、主に管理画面のパフォーマンスの改善に取り組んでいます。

## 概要

本記事ではWebサービスのパフォーマンスについて、以下の内容をご紹介します。

1. 顧客体験を向上させるためのパフォーマンスの計測の考え方
1. 管理画面のパフォーマンス計測を行うために構築したシステムの概要
1. パフォーマンス計測で見つかった具体的な課題と解決方法

## 対象読者

本記事の対象読者にはプログラミング言語の知識を前提としません。
記事の内容の一部で JavaScript (node.js) を使用しています。

- Webサービスを提供している事業者
- Webサービスの開発者

## 顧客にとって有効なパフォーマンス計測をするために

自社のWebサービスの顧客体験(CX)を向上させるには何が必要でしょうか？
魅力的なデザインや洗練された画面フローも大切です。
しかし、そのページを表示するのに何十秒も待つのでは台無しになってしまいます。
お客様を待たせないことも顧客体験において重要な要素の1つです。

それでは、「待つ」ということを定量的に観測するにはどうすればいいでしょうか？
画面のないAPIやCLIでは単純に応答速度を測れば良いのですが、多種のサーバとの通信や画面要素の組み合わせで構成されている現在のWebページでは応答速度を測るだけでは「待つ」ことを表現しているとは言えません。

## ユーザ体感に基づくパフォーマンス計測

ユーザを中心に考えるパフォーマンスのモデルの1つの例として RAIL があります。
RAIL の目標はユーザの満足感を向上させることであり、特定の端末でのWebサイトの処理速度を上げることではありません。

**RAIL = Response + Animation + Idle + Load**

- Response: 100 ms 以内にレスポンスを返す
- Animation: 各フレームの処理は 16 ms 未満
- Idle: メインスレッドの JS 処理ブロックは 50 ms 以内
- Load: 1000 ms 以内にコンテンツを表示する

詳しくは https://developers.google.com/web/fundamentals/performance/rail?hl=ja などに書かれています。

## Lighthouse について

弊社で提供している CXプラットフォームサービス 「[KARTE](https://karte.io)」 の管理画面のパフォーマンス計測のツールとして [Lighthouse](https://developers.google.com/web/tools/lighthouse/?hl=ja) を使うことにしました。
Lighthouse を採用した理由は以下のとおりです。

1. KARTE 管理画面の対応ブラウザである Google Chrome にデフォルトで組み込まれている
2. 上述の RAIL などの考え方を(ある程度)体現している
3. KARTE でも使っている node.js を使ってカスタマイズできる

### Lighthouse で計測できるパフォーマンス項目

Lighthouse ではパフォーマンスを含む複数の観点でのサイトの健全性を計測できる仕組みがあります。
各指標を適切に重み付けして足し合わせたスコアを 100 点満点で表したスコアが表示されます。
今回はパフォーマンス計測が目的なので "Performance" の項目のみに注目しています。
最終的には本番環境の管理画面の Performance スコアを 100 点に近づけるのが目標です。

- Metrics
  - First Contentful Paint: 最初のテキストか画像が表示されるまでの時間
  - First Meaningful Paint: 最初の主なコンテンツが見えるようになるまでの時間
  - Speed Index: ページのコンテンツが目に見える状態になるまでの時間
  - First CPU Idle: ページで最低限の操作ができるようになるまでの時間
  - Time to Interactive: ページが十分に操作可能になるまでの時間
  - Estimated Input Latency: ユーザの入力に対して応答する時間
- Opportunities: Webページの読み込み速度を改善するための指標
- Diagnostics: Webアプリケーションのパフォーマンスに関する詳細な情報
- Passed Audits: 監査を通った(問題のない)指標

### Lighthouse を試してみる

Chrome ブラウザで Lighthouse による計測を行ってみましょう。
以下の手順で計測を行うことができます。

1. 計測したいページを Chrome で開きます
2. 「Chrome DevTools」 を開きます (右クリックまたはダブルタップして「検証」)
3. タブ一覧から 「Audits」 を開きます
4. 「Run Audits」 をクリックします (必要に応じて各項目を設定してから)
5. 少し待つと計測結果が表示されます

また、ブラウザから実行するだけではなく、ホスティングサービスを利用して計測することもできます。
https://web.dev/
本記事では web.dev の説明を割愛します。

## パフォーマンス計測システム

今回はパフォーマンス計測結果を柔軟に取り扱うためにホスティングサービスを使わずに自前で計測システムを構築することにしました。
Cloud Functions で計測の実行と結果の出力を行います。

1. 社内チャットへの通知 (Slack)
2. 計測レポートのHTML表示 (Cloud Storage)
3. 各指標のロギングと統計的な評価 (BigQuery)

パフォーマンス計測を行う Cloud Functions は Cloud Scheduler に登録して定期的に実行されるようにしています。

最後の統計的な評価の部分は現在開発中です。

![Performance measurement with Lighthouse.png](https://qiita-image-store.s3.amazonaws.com/0/8421/36a15a5c-2b00-9c1f-038d-a32df1f53854.png)

### Puppeteer + Lighthouse 

Puppeteer というブラウザを操作できるツールを使って Lighthouse による計測を行う実装をしました。
github にサンプルコードを上げてあります。
https://github.com/algas/lighthouse-puppeteer-example

`/login` ページでユーザ名とパスワードでログインしてから `/users` ページを Lighthouse で計測する実装をしています。

```script.js
const chromeLauncher = require('chrome-launcher');
const puppeteer = require('puppeteer');
const lighthouse = require('lighthouse');
const {URL} = require('url');

let launchBrowser = async () => {
  return await puppeteer.launch({
    defaultViewport: null,
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox'
    ]
  });
};

let login = async (browser, host, path, username, password) => {
  const page = await browser.newPage();
  await page.goto(host+path, {waitUntil: 'load'});
  await page.type('input[name=username]', username);
  await page.type('input[name=password]', password);
  await page.click('button[type=submit]');
  await page.waitForNavigation({waitUntil: 'domcontentloaded'});
  await page.close();
};

let runTest = async (browser, host, path) => {
  return await lighthouse(host+path, {
    port: (new URL(browser.wsEndpoint())).port,
    output: ['json','html'],
    disableDeviceEmulation: true,
    logLevel: 'info',
    throttlingMethod: 'provided',
  });
};


(async (req, res) => {
  const host = 'https://example.com';
  const loginPath = '/login';
  const testPath = '/users';
  const username = 'foo';
  const password = 'bar';

  const browser = await launchBrowser();
  await login(browser, host, loginPath, username, password); // if you need to login
  const {lhr, report} = await runTest(browser, host, testPath);
  console.log(lhr);
  const html = report[1];
  console.log(html);
  if(browser){
    await browser.close();
  }
})();
```

## 具体的な課題と改善方法

上述のパフォーマンス計測システムで見つかった具体的なパフォーマンス上の問題点と改善方法を紹介します。
執筆時に見つかったパフォーマンス上の課題に対して項目ごとに具体的な改善方法を検討しました。
リンクは Lighthouse の各項目に表示されるヒントです。

- Ensure text remains visible during webfont load  
https://developers.google.com/web/updates/2016/02/font-display  
webfont のロード中にもテキストが表示されるようにする。`font-display: swap` を CSS で設定する。
- Serve static assets with an efficient cache policy  
https://developers.google.com/web/tools/lighthouse/audits/cache-policy  
キャッシュのTTLを伸ばす。変更される可能性があるコンテンツはURLにハッシュ文字列を付加することで変更可能にする。
- Avoid enormous network payloads  
https://developers.google.com/web/tools/lighthouse/audits/network-payloads  
コンテンツのファイルサイズを減らす。フォントはサブセットを使い、画像はサムネイルを表示するように変更する。
- Avoid an excessive DOM size  
https://developers.google.com/web/tools/lighthouse/audits/dom-size  
画面内に表示する項目を減らす。重要性の低い項目を遅延読み込みにする。

上記の一部の改善を反映させたところ Performance スコアが向上し、体感速度も多少上がった気がします。
基本的な問題も多く含まれています。自動的に計測できるようになると今後は問題に気づくのが早くなるはずです。
改善が進むごとに別の問題も見えてきます。十分にスコアが向上するまで改善に取り組んでいくつもりです。

## 今後の展望

- CIによるメインのブランチにマージされる前のパフォーマンス計測
- デプロイのフローによるデプロイ直後のパフォーマンス計測

## 参考資料

- RAIL  
https://developers.google.com/web/fundamentals/performance/rail
- Lighthouse  
https://developers.google.com/web/tools/lighthouse/
- Puppeteer  
https://developers.google.com/web/tools/puppeteer/

## まとめ

本記事では、ユーザ視点でのWebサイトのパフォーマンスの考え方と Lighthouse を使ったパフォーマンスの計測と改善の具体的な取り組みについて紹介しました。

ユーザ視点でのパフォーマンス計測とは別にバックエンドのパフォーマンス計測には [Datadog](https://www.datadoghq.com/) を使っています。
詳しくは [大規模解析サービスを支える監視サービスと監視構成のポイント](https://codezine.jp/article/detail/10674) の記事などで紹介しています。