ENECHANGE Developer Blog

ENECHANGE開発者ブログ

Amazon CloudFront を導入し、enechange.jp の Wordpress の記事配信を高速化した話

ENECHANGE 株式会社チーフエンジニアの川西こと cuzic です。

最近、enechange.jp のインフラ改善に精力的に取り組んでいます。 そこで、やりたいと思っていたことがかなり実現でき、ある程度、目処がついてきました。 せっかくなので、やってきた内容についてまとめてこの tech blog に掲載していきます。

それまでの enechange.jp の構成

もともとそれ以前から Ruby on Rails 側の静的アセットについては Amazon CloudFront の CDN 経由で、配信するように設定していました。

   if ENV["RAILS_ASSET_HOST"].present?
    config.action_controller.asset_host = ENV["RAILS_ASSET_HOST"]
  end

という設定を config/environments/production.rb に記述した上で、環境変数 RAILS_ASSET_HOST を設定することで、静的ファイルだけ別ドメインを利用して配信しています。 このあたりは、 Rails ガイド: CDNで静的なアセットを提供する において、詳述されています。

しかしながら、静的アセット以外のコンテンツについては CDN を経由せず、直接 アプリケーションサーバで処理される設計でした。

特に enechange.jp/articles 以下の wordpress の記事ページは、

  • 更新頻度が高くない
  • ユーザごとに出し分けすることはない
  • アクセスの大部分を占める

という状況で、 CDN を導入することによる高速化、負荷の低下のメリットが大きいにも関わらずそのままになってしまっていました。

CDN の導入における課題とその対応

CDN を導入する場合、対応すべき課題がいくつかあり、それぞれ必要な作業を実施しました。

順に説明します。

モバイルと PC の判定

CDN を導入すると一般的に User-Agent の情報を使ってサーバサイドで処理することができなくなります。User-Agent はとても多様で、それを CDN のキャッシュキーに含めてしまうと、キャッシュヒット率がとても低くなってしまい、 CDN 導入のメリットが減少してしまうからです。

Amazon CloudFront の場合は、下記のようにカスタムの HTTP ヘッダが追加されます。

  • モバイルの場合 -CloudFront-Is-Mobile-Viewer
  • タブレットの場合
    • CloudFront-Is-Tablet-Viewer
  • PC の場合
    • CloudFront-Is-Desktop-Viewer

今まで User-Agent を使ってモバイル向けか PC 向けかの判定処理については CloudFront の設定で上記のカスタムヘッダの情報を Wordpress 側に転送するように設定した上で、判定処理を上記の HTTP ヘッダを利用するように修正する必要があります。

CloudFront のカスタムヘッダの情報を Wordpress に転送する設定

CloudFront のビヘイビアの設定で、該当のヘッダ情報を転送するように設定します。

Resource: aws_cloudfront_distribution : Forwarded Values Arguments で詳述されているので、参考にしてください。

具体的な設定内容は、ほかの設定とまとめて記事の最後に掲載しています。

Wordpress 管理機能の利用

Wordpress に管理者としてログインした場合、 最新の編集中の記事の内容などを表示することができる機能があります。

ログインし、記事を編集したあと、その記事の内容を確認しても古いままであれば、作業効率が悪くなってしまいます。一般ユーザに反映に時間がかかったとしても、ログイン中であれば最新の内容が確認できる必要があります。

Cookie の内容に基づく、キャッシュ利用可否の判定

Wordpress ログイン中でも最新の記事を表示するためには、ログイン中であれば、CDN がキャッシュを応答することなく、Wordpress のアプリケーションサーバが処理するように設定する必要があります。

ログイン中かどうかを判定するには、 Cookie の内容を使います。具体的には "wordpress_logged_in" と "wp-settings" という Cookie の値をアプリケーションサーバに転送する必要があります。

これらの値を転送すると、アプリケーションサーバは該当ユーザがログイン中かどうか判定でき、適切に処理できます。

これらの Cookie の値は、一般ユーザの場合には設定されていないものなので、キャッシュヒット率を低下させる心配もありません。

こちらについても具体的な設定内容は、記事の最後に掲載しています。

Wordpress のコードの書き換え

Wordpress のモバイルか PC かの判定条件を変更するために、 wp-includes/functions.php を編集する必要があります。

この件については、amimoto: CloudFront 配下での User-Agent の判定 で詳述されています。こちらをご参照ください。

静的アセットの処理の最適化

Wordpress の記事については上記のようなログイン中か、PC/モバイルかの判定が必要ですが、静的アセットはそのような出し分けは不要で、常に同じコンテンツを応答して問題ありません。

静的アセットについては上記で行った処理とは別に、常に同一のコンテンツを返すように設定する方が適切です。

最終的な Cloud Front の設定(抜粋)

上記の内容を踏まえて作成した Cloud Front 設定を紹介します。 本旨とあまり関係がない細かな設定は割愛していますが、参考になるかと思います。

locals {
  origin_id = "enechange-jp-origin"
  static_path_patterns = [
    "/articles/wp-content/uploads*",
    "/articles/*.css",
    "/articles/*.gif",
    "/articles/*.jpg",
    "/articles/*.js",
    "/articles/*.png",
    "/articles/*.svg"
  ]
}

resource "aws_cloudfront_distribution" "production_enechange_distribution" {
  origin {
    domain_name = "xxx.enechange.jp" # マスクしています
    origin_id   = local.origin_id
    custom_origin_config {
      http_port              = 80
      https_port             = 443
      origin_protocol_policy = "https-only"
      origin_ssl_protocols   = ["TLSv1.2"]
    }
  }

  enabled         = true // Whether the distribution is enabled to accept end user requests for content.
  is_ipv6_enabled = true

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    # 省略
  }

  // CNAMEs
  aliases = ["enechange.jp"]

  // origin behavior Default (*)
  default_cache_behavior {
    allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
    cached_methods   = ["GET", "HEAD", "OPTIONS"]
    target_origin_id = local.origin_id

    forwarded_values {
      query_string = true
      headers      = ["*"]
      cookies {
        forward = "all"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 86400
    max_ttl                = 31536000
  }

  // Cache behavior with precedence 0
  ordered_cache_behavior {
    allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
    cached_methods   = ["GET", "HEAD", "OPTIONS"]
    target_origin_id = local.origin_id
    path_pattern = "/articles/wp-admin*"

    forwarded_values {
      query_string = true
      headers      = ["*"]
      cookies {
        forward = "all"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 86400
    max_ttl                = 31536000
  }

  // Cache behavior with precedence 1
  ordered_cache_behavior {
    allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
    cached_methods   = ["GET", "HEAD", "OPTIONS"]
    target_origin_id = local.origin_id
    path_pattern = "/articles/wp-login.php*"

    forwarded_values {
        query_string = true
        headers      = ["*"]
        cookies {
          forward = "all"
        }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 86400
    max_ttl                = 31536000
  }

  dynamic "ordered_cache_behavior" {
    for_each = local.static_path_patterns
    content {
        allowed_methods  = ["GET", "HEAD"]
        cached_methods   = ["GET", "HEAD"]
        target_origin_id = local.origin_id
        path_pattern     = ordered_cache_behavior.value

        forwarded_values {
          query_string = false
          cookies {
          forward = "none"
            }
        }

      viewer_protocol_policy = "redirect-to-https"
      min_ttl                = 0
      default_ttl            = 3600
      max_ttl                = 86400
    }
  }

  ordered_cache_behavior {
    allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = local.origin_id
    path_pattern = "/articles*"

    forwarded_values {
        query_string = true
        headers      = ["CloudFront-Forwarded-Proto", "CloudFront-Is-Mobile-Viewer", "CloudFront-Is-Tablet-Viewer", "Host"]
        cookies {
            forward           = "whitelist"
            whitelisted_names = ["wordpress_logged_in*", "wp-settings*"]
        }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }
}

まとめ

今回の記事では、 Amazon CloudFront を導入して、 enechange.jp での Wordpress の記事配信を高速化した変更について、解説しました。

これから、Wordpress を利用した環境に Amazon CloudFront を導入したいと思っている方の参考になれば、幸いです。