kaa_a_zu’s blog

月ごとのレポートや技術的な内容を発信していきます

CA主催のスピードハッカソン に参加してきた話

本日、2021年2月6日から2日に渡って開催された、サイバーエージェント主催の スピードハッカソン に参加しました。そろそろパフォーマンス改善得意になりたい!って気持ちで臨みました。

尚、開発時にメモしていたことを簡潔に書くので、チューニングの詳細を知りたい部分があったらカーーズへのリプやコメントをください。

スピードハッカソンとは

既存の架空SNSのパフォーマンス改善を行うハッカソンで、順位決定のスコア測定にはLightHouseが使用されています。利用されていたメインな技術を載せておきます。

  • React
  • TailWind
  • Express(APIサーバー)
  • SQLite

ハッカソンのルール

①複数ページのパフォーマンスを上げる

Chrome最新版での動作確認を行う

③見た目を変更することはNG

④挙動を変更することはNG 

⑤サーバーは自分で用意する(サーバーサイドについてもチューニングできる)

参加する前に

実は今回はやろうとしていたことが2つありました。

1つ目は、NextJS(SSG) × Vervel(主にエッジCDN目的)です。最近業務でこのような構成を触っているのですが、実際に自分で作ったものではないので、パフォーマンスにこだわりながら構成をしたいなというモチベーションがありました。

2つ目は、WASMの利用です。最近Rustに少しだけはまっていて、WASMを用いてなんちゃってな(非実用的な)計算などをしていました。これらを、本番で使える部分を探していたところでした。

行ったパフォーマンスチューニング

羅列します。後で詳細を書くかもしれません。

ローカルのリソースについて

  • 画像(png,jpg)をWebPに変換
  • 画像を圧縮
  • 動画(mp4, gif)をWebMに変換
  • 音声(mp3)を圧縮
  • 利用していないフォントを削除

動的にアップロードするリソースについて

  • POST時にサーバー側で圧縮と変換を行う

ネットワークタブを見ながら....

  • フォントを rel=preload で読み込む優先順位を上げる
  • スクリプトファイルを defer で実行
  • FetchAPIにlimit, offset を指定して、必要な分だけ読み取る
  • スクリプトを分割して 必要な時にだけ読み取る(dynamic-import)

プロファイルをみながら....

  • addEventListnerに {passive: true} を明示
  • 無駄に計算をしている、ループを回している処理を削除

利用度カバレッジとかを見ながら

  • 無駄なCSSを探して削除
    • tailwind を中で使っていたが TreeShakingのために purge 設定
    • cssnanoを利用
  • 無駄なJSを探して削除

webpack analyzerをみながら....

  • lodashlodash-es に置き換え(tree-shakingの目的)
  • jQuery を削除(ajax とかを fetch に)
  • momentdayjs に置き換え

webpack について

  • mode: 'production' を付与して、圧縮やTreeShaking, Split
  • babel-loaderchrome最新版のためだけにカスタマイズ

APIサーバーについて

  • Cache が聞いていなかったので max-age, no-store 付与

やれなかったこと

  • imgwidth/height の指定
  • 画像Lagyloading
  • http2(これは証明書とかの問題で)
  • 静的ファイルをNginxとかから配信(リバースプロキシ) ※デフォルトでは Express を用いていた
  • express を fastify に置き換え
  • node-helmet を削除して、手動で記述

やってたけど途中で諦めたこと

冒頭でもお伝えしたように、Next.js(SSG) × Vervel の環境を作ろうと思いました。デフォルトではHerokuの環境だったので、移行をしなくてはいけませんでした。また、コードは React(react-router) で書かれていたため、Next.jsに移行する必要がありました。

  • APIサーバーを用意

DBがSQliteを利用していたため、Vercel のようなファンクションごとにインスタンスがたつ環境ではデータを保持することができません。別のDBに置き換えることも考えたのですが、APIサーバーを別途用意する方が楽そうだったので、Heroku で用意をしました。(ここら辺から生き地獄が始まります)

  • CORS設定

APIサーバーを別途用意したため、CORSの設定をする必要がありました(数行書くだけ)

  • ログインユーザー管理方法をJWTに変更

ドメインを跨がないといけないため既存のSessionでの管理はできなくなりました。今回は jsonwebtoken というライブラリを用いてJWT管理をしました。何気めんどくさかった。

