全世界からTypetalkを爆速で使える!キャッシュしないCDNによるAPI高速化

Typetalkチームのインフラ担当の二橋 (@futahashi) です。

本稿ではTypetalkでキャッシュしないCDNを用いたAPI高速化を実現しましたので、その技術を紹介したいと思います。

Webアプリケーションに汎用的に使える技術で、簡単に導入できて改善効果も大きいので、興味を持たれた方は是非試してみて下さい!

Typetalkが抱えてた課題

Typetalk は世界中にサービス展開しているチャットツールです。

しかし、ユーザがサービスを利用する地域に依存してネットワークの応答時間に差がある問題がありました。

これは、サービスを単一のリージョンで提供しており、リージョンから離れた地域よりアクセスされると応答に時間がかかってしまうためです。

静的なコンテンツ部分はCDNによるキャッシュで高速化できていたのですが、動的な部分は対応ができていなかったため、応答に時間がかかっていました。また、ユーザーが契約しているISPによって一部のユーザーのネットワークが遅延する問題も報告されていました。

Web アプリケーションにおいてネットワークの応答時間はユーザ体験に大きく影響する重要な要素です。そこで、世界中のユーザがどこからでも快適にサービスをご利用頂けることを目指し、本取り組みを行いました。

キャッシュしない CDN 適用による API 応答時間の改善結果

課題の解決方法として、今回はキャッシュしない CDN 適用による API 応答時間の改善を実施しました。

response timeresponse time

この図は、日本からアメリカ合衆国にあるサーバへのアクセスのHTTPの応答時間の計測結果です。

応答時間が50%程になり改善されたことが確認できます。実際にサービスを使ってみると、明らかに応答時間が改善されていることが体験できました!特にモバイルからのアクセスは応答時間が短くなったことで、表示速度が体感的に大幅に向上しました。また、ISP に依存したネットワーク遅延問題も解決されたようです。通信量に比例してお金がかかりますが、そんなに高くないお値段で大きな効果を得ることができました。

次章ではそもそものCDNについて簡単に説明します。ご存知の方は読み飛ばして (本題) キャッシュしない CDN へ飛んで下さい。

(余談) CDNについて

本章はCDNについての説明になります。本題とは逸れるので、不要な方は読み飛ばして (本題) キャッシュしない CDN へ飛んで下さい。

CDN の概要

CDN (Content Delivery Network) は、Web コンテンツを広範囲に渡る多くのユーザに効率良く配信するためのネットワークの仕組みです。CDN の仕組みを図で簡単に説明します。

CDNCDN

CDN では、オリジンの Web サーバのコンテンツを複数のエッジにキャッシュします。ユーザは、 Web サイトにアクセスする際に複数ある CDN のエッジの中から最も近いエッジにアクセスするように誘導されます。この時、エッジのキャッシュにヒットした場合はキャッシュのコンテンツを利用し、ミスした場合はエッジからオリジンへアクセスして目的のコンテンツを取得します。

CDN の効果

CDN の効果を図で簡単に説明します。

  • 広範囲のユーザに効率良くコンテンツを配信できる。

CDNの効果1CDNの効果1

通常の場合、ユーザはネットワーク的に遠いオリジンにアクセスしなければなりません。CDN を用いた場合、ネットワーク的に近いエッジからコンテンツを取得できるようになり、高速にコンテンツを取得できることになるという訳です。

  • オリジンの負荷を減らすことができる。

CDNの効果2CDNの効果2

通常の場合、全てのユーザがオリジンにアクセスすることになります。CDN を用いた構成の場合、エッジのキャッシュにヒットするとオリジンへリクエストは不要になります。また、悪質なユーザによる DDoS を CDN で緩和/防御してオリジンのサービスを守ることができます。

注意点

CDN の注意点としては、エッジにキャッシュして良いのは静的コンテンツだけであるということです。マイページなどのユーザ固有の動的コンテンツ部分をキャッシュすると情報漏えいに繋がるので気をつけましょう。

(本題) キャッシュしない CDN

概要

キャッシュしない CDN は、その名の通り CDN でコンテンツをキャッシュをせずにオリジンにパススルーして使います。前章では、CDN は静的コンテンツをキャッシュすることで高速化できると説明してきました。ですが、CDN のメリットはキャッシュヒットによる高速化だけではないのです。実は CDN のエッジとオリジン間は強力なバックボーンで接続されており、安定した高速な通信ができるのです。これにより、キャッシュに頼れなかった動的コンテンツ部分の配信を高速化することができます。

キャッシュしないCDNキャッシュしないCDN

