Netlifyが日本からだと遅い
id:anatooのブログ 2020年8月3日

仕事で Netlify にデプロイしたSPAの読み込みが遅いので原因を調査してほしい、という依頼を受けてウェブパフォーマンス調査を行った。顧客から許可をもらって、この記事ではNetlifyに対してどういう調査をしたのかを書く。

結論だけをまず書くと、NetlifyのCDNのファイル配信パフォーマンスは日本国内からだと非常に悪い。パフォーマンスを改善させるためには、Netlifyに直接アクセスさせるのではなく、前段に他のCDNやキャッシュサーバを挟んだりするほうがいいだろう。

調査の前提

  • 日本国内からのみの調査
  • サイトには静的なファイルをデプロイしているのみ

該当するNetlifyにデプロイしたSPAをブラウザで試しに開いてみると、確かに初回の読み込みのパフォーマンスがめちゃくちゃ悪い。

Chrome Devtoolsを開いてネットワークタブでどういうふうにリソースの読み込みを行っているのかを見てみる。HTTP/2が使われておりEtagの条件付きキャッシュも設定されている。フロントエンドの組み方を確認したが問題はなく、クリティカルレンダリングパスにおかしな部分はなかった。

ただ初回のファイルのダウンロードが単純に遅い。例えばgzip済みで60kb程度のサイズのJavaScriptファイルののダウンロードに2,3秒ぐらいかかっている。さすがに時間かかりすぎてておかしいのでこの辺りをさらに調べてみることにする。

ApacheBenchで計測してみる

ファイル配信のパフォーマンスが良くないらしいので、ApacheBenchを使って計測してみることにする。

次のようなコマンドを叩いて、どれぐらいスループットが出るか調べてみる。今回はサーバに負荷をかけずに、単一のコネクションでのパフォーマンスを調べるので、並列でリクエストを投げないようにして、リクエストを投げる数自体も少なめで計測した。リクエストを送る時に毎回TCPコネクションを作らないように、KeepAlive(-kオプション)を有効にしている。

$ ab -n 10 -c 1 -k https://***.netlify.app/vendors.bundle.js