「よっしゃドメインまたいだAPI通信もできるようになったし、あとはやるだけだ。」と思って頑張ってやっていた自分を褒めてあげたいです。ちなみにこの時、SSG, CDN, Nextによる最適化 が目前だったため、圧倒的な優勝を想像していました。

  • NextJSへの移行

SSRが行われているため、サーバーサイドでは機能しないようなコードがいくつかあったので修正をしました。react-routerからpages によるルーティング変えました。ImageLinkタグをNext.jsのものに置き換えました。

ここまでやって断念しました。

後でやればいいやと思っていた、TailWind のデザインが なぜか上手く反映されない。。。。。。。。。。。。。。デザインがすっごい崩れるという問題にぶつかりました。TailWindは初見だったし、「分からん」ってなってしまいました。

諦めた時に打ったcommit ↓

f:id:kaa_a_zu:20210207182219p:plain

結果

レギュレーション違反

時間がなくて、焦っていました。最後の方で必死に色々と触っていたため、挙動の確認をしていませんでした。 残り5分で挙動確認をしたところ、サインインができなくなっていました。ログインが出来ず、呟けないSNSは、新しいですね。 途中優勝するんじゃないか?とか思ってたので、最も情けない結果ですね。

考察

  • Next.js に移行する時に行ったインフラの構築に何気に時間がかかりました。インフラの勉強をしなくてはいけないなと思いました。
  • webpack への知識まだまだだなーと思いました。こういう時ってどう書けば良いんだろうとか結構ググりました。
  • 時間管理能力の不足、もしくは最新の技術(TailWindのCSSの当て方とか)を触っておくべきだったなと思いました。

感想

Webフロントに入門した頃の、過去の自分と比べると成長を感じれました。「何をすればいいか分からない」ではなく「しないといけないことがいっぱいあって時間がない」と思えたのは凄くよかったです。1日目はフルで参加できたのですが、ご飯を食べることも忘れるくらい熱中していました。また、やりたかったけどできなかったことに挑戦する機会、新たな学びを得れる機会を半ば強制的に作れたのはよかったです。

改めて、最高のイベントでした!運営のサイバーエージェントのみなさま、ありがとうございました。ここについて、詳細が欲しいという内容がありましたら、コメントください🙏

Recruit主催のスピードハッカソンに参加してきた

本日、3/7 RECRUIT主催の スピードハッカソン に参加しました。色々な理由があって、絶対に優勝する心積もりで参加をしました。結果、優勝できなかったです。凄く悔しい思いをしています。

本日やったことと今の想いは文字に起こさないとダメな気がしたので拙い文章ではありますが、書きます。 尚、簡潔に書くので詳細を知りたい部分があったらカーーズへのリプやコメントをください。

スピードハッカソンとは

Recruit社の実サービスであるホットペッパービューティのパフォーマンス改善を行うハッカソンで、順位決定のスコアの測定にはLightHouseを使用します。 

ハッカソンのルール

①1つの詳細ページのみのパフォーマンスを上げる

Chrome Canaryでの動作確認を行う

③見た目を変更することはNG

④挙動を変更することはNG 

行ったパフォーマンスチューニング

ボトルネックの探し方

サイトの解析ツールとして、LightHouseよりもわかりやすく、詳細に修正した方が良い箇所を指摘してくれる、yellowlabを利用した。 

改善前のサイトの状態

  • HTTP1.1 での通信
  • webサーバーはNginx
  • ビルドツール, タスクランナー等は使われていない
  • ノーマルなCSSを使っている
  • jQueryが動いている

f:id:kaa_a_zu:20200307231625p:plain

チューニングした項目

チームの皆で大きく分けて10個の改善をしました。

尚、1ページのみのチューニングで時間も限られていたためビルドツール,タスクランナーを使うことは本質的ではないと思い使わない事を選択しました。

1. Scriptのdefer/async読み込み 

Scriptは パーサー ブロックをしてしまうため、それを阻止するため非同期で読み込み

(jQueryなど他から依存されているスクリプトの読み込みは同期的に読み込まないといけないので対象外)

2. 各サブリソースの読み込み順序の変更

CSSはできるだけ最初に読み込んだ方が良いため<Head>の序盤に配置、ScriptについてもDOM操作がないものは序盤に配置

3. 画像のwebp化, 他拡張子の圧縮

