こんにちは,テレビ&ビデオエンターテインメント「ABEMA」で Web エンジニアをしている野口 (@nodaguti) です.

今回は2020年の4月と7月に行ったパフォーマンス改善ハッカソン「Web Speed Hackathon Online」の開催記をお届けします.本イベントは 4/25 – 4/26 で採用イベントとして学生向けに開催されたのち,同じ問題を使って 7/21 – 7/22 に社内イベントとしても行われました.主催・作問を担当した筆者が開催までの流れと当日の様子をお伝えします.

なお記事の最後では実際の問題と解説を公開していますのでお見逃しなく!😎(途中ネタバレがあるので,先に問題を解いてみたいという方はページ下部までスクロールすることをお勧めします)

採用イベント「Web Speed Hackathon Online」

コロナが徐々に広まり始めオフラインでのイベントが困難となっていた3月上旬に,オンラインで開催できるリモートイベントとして企画が始まりました.

これまで弊社では Web 領域において非同期リモート参加型の「Git Push Hackathon」を開催したことがあります.また,ゲーム領域においてはパフォーマンスチューニング競技「ヒダッカソン(社内版, 学生版)」が何度か開かれています.しかし, Web フロントエンド領域のパフォーマンスチューニングイベントは開催経験がなく,さらに今回は「オンライン同期型」という制約が加わったことで,運営としても手探り状態で一からイベントを組み立てていく必要がありました.

まず,チューニングポイントとして出題できそうな内容をとにかく列挙していきました.以下は実際のメモです(初期段階のメモのため,実装過程で実現ができないと分かったりうまく組み込めなかったりしたものもあります).

## Client
### JS
- すべての JS を一つの巨大な bundle に固める
  - chunk splitting optimisation をする
- JS を minify しない
  - terser で minify の設定をする
- NODE_ENV=development でビルドする
  - NODE_ENV=production でビルドするように直す
- JS を blocking resource として読み込む
  - defer/async を指定する
- polyfill として core-js@3 のすべてを読み込む
  - browserslist による最適化,あるいは polyfill.io への移行を行う
    - Array.prototype.flat などを使って, useBuiltIns: usage を使うとこけるという罠を入れてもよさそう
- @babel/preset-es2015 で変換する
  - babel/preset-env に移行する
- dependency に最適化の余地を残す
  - webpack-bundle-analyzer を使ってでかい dependency を検出し,対処する
  - ImmutableJS -> 脱 ImmutableJS
  - lodash 全部入り -> lodash-es にする or 個別の lodash package を使う or 脱 lodash
  - React -> preact
  - Bluebird -> native Promise
  - moment -> dayjs or 自前で書く
  - axios -> fetch
- first view に入らない要素に必要な情報も最初に API で取得する (コメント)
  - Intersection Observer を使って遅延読み込みする
- XHRを同期的に処理
  - 非同期化させる
- ページ表示に必要なリクエストを初期化時に全て行ってから表示処理に移行する
  - 適切なタイミングを探って処理を分散させる
- Reflow を定期的に無理やり起こしておく
- css animation の perf

### CSS
- 不要な CSS を大量に追加する
  - 使われていない CSS を削除する
- CSS を minify しない
  - cssnano などで minify の設定をする
- CSS はすべてを一つに固めて blocking resource として読み込む
  - above-the-fold だけを blocking で読み込むようにしてあとは遅延させる

### Assets
- 画像は png/最高画質/高解像度 のものを配信する
  - 使える場合に webp を使用する
    - サーバ側で判別 or picture 要素の使用 or Chrome に特化して webp のみを配信
  - 画像の表示サイズに応じた適切なサイズに変更する
  - srcSet で画面解像度に応じたサイズを出し分ける
- ファーストビュー以外の画像もすべて読み込む
  - lazyload の設定をする
- 日本語の特定部分 (ブログサービス名とか?) だけのために Web Font を使う
  - `text=xxx` を指定して必要なものだけ読み込む
  - preload を指定する
  - 画像に置き換える?
- サードパーティの何かを読み込む
  - preconnect を指定する

## Server
- static file を express から配信する
  - Nginx に移動する
- gzip しない
  - gzip や brotli の設定を行う
- クライアントサイドレンダリングにする
  - SSR 化? (効果はわからない)
- http/1 で配信する
  - http/2 対応する
- babel-node で起動する
  - サーバー側も transpile して node を直に使うよう設定する
- 無意味な sleep や loop
- リクエスト毎に fs 使って大きなファイルを見る
- heavy task をたくさん準備しておく
- Memory Leak を準備して、だんだん重くさせる
- 他 Bad Practice があれば

なお,Web フロントエンドのパフォーマンスチューニング競技では株式会社リクルートさんが 数年前 より 取り組まれて おり,作問段階でも勝手ながら大いに参考にさせていただきました.この場を借りてお礼申し上げます.

上記要素を組み込めるだけ組み込みながら競技課題のサービスを実装していきました.実装していく過程で特にビルド周りにおいて

  • Deprecated になっている @babel/preset-es2015 は公式ドキュメントすら消滅していたこと
  • @babel/preset-env の modules のデフォルトがいつの間にか auto になっていて tree shaking が設定なしで効くようになっていたこと
  • webpack の mode: production は思っていた以上に高性能で chunk splitting や terser など大抵の最適化機能を有効にしてくれること
  • core-js が useBuiltIns: usage でうまく動かないケースがあるのは v2 系までで,v3 では原則として解消されていること

などがわかり,知識のアップデート不足を痛感するとともにいかにしてさりげなく「デチューニング」するかに苦心させられました.

アプリケーションとしてはシンプルなブログサービスとし,エントランスや記事詳細ページなど5ページからなるサービスになりました.

お題アプリケーション「あみぶろ」のエントランスページのスクリーンキャプチャ
お題アプリケーション「あみぶろ」のブログ記事ページのスクリーンキャプチャ

一方で最終的なパフォーマンスは「表示に 90 秒かかり Lighthouse v5 で全ページ安定して 0 点」「main.bundle.js のサイズが 15.6 MiB」というなかなかなものに仕上がりました 😆

Lighthouse で0点が出ている様子

webpack の出力結果でファイルサイズが15.6MBになっている様子

また,募集については「Web Speed Hackathon Online | 株式会社サイバーエージェント」で行い,オンライン参加型という特性を生かして選考なしのイベントとしました.その結果ありがたいことに非常に好評を博し,最終的には 70 人以上の学生からエントリーをいただきました.あまりの勢いにもともと予定していた募集期間を短縮せざるを得ないほどでした!

当日は Slack でのアナウンスにより競技が開始しました.

Slack で競技開始をアナウンスしている様子

2日間みっちりとチューニングをしてもらい,2日目 17:30 時点のスコアで順位を決定しました.競技では全5ページの Lighthouse v5 のスコアを加工して得点を算出し,上限は 575 点でした.そんな中最終的に400点台が4人,500点台が6人で最高得点が550.81点という非常にハイレベルな戦いとなり,大変盛り上がりました!

一方で,レギュレーションとして機能落ち・デザイン差異がないことを挙げていましたが,時間の都合で自動的に検証する仕組みを準備することができず,各自の確認に委ねられていました.そのことも影響してか,惜しくもレギュレーション違反となってしまった方が上位陣を中心に多く発生してしまい,最終的な順位としては以下のようになりました.

競技の最終順位を Slack で発表している様子

イベント後のアンケートでは80%以上の方から「楽しかった」という感想をいただくことができ,成功裏に終えることができたと考えています.改めて上位入賞された方はおめでとうございます!また多くの方にご参加いただきましてありがとうございました.

社内イベント「WIC Speed Hackathon Online」

採用イベントが大変好評だったことで,同じ内容を社内向けにも実施したいという欲が湧き上がってきました.そこで,社内の Web フロントエンド横断組織である Web Initiative Center (WIC)エンジニアの交流促進組織 Developers Connect とも連携しながら計画を進め,同じ問題を使って開催することができました.

WIC Speed Hackathon Online の社内イベントページ

土日を潰して開催しようとすると参加者が集まりにくくなることが想定されたので,平日の 18:00 – 21:00 を2日連続で確保することで競技時間としました.また,せっかくなので2021年入社予定の内定者にも人事経由で声をかけていただきました.

その結果,社員18人,内定者12人の合計30人にエントリーしていただくことができました!

業務との兼ね合いで採用イベント時よりも時間的制約が厳しい条件での開催となりましたが,専門知識を遺憾なく発揮して高得点者が続出しました 🎉

ちょうど開催前に Lighthouse v6 がリリースされスコアリングを v6 ベースに切り替えたため,採用イベントの得点と直接比較できるものではないのですが,最終的な順位は以下のようになりました.

WIC Speed Hackathon の Leaderboard

Twitter での様子はこちらです:

競技後のアンケートでは満足度が100%となり,コロナ下で交流が少なくなる中でよいイベントにすることができたと思います.

出題された問題と解説

今回のハッカソンで使われた問題とその解説は以下で公開しています.

特に解説は図らずも昨今のパフォーマンスチューニングテクニックを概観する形となり,リファレンスとしてもお使いいただけるものに仕上がったのではないかと自負しています.皆様のお役に立てば幸いです!

おわりに

筆者は現在 ABEMA にて Web Performance Engineering というパフォーマンス改善を専門に行うプロジェクトに属しています.このところ Google が2021年より検索順位を決定する要素の一つに Core Web Vitals を加える予定と発表するなど,ビジネスの観点からもパフォーマンスの重要性は高まるばかりです.

今回のような活動を通して,「パフォーマンス最適化」という分野をより盛り上げることに少しでも寄与できればと思っています.これからもよろしくお願いします!

また,2021/2/6 (土) – 2021/2/7 (日) で第二弾となる Web Speed Hackathon Vol.2 を開催予定です!作問・解説は社内イベント時に1位となった @3846masa が担当しています.今回も採用イベントとなりますので学生の方だけとなってしまいますが,前日までエントリーを受け付けていますので興味のある方はぜひご参加いただければと思います!