キャッシュしない CDN の効果

キャッシュしない CDN の効果としては、以下のようなことが考えられます。

  • 既存のインフラ構成に大きく影響を与えずに応答時間の改善ができる。
  • ユーザの ISP 網から最も近い CDN エッジに誘導され、経路障害や遅延が起きにくくなる。
  • 単一リージョンから世界中へ地域に依存しないネットワーク品質のサービスを展開できる。
  • CDN プロバイダが提供する保護機能が使えるようになる。

複数のリージョンにサービスを展開すると、インフラ管理/運用コストが気になってきます。現状のインフラの構成や運用スタイルを変えずに、安くグローバルにサービス展開できるのは嬉しいですね!

また、CDNのプロバイダにより異なりますが、Typetalk が利用しているAWSの CloudFront では、DDoS 攻撃の防御をするAWS Shieldの機能も付いてきます。他にも AWS WAF と連携してアクセス制御ができたり、AWS Certificate Manager の無料の証明書が使えたりするなどのメリットがあります。

注意点

いくつか注意点があるので、導入の際には気をつけて下さい。

  • キャッシュ設定を誤るとセキュリティ事故に繋がる。

キャッシュは静的コンテンツのみに有効です。例えば、ユーザのマイページはユーザごとに異なる動的コンテンツで、他ユーザと共有するコンテンツではありません。このようなコンテンツを誤ってキャッシュしてしまった場合、キャッシュされた他人の個人情報が載ったページが表示されてしまうことになります。

  • CDN プロバイダによって設定と挙動が異なる。

CDN のキャッシュ制御は、CDN プロバイダによって異なります。プロバイダによっては Cache-Control ヘッダを利用して制御する場合もありますし、CDN の設定によって制御する場合もあるのでご注意下さい。

  • ホップ数が増える。

CDN を利用することによって、ホップ数が1段増えることになります。そのため、場合によってはネットワークの応答時間の改善が見られない場合があります。また、これにより L4 の情報が変わるので、クライアントの IP を正しく取得するための処理を入れる必要があります。

  • CDN プロバイダによって WebSocket のサポート有無が異なる。

CDN プロバイダによって WebSocket のサポート有無が異なります。サポートされてない場合は、WebSocket の通信だけドメインを分けるなどして CDN を介さないようにする必要があります。

  • CDN プロバイダによってネイキッドドメインのサポート有無が異なる。

CDN プロバイダによっては、ネイキッドドメインをサポートしていません。ネイキッドドメインとは、typetalk.com のようなホスト名なしのルートドメインのことです。CDN プロバイダ検討時にはご注意下さい。

これらの注意点を踏まえた導入方法の一例を次節で説明します。

キャッシュしない CDN 適用による API 応答時間の改善の導入例

キャッシュしない CDN を利用するまでの導入方法の一例を紹介します。導入の際にご参考になると幸いです。

なお、Typetalk はクラウドは AWS 、フロントのリバースプロキシとして Nginx を利用しております。その他のポイントは以下のとおりです。

  • リアルタイムの通信を実現するために WebSocket を使っている。
  • アプリではアクセス元の IP を利用した機能を実装している。(IPアドレス制限機能)
  • 動的な部分は単一のドメインを利用している。

導入前後のインフラ構成の変化

導入前後のインフラ構成の概要は以下の通りです。

インフラ構成インフラ構成

コストの変化としては、CloudFront と ALB の料金が増える差分になります。CloudFront はサポートするエッジローケーションが異なる料金クラスに応じた通信量 (ユーザへのデータ転送量とユーザからのリクエスト数) に基づき課金されます。ALB はインスタンスの料金と LCU という接続数や帯域幅に基づく指標に応じて課金されます。

Nginxの設定

Nginx の設定で、X-Forwarded-For 偽装を防止しクライアントIPを正常に取得するための対応を入れました。

TCP/IP では送信元の IP を remote_addr として保持しています。クライアントとアプリが直接コネクションを張って通信する場合は remote_addr がクライアント IP となるのですが、間にプロキシが来るとそのままではクライアント IP を取得できなくなります。後の手順で、ALBCloudFront を追加するのですが、これにより remote_addr でクライアント IP が取得できないということになってしまいます。そこで、HTTP ヘッダであるX-Forwarded-For ヘッダや X-Real-IP ヘッダを使ってクライアントの IP を認識します。

X-Real-IP ヘッダはプロキシの経由に関係なく、クライアントIPを判別するためのヘッダになります。X-Forwarded-For ヘッダはロードバランサやプロキシを経由する時に送信元を判別するために利用される HTTP ヘッダです。以下のようなフォーマットでクライアントIPとプロキシIPが連なっていきます。

