海外向けサイトをCDNで高速化しようぜ？

海外向けサイトを普通に日本のサーバーから配信するととても遅くなります。
高速化しないと海外の競合と戦えません。
どうすれば高速化できるのでしょうか？

※　リアルタイムで試行錯誤しながら更新しているので雑です

## 3行で

- 海外の一般消費者が日本のサーバーにアクセスするとTTFBに400ms〜1000ms以上かかる
- 最初にアクセスするページも含めてCDNを使うとキャッシュヒットした場合にTTFBを100ms以下に短縮できる
- でも過疎っているページだとキャッシュヒット率が低いので意味ない
- cdn77のようなキャッシュウォーミング(事前に特定のURLを全てのデータセンターにおいておく)に対応したCDNを使うとキャッシュヒット率が上がるはず

## TTFBの計測

[Keycdn Web Performance Test](https://tools.keycdn.com/performance)で世界の14箇所からのTTFBを計測できます。

https://pulse.turbobytes.com はDNSやHTTP headerもチェックできます。こちらは一般の回線からアクセスするみたいなので、海外の一般消費者をシミュレーションできます。

日本のサーバーから配信しているサイトの計測結果です。
日本では15msに対して、海外では500ms～1000ms程度かかっています。
DNSは大差ありません。
ConnectとTLSに時間がかかります。

![keycdn-aimastering.png](https://qiita-image-store.s3.amazonaws.com/0/115870/c752ee7d-6c31-1f23-e9db-b7914872b614.png)

PageSpeedで計測したTTFBは、460msです。
実際のユーザーのTTFBがどちらに近いのか分かりませんが、仮にkeycdnに近いと仮定すると、最適化しないとビジネス影響が大きいと思います。
JS圧縮とかcss圧縮はメジャーな最適化ですが、それらをいくら頑張ってもそもそもTTFBで台無しになるのが悲しいですね。

TTFBを短縮するにはどうすれば良いでしょうか？

## 接続に必要なラウンドトリップ回数

### TCP Connect

https://t32k.me/mol/log/reduce-http-requests-one-second/
によると、ラウンドトリップが1回必要みたいです。

### TLS

以下によると、TLS 1.3だとラウンドトリップは1回で、TLS1.2だと2回らしいです。
https://www.wolfssl.jp/wolfblog/2017/07/22/tls13-latency/

## モバイル

モバイルだとラウンドトリップタイムがもっと長いみたいです。
https://developers.google.com/speed/docs/insights/mobile?hl=ja

## ソリューション

### CDN

Cloudflareを使ってキャッシュしたサイトのTTFB計測結果です。
普通に速いですね。

<img width="1052" alt="screenshot 2019-01-11 13.17.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/115870/e41603d7-fc13-d12c-0d25-cfe7bf5da62c.png">

### キャッシュしないCDN for キャッシュできない場合

以下の記事の手法が使えると思います。
https://nulab-inc.com/ja/blog/typetalk/typetalk-api-acceleration-with-non-cache-cdn/

細かい原理は知りませんが、実際に高速化されています。

Cloudflareを使ったサイトのキャッシュをオフにした場合のTTFB計測結果です。
Cloudflareでキャッシュをオフにする方法: https://webania.net/disable-cache-in-cloudflare/
速くなっていないですね。TLSは短縮されていますが、バックエンドとの通信で多分4ラウンドトリップくらいかかっています。多分近場のcloudflareエッジサーバーとのTLS接続までは速いが、そこからバックエンドとの通信が、CDNを介さずにバックエンドと通信した場合と同じくらいかかっています。

<img width="1052" alt="screenshot 2019-01-11 13.22.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/115870/260ffd16-f0e7-6e69-9823-136dcca8c053.png">

上記記事では、cloudfrontを使っているみたいなので、cloudflareとcloudfrontとの違いかもしれません。

https://support.cloudflare.com/hc/en-us/articles/214534978-Are-the-HTTP-2-or-SPDY-protocols-supported-between-Cloudflare-and-the-origin-server-
cloudflareはバックエンドとhttp 1.1で通信しているみたいです。keep aliveしているかは知りません。

connectにかかる時間から考えると1回のラウンドトリップは長くても多分250ms程度です。250msで光は地球を1.9周します。その他のオーバーヘッドとかを考えると妥当な数字だと思います。
TTFBと1回のラウンドトリップ時間から考えると、多分4回くらいラウンドトリップしています。

これを一回に減らせれば良いです。そのためには、CDN的なサーバーとバックエンドが一回のラウンドトリップで通信できれば良いです。

cloudflareのRailgunというものを見つけました。名前がかっこいいですね。
推測ですが、図にpermanent fast connectionと書いてあるので、上記アイデアを実現しているかもしれません。
https://www.cloudflare.com/website-optimization/railgun/

>Railgun is available for customers with a Cloudflare Business or Enterprise plan or customers hosted with an Optimized Hosting Partner.
https://www.cloudflare.com/website-optimization/railgun/

ビジネスプラン契約で使えるみたいです。

https://www.cloudflare.com/plans/
ビジネスプランは$200/month/domainです。

gcpかAWSを使って、世界中にエッジサーバーを自前で用意してnginxでhttp1.1のkeep aliveでバックエンドに接続すればRailgunと同じ仕組みを作れるかもしれませんが、メンテコストを考えるとRailgun使うのが良さそうですね。

http2でリバースプロキシはできないみたいです。
https://serverfault.com/questions/765258/use-http-2-0-between-nginx-reverse-proxy-and-backend-webserver

こちらの記事で、似た話題が議論されています。
https://www.fastly.com/blog/reusing-backend-connections-increase-performance

### HTTP2 or Keep Alive

JavascriptからAPIをたたくとかなら、http2かhttp1.1 keep aliveすると良いと思います。http2はnginxの設定で簡単にできます。

### TLS1.3 (費用対効果悪いので無視でOK)

TLS1.3にするとラウンドトリップを1回にできます。

nginxでTLS1.3を有効にするにはnginxをTLS1.3に対応させたビルドが必要です。

>Nginx version 1.13.0 or greater built against OpenSSL 1.1.1 or greater.
https://www.howtoforge.com/how-to-enable-tls-13-in-nginx/

nginxの公式dockerイメージだとダメみたいです。
https://github.com/nginxinc/docker-nginx/issues/251

方法1: nginxでTLS 1.3に対応している非公式dockerイメージを使う
https://hub.docker.com/r/alexcohn/nginx-tls13/dockerfile

方法2: 公式dockerイメージに以下のパッチを当てる
https://github.com/nginxinc/docker-nginx/issues/190#issuecomment-441449050
dockerイメージ作りました -> https://hub.docker.com/r/contribu/nginx-with-tls13

イメージ名: contribu/nginx-with-tls13:latest

私は方法2を使いました。理由は公式dockerイメージとの互換性です。
細かい差でハマって時間を喰いたくないです。

ビルドが用意できたら。nginx設定方法
https://ma.ttias.be/enable-tls-1-3-nginx/

TLS1.3が有効になっているか確認する方法
https://dev.ssllabs.com/ssltest

TLS1.3を有効にして日本のサーバーのTTFBを計測した結果。

<img width="1058" alt="screenshot 2019-01-11 13.11.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/115870/f6adf60c-34ae-5f21-0c80-815a81190a8b.png">

速くなっていないですね。TLS1.3が有効になっていることは確認できたので、keycdnがTLS1.3を使っていないか、そもそもそんなに効果ないかだと思います。

## PageSpeedの罠

計測していて思ったのですが、PageSpeedのTTFBはバグっています。
問題があると問題ないと表示され、問題がないと問題あると表示されます。
これのせいで油断している方は多いのではないでしょうか？

## 海外のベータテスター

海外のベータテスターに自分のサービスを使って感想を教えてもらったことがあります。
全て遅くてくそだ、みたいな感想をもらいました。
そのときは、JSがもっさりしているとか、サーバーがもっさりしているとか思ったのですが、TTFBのことを言っていたのかもしれません。

## なぜかアジアが多かった

海外向けサイトを遅いまま公開していた頃、なぜかアクセスがベトナム、中国、台湾、韓国など、そのへんの国から多く来ていました。
アジアだから文化的な親和性が高いのかとか思っていましたが、単純に距離が近いせいでTTFBが小さくなり、UXが上がり、滞在時間が上がり、googleに高く評価されていただけなのかもしれません。
CDNで最適化したので、今後は全世界からアクセスが来ることを期待しています。

## 感想

世間知らずなだけかもしれませんが、今回始めてTTFBがかなり遅いことや、CDNがそれに対してかなり効くことを知りました。

実際、海外SEOは海外サーバーが重要だという情報がいくつかあります。その原因は、サーバーのgeoipや、TTFBなど、諸説ありますが、CDNを使えばどちらも解決できます。
https://note.mu/makoto_arii/n/nc32a6665f98b
https://blog.siteengine.co.jp/kaizen/server-overseas.html

そもそも海外SEOという分野がニッチなのかもしれませんが、かなり積極的に調べないと到達できない情報でした。

高速化するならCDNを使えというのは当たり前といえば当たり前ですが、使ったほうが良いレベルではなく、海外で土俵に立つにはCDN必須であるということは、軽くぐぐっただけではわからない情報です。

私が検索下手なだけかもしれませんが、CDNでぐぐって最初に見つける情報は、jqueryはCDNで配信したほうが速いのか？どのCDNが一番速いか？多くの人がjqueryを使っていればローカルキャッシュに残るから速い。とかそういう話でした。CDNの価値の一つかもしれませんが、一番大きい価値ではありません。

CDNの一番大きい価値は、海外向けサイトです。
知ってるって言われそうですが。

## 呼び水効果について

>②：SNS広告を使って「呼び水流入」を引っ張ってきた
>https://note.mu/makoto_arii/n/nc32a6665f98b

自動でニュースサイトに転載されるようなプレスリリースサービスで配信するのも手だと思います。海外だと、

## 参考情報

https://kinsta.com/jp/blog/ttfb/
勉強になります。

https://moz.com/blog/improving-search-rank-by-optimizing-your-time-to-first-byte のグラフを見てください。検索順位とTTFBの中央値の関係です。

日本のサーバーから配信すると海外のTTFBは平均で700msらいになると思うので、単純に考えると、このグラフだとあきらかに圏外ですね。分散次第ですが。
逆にCDNをちゃんと使ってTTFBを100ms以下にすれば圧勝できます。

https://www.slideshare.net/tech_jstream/cdn-icn
なるほど、CDNの利害構造について。面白いです

## まとめ

### キャッシュ可能なサイト

普通にCDNを使うのが良いです。
アセットだけではなく、最初に取得するページ自体をCDN化しないと、他でどんなに最適化してもロードに400ms〜1000msかかるサイトになります。

### キャッシュ不可能なサイト

cloudflare Railgun的なしくみを使って、国間のラウンドトリップ回数を最小回数(1回)に減らすのが良いです。

キャッシュ不可能なサイトはAPIとかだと思いますが、APIなら最初の接続以外はhttp2で多分ラウンドトリップ一回で通信できるので、妥協しても良いかもしれません。

妥協する場合は、ドメインまるごとcloudflareにしてしまい、キャッシュ不可能なところだけキャッシュをオフにするか(セキュリティに注意する必要がある)、ドメインを分けてしまえば良いと思います。

## 追記 (2019/01/14)

<img width="815" alt="screenshot 2019-01-14 11.27.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/115870/79d78048-9f80-ec83-0d6e-7e10bf2f5601.png">

海外からのアクセスだと、google analyticsの読み込み時間が、オレンジとか青とかになる。サンプル数が少ないからあれだけど。少なくとも、10秒以上になった人が二人以上いることはわかる。

これは多分PageSpeedやPingdomやTTFB計測ツールの計測結果から予想できない。それらの計測結果でも十分悪い結果が出るが、実際はそれの3倍くらい悪いということ。

CDNで解決されているかはサンプルが増えないとわからない。サンプルが増えたらまたグラフ貼る。

google analyticsのタグを以下のようにすると(site_speed_sample_rate)全員分の読み込み時間を計測できる。アクセス数が少ない場合に有効。
```
gtag('config', 'UA-XXXXXXXX-X', { 'site_speed_sample_rate': 100 });
```

## UC Browser

なるほど。UC Browserというのを見かけたが、そういう事情もあるのか
https://qiita.com/takanamito/items/8c2b6bc24ea01381f1b5

iphone版のUC Browserでスピードモードを使ってみたが、何KBsavedが増えないので、多分機能しなかった。

## CDNで解決しない場合

しばらくCDNで解決するかを観察しますが、解決しない場合は別手段を考えます。
たとえば、キャッシュヒット率が低い場合は解決しません。
今の所かなり低い気もします。

## キャッシュヒット率

<img width="909" alt="screenshot 2019-01-17 10.35.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/115870/89d5ae0d-df7b-f018-a972-5dbef809fa9f.png">

かなりキャッシュヒット率がかなり低いです。突出している部分は、リンクチェッカーbotのアクセスなので、それ以外の部分に注目してください。

そもそも現状のアクセス数が少ない場合、cloudflareを普通に使っても、キャッシュヒット率が低く、高速化に貢献しないということが言えそうです。

次のプランを考えてみます。

### 前提

一般のサイトでは当てはまらないかもしれませんが、私のケースだと、同じドメインでAPIを提供しており、そのAPIが動画や音声を扱うので転送量が高い。APIはCDNを使う必要はないが、同じドメインで提供しているから、CDNを使うとバイパスしたとしても転送量課金の対象になってしまう。

なので、CDNを選ぶときは転送量課金が無い、または、極端に安いものを対象とします。

### 候補1 cloudflare + 自前のcache warming

DDOSみたいな感じになるので、法的リスクを考えて、却下。

### 候補2 cache warmingに対応した他のCDN

cloudflareはenterpriseで対応している。
https://support.cloudflare.com/hc/en-us/articles/206776707-Does-Cloudflare-Do-Prefetching-

enterprise料金は$2000〜3000/month

cdn77は、転送量課金が無料ではないが安くて、prefetchに対応している。
しかし、パス単位でキャッシュ制御を、cdn77のダッシュボード上からはできない。
APIは認証付きなので、正しくキャッシュ制御をしないといけない。
しかも、以下のブログの記事によると、メルカリでキャッシュ制御 + CDNで流出事故が起きたらしい。
https://kiririmode.hatenablog.jp/entry/20170625/1498389317

以下の構成で行けました。
ユーザー -> cdn77 -> hostヘッダー書き換えプロキシ（nginx) -> nginx

cdn77のHost header forwarding設定は今回のユースケースではあまり有用ではありません。理由は、prefetch時に使えないからです。そのかわり、nginxでhostヘッダーを書き換えました。127.0.0.1で自分自身に対するプロキシにしています。

nginxのproxy_redirectでlocationヘッダーも置き換える必要があるかもしれません。

### 候補3 publicとprivateでドメインを分ける

SEOで、ドメインのパワーというものがあるらしく、example.com/aとexample.com/bは互いに力を高め合うけど、a.example.comとb.example.comはgoogleに別ドメインとして認識されるからその効果はない、みたいなのを見ました。本当かうそかわかりませんが

また、SSL証明書が複数必要になるというのも理由です。Let's encryptだとダメで買う必要がある場合があります。ワイルドカードにすると高いです。

なので、なるべくサブドメインではなくパスでやっていたのですが、publicなものとprivateなものでドメインを分ければセキュリティを維持しつつ、cdn77を使えます。

APIならSEO効果を気にせず分離できますが、お金がかかります。既存のいろいろなものを変更するのも面倒です。

面倒なので断念

### 候補4 このニーズに特化した自作CDN

海外の転送量無料のVPSを借りて、自前でCDNを作ります。

参考

https://otya.me/tech/dev/owned-cdn-idea/
https://qiita.com/dseg/items/db3b66f824cfcb12f065#%E3%82%AA%E3%83%AC%E3%82%AA%E3%83%ACcdn

この仕組みで動かせたらキャッシュしないもののTTFBも減らせるので良さそう
https://github.com/mmatczuk/go-http-tunnel#how-it-works

## 候補2を実施した結果

一週間後くらいに掲載

## netlifyについて

netlifyでホスティングしたサイトのTTFBはhttpsで世界中から250ms程度でした。netlifyは解決策の一つです。

## github

githubのファイルをrawで参照すると、httpsで世界中から100ms〜300ms程度でした。これも解決策の一つです。