今回はchromeが対象のブラウザだったため、<Picture>タグでは囲まずにwebpのみを採用。webpの画像は用意されていなかったので、squooshを使ってwebpに変換。また、一部の画像はwebpよりもpngを使った方がサイズが小さくなったので、それらには ImageAlpha での減色 と ImageOptim での圧縮を実行(盲目的にwebp最強だと思ってたので意外でした。てかwebpの実体をあまり理解せずに使っているの良くないな....

4. 画像の遅延読み込み

最初はchrome のネイティブlazy-load を利用をしていた

<img src="hoge.png" loading="lazy"

時間があったので、表示領域だけを無駄なく遅延読み込みしてくれる lazysizes を使ったところ、こちらの方が良いスコアが出ました。

5. <img>にwidth, heightを設定

webpなどベースライン形式の画像は縦幅横幅を指定しないで表示してしまうと、ロードが完了するまで縦幅横幅がわからないため、ロードに応じて<img>要素のサイズ更新が行われてしまうと聞いたことがあったので追記した

6. Scriptのminify化

今回は、UglifyJS を使った。

webpack v4の圧縮機構でデフォルト採用されている Terser との比較をしてみたかったです。

7. 使われていないcssの削除

今回は、 PurgeCSS を使った。

リクルートの方が作った[css-optimization](https://github.com/toshi1127/css-optimization)も試してみたかったです。

8. Cssのminify化

今回は、clean-css を使った 。

 

〜ここまででLightHouseでのスコアは65くらいでした〜

9. HTTP/2に変更

リクエスト数の上限の壁を超えました。(笑)

10. NginxでのgZip

配信の時に小さい方が良いよね。

 

これら10個のことをやった結果、

パフォーマンス96

改善後のスコア

5時間で 11 から97!!!!!!!!!!!!!!!!!!!! 個人的にはすごいと思っちゃいました(笑)

 

.....ここまでやって負けました!!!!!!!!

ほぼ、同じような内容をやっていた1位との差はホスティングの部分に注目したか』でした。

僕たちのチームは終了30分前くらいまではある程度の差をつけて1位でいました。正直、慢心をしてしていました。そして、その間に2チームに抜かれました。嘘だろって思いました。

愚直なパフォーマンス改善を続けた @しゃがみーさんたち、そして express のホスティング(静的ファイルの提供)の遅さに気がつき、大差を付けて優勝した @どらくん, @たじまちゃん,@ましくん, @もっちくん たちは凄かったです。

 

コンテストが終わって更にすればよかったなーと思う点がいくつかあるので書きます。

  • バンドルしてラウンドトリップを1回にしたらどうなったのかな。これは今回の場合http2との無制限並列リクエストの比較になるのかな?
  • gZipじゃなくてSSL化してBrotliで圧縮したらどうなったのかな?
  • JSのデッドコードを削除 minify化した後に出来るのかな?
  • CSSセレクタをもっと簡単に参照できるようなチューニングしたらどうなるのかな?
  • HTTP3って適用できたのかな?
  • Performanceパネルをもっとしっかりと見えば良かった
  • 表示サイズに合わせた画像の最適化は、今回においてあんまり効果なさそうだったけど、推測するな計測しろらしいので時間が余ったらやった方が良かった?

感想

パフォーマンスについては、興味があっても、なかなか触れる機会がなく1人だと詰まってしまうことが特に多い部分だと思います。それを既存の大きなサービスを使い、複数人で行えたことは、本当に良かったです。何より楽しかったです(笑) 主催をしてくださったRecruit社の皆さん、ありがとうございました。

結果に対してですが、随所でも書いた通りとても悔しいです。(どのくらいか悔しかったかというと、懇親会で出た寿司を1つも食えないくらい) ですが、過去の自分と比べると成長を感じれました。また、自分が苦手な部分(今回で言うとPerformanceパネルを見ることを毛嫌いしてるのが良くないw)についても、改善をすることの重要性を知れたので良かったです。

最後に、これだけは言わせて欲しいです!!!! 優勝はできなかったけど、僕たちのチームの団結力は1位だった気がしています。初めて会う人たちで、それぞれの分野も知識量も違いどうなるか最初は不安でした。その中でそれぞれの得意分野を活かして進めることができたのは、このチームの皆だったからな気がしています。最終的にそれぞれが出来ることをやり切れたことは良かったです。@さぃとくん @大石くん @やだくん ありがとうございました。

ただ、良い思い出で終わらせるわけにはいきません。
すげえ悔しいから(笑)
絶対に来年は優勝してみせます。1位になったチームの皆さん、来年参加する皆さん、よろしくお願いします。

改めて、最高のイベントでした!

 

p.s. リクルート社員の皆さん。パーカーを貰い忘れました。