X-Forwarded-For: [クライアントIP], [プロキシIP1], [プロキシIP2]

Nginx では real_ip_header モジュールを使うことで、remote_addr を書き換えることができます。また、real_ip_recursive を有効化することで、X-Forwarded-For を再帰的に処理してクライアントIPを取得できます。例えば、以下のようにすることで X-Forwarded-For ヘッダを用いてクライアント IP を remote_addr にセットできます。

real_ip_header     X-Forwarded-For;
real_ip_recursive  on;

X-Forwarded-For と X-Real-IP ヘッダの注意点として、ヘッダの内容の偽装が簡単だということです。つまり、悪い人がクライアント IP を偽りの IP に設定できてしまうということです。Nginx では set_real_ip_from を用いて信頼できるプロキシを定義することで偽装を防ぎます。今回の場合、CloudFrontALB を経由するので、それらを設定します。ALB は VPC の CIDR を指定します。CloudFrontCloudFront エッジサーバーの場所と IP アドレス範囲 を基にエッジの IP を取得して設定します。

# Trust ALB
set_real_ip_from   [VPCのIP]
# Trust CloudFront
set_real_ip_from   [CloudFrontのエッジのIP1]
set_real_ip_from   [CloudFrontのエッジのIP2]
...

注意点として、CloudFront のエッジの IP は時々変わるので、変更を追従する必要があるということです。Typetalk では以下のように対応しました。

CloudFrontのIP反映CloudFrontのIP反映

CloudFront のエッジの IP の変更通知は SNS で受け取ることができます。詳しくはAWS の IP アドレス範囲の通知を参照してください。ポイントは、以下のとおりです。

  • SNS は[米国東部(バージニア北部)]のリージョンで設定する。
  • SNS の変更通知が来てもCloudFrontのエッジのIPが変わってない場合がある。(この SNS の通知には、CloudFront 以外の AWS のサービスの IP アドレスも含まれるため。)

SNS の変更通知をトリガに Lambda を実行します。LambdaではCloudFront のエッジの IP の変更差分をチェックして、変更されていた場合Typetalk に通知とJenkins のジョブを実行するようにしました。

Jenkins のジョブでは、Ansible の変数として保存されてるCloudFront の IP リストを更新して、Jinja2 で書かれた Nginx の Config を更新する playbook を流す処理をしています。

ALB の導入

フロントにロードバランサーとして ALB を導入しました。本ステップは必須ではないのですが、CDN のオリジンとして ALB を指定できるが嬉しいポイントです。CloudFront ではオリジンとしてEC2 やドメイン指定も可能ですが、EC2 は入れ替えることが多いですし、わざわざ専用のドメインを取得するのも好ましくないです。

ドメインの分割

WebSocket 用にサブドメイン (message.typetalk.com) を作成しました。本ステップは CloudFront が WebSocket は非サポートなので、WebSocket の通信だけ CloudFront を介さないようにするためです。

DNSを変更する時の注意点として、クライアント側で DNS のキャッシュがあることに気をつけて下さい。Typetalk では、しばらくドメインを並行運用して古い方にアクセスが来なくなったことを確認してから切り替えるようにしました。

CloudFront の設定

最後に CloudFront で CDN の設定をしました。Origin に ALB を指定し、キャッシュしないで全てをオリジンにパススルーするために Behavior の項目で以下のように設定します。

CloudFrontの設定CloudFrontの設定

最後に Route 53 でサービスの DNS を作成した CloudFront に切り替えて完了です。CloudFront はネイキッドドメインにも対応しており、Route 53 で A レコードの Alias Target に作成した CloudFront を指定して切り替わります。

注意点で述べたように、誤ってキャッシュしてしまうと事故につながるので動作検証はしっかり行うようにして下さい。複数のユーザアカウントを使って動作検証をするのが良いと思います。

最後に

今回はキャッシュしない CDN を用いた API 高速化について技術紹介をしました。CDN というと静的コンテンツのキャッシュのイメージが強いので、意外だった方もいるかも知れません。少しでもご参考になれば幸いです。

Typetalk チームでは、DevOps エンジニア、Scala エンジニアを募集しています。ヌーラボの製品や技術に興味を持たれた方はお気軽にエントリーしてみて下さい。

以下のWantedlyページからみなさんのエントリーをお待ちしています!

株式会社ヌーラボの会社情報 – Wantedly 

開発メンバー募集中

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる