Posted at

Service Worker で横取りしたリクエストをネットワークにフォールバックさせたい時はただ return するべき

More than 3 years have passed since last update.

タイトルの通り、Service WorkerFetchEvent で横取りしたリクエストをネットワークにフォールバックさせたい場合は return するだけで OK です。


sw.js

self.AddEventListener("fetch", function(event) {

return; // network fallback happens
});

次のように明示的に fetch して respondWith() で返す必要はありません。むしろ fetch したレスポンスが一旦 Service Worker スクリプトに渡されるため、ただ return するよりも処理が遅くなります。


sw.js

self.AddEventListener("fetch", function(event) {

event.respondWith(fetch(event.request));
});

以後、ただ return する場合を "return-fallback"、fetch して respondWith() で返す場合を "fetch-fallback" と表記します。


ベンチマーク

fetch-falback だと処理が遅くなることを確認するため、簡単なベンチマークを実施してみました。次のコードは 2048 bytes のファイルを 100 回リクエストします。リクエストは Service Worker によって横取りされますが、return-fallback もしくは fetch-fallback でネットワークにフォールバックされます。


sw.js

self.AddEventListener("fetch", function(event) {

// return;
// OR
// event.respondWith(fetch(event.request));
});


index.html

var start_time = 0;

navigator.serviceWorker.ready.then(function() {
var promises = [];
start_time = performance.now();
for (var i = 0; i < 100; ++i)
promises.push(fetch("2048-bytes.txt"));
return Promise.all(promises);
})
.then(function() {
console.log(performane.now() - start_time);
});

これを Macbook Air (OS X 10.10, 1.7 GHz Intel Core i7, 8GB RAM) の Chrome 49.0.2568.0 で、それぞれ 5 回ずつ実行して計測してみました。サーバは localhost に立て、あらかじめ何回か実行して HTTP Cache や Service Worker をウォームアップしてから計測しています。フォールバックしたリクエストは HTTP Cache から返ります。結果は次の表の通りです。

return-fallback [ms]
respondWith(fetch) [ms]

1
125.985
207.890

2
132.205
238.600

3
119.090
200.055

4
136.850
197.080

5
118.105
202.100

avg
126.447
209.145

return-fallback だと平均 126 ms、fetch-fallback だと平均 209 ms かかりました。このことから、リクエストをネットワークにフォールバックさせたい場合は respondWith(fetch(request)) で返すのではなく、ただ return した方が処理が早いことが分かります。


(おまけ) Chrome ブラウザの内部実装の話

「レスポンスが一旦 Service Worker スクリプトに渡される」とどうして遅くなるかというと、(1) ネットワーク処理を行うプロセス (Browser プロセス) と Service Worker のプロセスの間でプロセス間通信が発生するのと、(2) Service Worker のスクリプトにレスポンスが渡ると C++ と V8 の間で実行コンテキストが切り替わるからです。

current-data-flow.png

Chrome では event.respondWith(fetch(event.request)) というコードがあった場合には Service Worker プロセスをバイパスし、プロセス間通信やコンテキスト切り替えのオーバヘッドを取り除く最適化を入れるのはどうか、という提案が出されています (crbug)。しかし、return-fallback によってこれを回避でき、また最適化を入れるには複雑な変更が必要となるため、まだ実装されていません。

optimized-data-flow.png


謝辞

この記事の草稿を @kinu さんと @horo さんにチェックしてもらいました。ありがとうございます。


DISCLAIMER: ここで述べられている内容はすべて私の個人的な意見に基づくものであり、所属する組織、団体とは一切関係ありません。