(中略)
Concurrency Level:      1
Time taken for tests:   19.171 seconds
Complete requests:      10
Failed requests:        0
Keep-Alive requests:    10
Total transferred:      6106590 bytes
HTML transferred:       6102630 bytes
Requests per second:    0.52 [#/sec] (mean)
Time per request:       1917.081 [ms] (mean)
Time per request:       1917.081 [ms] (mean, across all concurrent requests)
Transfer rate:          311.07 [Kbytes/sec] received
(以下略)

Transfer rate: 311.07 [Kbytes/sec] received

条件や計測するファイルのサイズを変えて何度か計測してみたが約200-300Kbytes/sec程度しか出なかった。どうもNetlifyのCDNではファイル配信のパフォーマンスが非常に悪いようだ。

CDNの基本的な仕組み

ここでCDNの基本的な仕組みについて簡単に記述する。

CDNがパフォーマンスを出すために基本的にどういうことを行っているかというと、世界各国にCDNノード(大まかに言うとコンテンツを配信するキャッシュサーバ)を配置しておいて、ユーザからアクセスがあった場合にはネットワークの経路的に近いCDNノードに誘導してあげることでレイテンシを少なくしてパフォーマンスをだしている。

例えばAWSのCloudFrontを使っているサイトにアクセスする場合には、日本国内からアクセスすると大阪や東京にあるキャッシュサーバに、アメリカの西海岸からアクセスするとカリフォルニア州辺りにあるキャッシュサーバに誘導にされたりする。

CDNがユーザのアクセスを経路的に近いサーバに誘導する方法には、DNSサーバを使う方法と、IPエニーキャストを使う方法がある。DNSサーバを使う方法では、CDNが払い出したサブドメイン(例えば*.cloudfront.net*.netlify.app)を名前解決する際にそのCDN事業者が提供するDNSサーバにクエリが飛び、経路的に近いキャッシュサーバのIPに解決することでHTTPリクエストを飛ばす先を振り分けている。

NetlifyのCDNについて調べる

ApacheBenchで計測してみた結果、Netlify自体が遅いらしいことがわかったので、今度はNetlifyのCDNについて調査した。

  • 通常のCDNであればCDNノードを世界各国のどの辺りに配置しているのか普通公開しているが、NetlifyのウェブサイトにはCDNノードがどこにあるのか公開していなかった
  • Netlifyのフォーラムを探してみると、Netlifyのサポートエンジニアが次のように書いている

Is there a list of where Netlify’s CDN pops are located?
We don’t publish an authoritative list since things can change frequently, and it would fall out of date quickly, but as of today I think the list is:

  • Regular: Frankfurt, Singapore, San Francisco, New York, Sao Paolo, Sydney
  • Enterprise: same locations but additionally: Mumbai, Columbus, Des Moines, Tokyo, Dublin, and Toronto
  • 2019年5月の情報だが、Enterpriseプラン(営業と連絡を取って個別に契約が必要なプラン)でしか東京のキャッシュサーバは利用できないということが書いてある。現在契約しているプランの場合、アジアではシンガポールにのみCDNノードがある
  • 日本からのアクセスにもシンガポールのサーバに誘導されるらしい

日本国内には通常の有料プランで利用できるCDNノードがないようだ。

tracerouteで経路を確認する

Netlifyのキャッシュサーバにアクセスする際に、実際にどういう経路をたどるのかをtracerouteで調べてみる。

$ traceroute ***.netlify.app
traceroute: Warning: ***.netlify.app has multiple addresses; using 206.189.89.118
traceroute to ***.netlify.app (206.189.89.118), 64 hops max, 52 byte packets
 1  192.168.2.1 (192.168.2.1)  1.621 ms  1.988 ms  1.224 ms
 2  nas81g-3.p-fukuoka.nttpc.ne.jp (153.152.222.63)  7.775 ms  10.068 ms  11.247 ms
 3  153.152.223.169 (153.152.223.169)  12.110 ms  7.079 ms  6.904 ms
 4  153.152.223.29 (153.152.223.29)  11.779 ms  14.903 ms  16.173 ms
 5  p252--502.osk-d-acr01.sphere.ad.jp (210.153.241.245)  20.572 ms  24.208 ms  24.016 ms
 6  p102--2026.k-otemachi-core1.sphere.ad.jp (202.239.117.33)  34.603 ms
    p101--2025.n-otemachi-core1.sphere.ad.jp (202.239.117.1)  28.541 ms
    p102--2026.k-otemachi-core1.sphere.ad.jp (202.239.117.33)  24.338 ms
 7  ix-ae-16-0.tcore2.tv2-tokyo.as6453.net (180.87.181.113)  20.852 ms
    ix-ge-1-0-5.hcore1.ovc-tokyo.as6453.net (180.87.181.65)  22.720 ms
    ix-ae-16-0.tcore2.tv2-tokyo.as6453.net (180.87.181.113)  22.847 ms
 8  if-ae-15-2.tcore2.tv2-tokyo.as6453.net (120.29.217.9)  92.022 ms
    if-et-24-2.hcore2.kv8-chiba.as6453.net (180.87.181.73)  27.490 ms
    if-ae-15-2.tcore2.tv2-tokyo.as6453.net (120.29.217.9)  94.665 ms
 9  if-ae-23-3.tcore1.svw-singapore.as6453.net (116.0.74.7)  97.566 ms
    if-ae-36-2.tcore1.svw-singapore.as6453.net (120.29.217.13)  92.545 ms
    if-ae-23-3.tcore1.svw-singapore.as6453.net (116.0.74.7)  90.865 ms
10  if-ae-11-2.thar1.svq-singapore.as6453.net (180.87.98.37)  89.245 ms  96.020 ms  91.922 ms
11  if-ae-11-2.thar1.svq-singapore.as6453.net (180.87.98.37)  91.025 ms
    120.29.214.142 (120.29.214.142)  101.792 ms
    if-ae-11-2.thar1.svq-singapore.as6453.net (180.87.98.37)  105.177 ms
12  138.197.245.9 (138.197.245.9)  96.864 ms
    138.197.245.5 (138.197.245.5)  95.857 ms *
13  * * 138.197.245.5 (138.197.245.5)  100.156 ms
14  206.189.89.118 (206.189.89.118)  97.187 ms !Z *  100.550 ms !Z

これを見ると、シンガポールを経由してNetlifyのキャッシュサーバにアクセスしていることがわかる。これはNetlifyのサポートエンジニアが書いていた情報と合致する。

レイテンシを確認する

キャッシュサーバへアクセスする際のネットワークのレイテンシを見るためにpingコマンドを打ってみる。

$ ping -c 10 -s 1024 ***.netlify.app

PING ***.netlify.app (178.128.17.49): 1024 data bytes
1032 bytes from 178.128.17.49: icmp_seq=0 ttl=50 time=105.612 ms
1032 bytes from 178.128.17.49: icmp_seq=1 ttl=50 time=97.794 ms
1032 bytes from 178.128.17.49: icmp_seq=2 ttl=50 time=100.096 ms
1032 bytes from 178.128.17.49: icmp_seq=3 ttl=50 time=98.198 ms
1032 bytes from 178.128.17.49: icmp_seq=4 ttl=50 time=96.984 ms
1032 bytes from 178.128.17.49: icmp_seq=5 ttl=50 time=98.017 ms
1032 bytes from 178.128.17.49: icmp_seq=6 ttl=50 time=97.703 ms
1032 bytes from 178.128.17.49: icmp_seq=7 ttl=50 time=97.684 ms
1032 bytes from 178.128.17.49: icmp_seq=8 ttl=50 time=104.290 ms
1032 bytes from 178.128.17.49: icmp_seq=9 ttl=50 time=98.981 ms

--- ***.netlify.app ping statistics ---
10 packets transmitted, 10 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 96.984/99.536/105.612/2.839 ms

RTT(パケットが行って返ってくるまでの時間)が約100ms程度あることがわかる。

比較のために国内にCDNノードがあるCloudFrontを使っているこのブログ(blog.anatoo.jp)のRTTを確認してみると約15-20ms程度だった。

$ ping -c 5 -s 1024 blog.anatoo.jp

PING d2973cirduzgv9.cloudfront.net (13.249.146.3): 1024 data bytes
1032 bytes from 13.249.146.3: icmp_seq=0 ttl=244 time=14.537 ms
1032 bytes from 13.249.146.3: icmp_seq=1 ttl=244 time=15.278 ms
1032 bytes from 13.249.146.3: icmp_seq=2 ttl=244 time=21.195 ms
1032 bytes from 13.249.146.3: icmp_seq=3 ttl=244 time=21.263 ms
1032 bytes from 13.249.146.3: icmp_seq=4 ttl=244 time=20.353 ms

--- d2973cirduzgv9.cloudfront.net ping statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 14.537/18.525/21.263/2.980 ms

この時点でNetlifyのシンガポールにあるサーバのレイテンシは国内のサーバに比べると約5-7倍大きいことがわかった。

ウェブパフォーマンス上、レイテンシがどれぐらいあるかは重要である。というのもHTTPプロトコルはTCPプロトコル上に構築されており、TCPプロトコルの場合RTT(要するにpingで測った値)とTCPウィンドウサイズによってTCPコネクションあたりのスループットの限界値が定まるからである。これは次の式で示すことができる。

スループットの限界値(Kbytes/sec) = TCPウィンドウサイズ(Kbytes) / RTT(s)

これはあくまで理論上の限界値であり、実際にはパケットロスや輻輳制御、HTTPサーバやTCPの上位プロトコル(HTTP/1.1やHTTP/2)自体のオーバーヘッド等があるので実測値はこれよりも下がる。

パケットキャプチャして調べる

この辺りで、Netlify遅すぎるということになって調査は切り上げた。ただ、レイテンシの割にはそれでもまだ遅い気がしたので、ここからはプライベートな時間を使ってWiresharkを使ってパケットキャプチャしてもう少し詳しく調べてみることにした。

TCPコネクションを確立する際のパケットを見てみると、Netlifyが提供する *.netlify.app と通信するコネクションではTCP window scale optionが有効になっていない。このオプションが無効になっているとセッション中のTCPウィンドウサイズの最大値が64kbに制限される。

Wiresharkでのパケットキャプチャ

通信中のパケットのTCPウィンドウサイズを見てみると64Kbで固定されている。そこで次のようにスループットの限界値を計算した。

スループットの限界値(Kbytes/sec) = 64Kbytes / 0.1s = 640Kbytes/sec

先程測ったRTTが100ms程度だったので、RTT=0.1sとしてスループットの限界値を計算すると640Kbytes/secとなる。ApacheBenchで計測した際の実測値だとこれの半分ぐらいなので限界値としてはこんなもんだと思う。

このスループットの限界値は、TCPコネクションひとつあたりの限界値であり複数のTCPコネクションを確立すればこれよりもスループットが出るが、NetlifyではHTTP/2が有効になっているため確立されるTCPコネクションはひとつだけだった。

HTTP/1.1の場合、ブラウザはTCPコネクションをオリジンごとに最大6つまで作るが、HTTP/2の場合には1つだけである。 HTTP/2プロトコルの場合、複数の双方向ストリームを扱えるためわざわざ複数のTCPコネクションを確立する必要がないからである。

つまりNetlifyの場合、スループットの出ない1つのコネクションで複数のファイルを読み込むような形になっていた。

結論

  • 通常のプランだとNetlifyのCDNノードは日本国内にはなく、日本国内からNetlifyのホストするサイトにアクセスするとシンガポールのキャッシュサーバに誘導される
  • NetlifyのCDNのファイル配信パフォーマンスは日本国内からだと非常に悪いので、いわゆる普通のCDNとしての速度を期待しないほうがいい
  • 読み込みのパフォーマンスを改善させるためには、Netlifyに直接アクセスさせるのではなく、前段に他のCDNやキャッシュサーバを挟んだりするほうがいいだろう
  • もしくはFirebase Hostingなど他のCDN付きホスティングサービスに切り替えを検討してみてもよいだろう

このブログの著者について

id:anatooこと久保田光則。福岡在住。ソフトウェアエンジニア兼UI/UXデザイナー。RelayHub, LLCという会社をやっています。

anatoo.jp Twitter Facebook

© Mitsunori Kubota