BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース Facebook.comの再設計:持続可能なパフォーマンスのためのスタックと戦略

Facebook.comの再設計:持続可能なパフォーマンスのためのスタックと戦略

原文(投稿日:2020/11/10)へのリンク

Facebookがブログ記事で、facebook.com Webサイトの最新イテレーションとなるFB5で使用されているテクノロジと戦略を詳細に説明している。Facebookは同社のWebサイトを再構築して、テクノロジスタックをReact、GraphQL、Relay、独自開発のCSS-in-JSライブラリによって標準化した。再開発の目標はパフォーマンスの向上と、新機能の追加を容易にすることだ。

FB5開発のモチベーションについて、同社は次のように説明している。

新たなWebアプリケーションの構築方法を検討した時、[…] 既存の技術スタックでは、私たちの必要とするアプリ感覚の操作性やパフォーマンスをサポートできないことが分かりました。完全なリライトは極めて稀なケースですが、今回に関しては […] パフォーマンスや、将来にわたって継続可能な成長という私たちの目標を達成するためには、それが唯一の手段だったのです。[…]
Facebook.comが素早く起動し、迅速に応答し、高度にインタラクティブなエクスペリエンスを提供することが、私たちの目標です。

Facebookは2004年、サーバサイドPHP Webサイトとして立ち上げられた。FB5で同社は、高度にインタラクティブなユーザエクスペリエンスをよりよくサポートするために、クライアントサイドアプリの構築を選択した。これには、パフォーマンスや応答性の問題に、定常的な新機能追加に対する回復力を備えた方法で対処する、という意図がある。採用されたテクニックは、Webアプリケーションの重要な4つの構成要素、すなわちスタイル(CSS)、対話性(JavaScript)、リモートデータフェッチ、ナビゲーションに対応するものだ。

FacebookのFrank Yan氏の報告によると、ホームページのCSSが80パーセント削減されて、413KBあったものが73KBになった。使用しないスタイルの削除とCSSルールの再利用に寄与したのは、独自開発のCSS-in-JSライブラリである。スタイルとそれを使用するコンポーネントコードを同じ場所に配置することで、その組み合わせが明確になり、コンポーネントとスタイルの変更や削除を同時に行うことが可能になった。コンパクトな単一目的のCSSクラスを指向するCSSアーキテクチャのアプローチであるAtomoc CSSにより、CSSルールの重複排除と再利用が実現されたのだ。CSSファイルのサイズと機能との比例的な増加関係を打破できた理由として、Yan氏はこのAtomic CSSを挙げている。並記されたスタイルを分離して最適化されたバンドルにする作業は、独自開発のツールによって行われる。

Facebookはさらに、独自のCSSプロパティを使用したテーマ機能の改善や、pxベースからフォントサイズ比例のremベースへの切り替えによるアクセシビリティ向上、SVGをファイル転送からHTMLにインライン化することによるユーザエクスペリエンスの改善なども行っている。 後者では、コンテンツの表示後にアイコンが表示される状況を防止することが可能になる。

Webアプリケーションのインタラクティブな機能の実装は、JavaScriptコードベース数の増加やアプリケーション機能の複雑化という結果に行きつくことが少なくない。ダウンロードするコードやデータの量は、ページのロード速度、延いてはユーザエクスペリエンスの重要な要因になるため、Facebookでは、コード分離とデータフェッチ手法の最適化を併用することによって、必要なものだけを早い段階でフェッチするようにした。

アプリケーションコードは3つのティアに分離されており、個別にダウンロードされ、独自のインポートAPIによって通知される。最初のティアには、最も重要な(above-the-fold)コンテンツの最初の描画を表示するために必要となる、基本的なレイアウトが集められている。最初のローディング状態で使用するUIスケルトンなどがその内容だ。最初のティア内のコードからは、標準的なimport構文を使ってモジュールのインポートを行う。

第2のティアには、重要コンテンツを完全に描画する上で必要なJavaScriptがすべて含まれている。第2ティアのコードは、独自のimportForDisplay APIを使って依存関係をインポートする。ブログ記事の説明では、

importForDisplayが呼び出されると、その関数と依存関係はティア2内に移動します。そのモジュールがロードされれば、それにアクセスするためのpromiseベースのラッパが返されます。[...] ティア2は完全にインタラクティブでなくてはなりません。ティア2コードがロードされ、描画された後でメニューがクリックされると、メニューのコンテンツをレンダリングする用意ができていなくても、フィードバックが即時に呼び出されます。

第3のティアは残りのコード、つまり、重要なコンテンツの描画に関連しないコードを取得する。さらに、独自のimportForAfterDisplay APIを使用して、依存関係の宣言も行う。

このような構造によってユーザは、画面(ティア1コードが描画するレイアウトとプレースホルダ)をより早く見ることができるのだ。しかも、第3ティアのコードがダウンロードされて実行される前に、インタラクティブなアプリケーションを利用することも可能になる。

ここで述べたコード分割の手法は、2つの特別なケースにおいて、必要なコードのみを選択的かつ動的に含めることでさらに強化される。

A/Bテストが、A/B依存ロジックを宣言するimportCond APIでサポートされている。これによってサーバは、両方のバージョンではなく、必要なバージョンのコンポーネントのみを送信すればよくなる。この手法は、複数のバリエーションを持っているが、その中のひとつだけを必要とするようなUIに使用することができる。

コンポーネントは、フェッチしたデータに基づいて、いくつかのサブコンポーネントのいずれかにブランチする場合がある(FeedPostPhotoPostのように)。この場合、インポートするのは実際にブランチするサブコンポーネントのみとするのが望ましい。FacebookのソフトウェアエンジニアであるAshley Watkins氏は、この手法をデータ駆動コード分割(data-driven code-splitting)と呼んでいる。React Confで行われた講演の中で氏は、HTMLフラッシュの方法、ページに必要なデータのGraphQLデータによる一元的記述、開発者がアノテートしたコードやデータをクライアントへのストリームダウンに寄与する専用のRelay API、などについて詳細に説明した。Relayクエリのアノテーションの例を次に示す。

... on Post {
  ... on PhotoPost {
    @module('PhotoComponent.js')
    photo_data
  }
  ... on VideoPost {
    @module('VideoComponent.js')
    video_data
  }
  ... on SongPost {
    @module('SongComponent.js')
    song_data
  }  
}

クエリとそのコード依存関係を同じ場所に置くことで、関係するコードのみを選択的に取り込むためのカップリングが作られる。

コンパイル時や実行時にユーザがダウンロードするコードサイズの最適化に加えて、Facebookでは、データフェッチスタックを標準化(GraphQL)し、最初のサーバ要求でデータをプリロードして起動時間を改善すると同時に、非優先データよりも優先的データを先にストリーミングすることによって、データフェッチの最適化も実現している。

Joe Savona氏はReact Confで、GraphQL、Relay、Suspenseを併用することによるデータフェッチ最適化の達成について詳説している。Relayはビルド時にページが必要とするデータが何であるかを知っているので、ページへの要求を受信すれば直ちに、要求されたコードと並行して要求データのフェッチを始めることができる。この並列性により、余分なラウドトリップを排除し、最終的なページコンテンツをより早く描画することが可能になるのだ。

Facebookでは、GraphQLクエリを独自のGraphQLエクステンション(@stream、@defer)でアノテートすることで、非優先データ(即時必要ではないもの)から優先データ(用意ができればすぐストリームされる)を宣言的に区別している。

シングルページアプリケーションにおいて、動作の早いナビゲーションは重要な機能のひとつだ。Facebookでは、動的ルーティング、プリフェッチ、並列化手法を併用することで、ページ間のシームレスな移動を実現している。

アプリケーションの他の部分に移行する場合、アプリケーションのルート(ルートマップ)と、そのルートに移行するためのコードやデータが必要になる。Facebookのルートマップは一度に送信するには大き過ぎるため、新たなリンクの描画毎にルートを動的に追加する方法を採用している。

ルートリソースは、ブラウザが実際にルートにナビゲートする前にフェッチされる。デスクトップでは、ユーザがリンクをホバーした時点でリソースをロード(resource hint rel=preload)し、mousedownイベントでコード(スクリプトタグ)とデータをロードして、クリック時にSuspenseでレンダリングを行う、という方法が可能だ。このReact Suspense Transitionsによって、ユーザに途中のブランク画面を見せることなく、完全に構成されたページ(スケルトンページ、完全にレンダリングされたページのいずれでも)に直接移動することが保証される。

コードとデータを直列的ではなく並列的にフェッチするため、FacebookはEntryPointsを使って、そのルートに必要なデータをフェッチするクエリをキャプチャしている。

FB5を支えているテクノロジに関するプレゼンテーションに興味のある開発者は、Facebookの再設計に関する活動を説明したブログ記事を参照するとよいだろう。さらに、CSSデータフェッチその他の最適化手法を取り上げたReact Confの講演がオンラインで公開されており、より詳細な情報やイラストレーション、デモなどを提供している。

GraphQLは、APIとランタイムのクエリおよび操作用の言語である。Facebookが2012年に社内的に開発し、2015年に一般向けにリリースされた。GraphQLではデータの読み込み、書き込み、変更のサブスクライブがサポートされている。データのフェッチ方法ではなく、コンシューマが必要とするデータを記述することにより、コンシューマからのAPIプロバイダの分離を支援する。

RelayはReact用のGraphQLクライアントである。データの依存関係をローカルで宣言したReactコンポーネントを使用し、すべてのコンポーネントのデータ依存関係をひとつのGraphQLリクエストでフェッチすることで、ラウンドトリップの最小化を実現する。また、楽観的更新(optimistic update)をサポートするAPIを提供し、データ依存関係とコンポーネントとの同期を行う。

この記事に星をつける

おすすめ度
スタイル

BT