Giải quyết vấn đề khi caching resource, CDN caching

0. Mở đầu

Chào các bạn, đến hẹn lại lên, hôm nay mình sẽ chia sẻ một chút về caching resource

Như các bạn đã biết, http caching (cơ chế caching của client, browser...) giúp chung ta tăng performance của ứng dụng (Có thể tìm hiểu thêm ở đây https://viblo.asia/p/tim-hieu-ve-http-caching-djeZ1BRJlWz).

Tuy nhiên, caching đem lại cho ta 1 vài rắc rối, và hôm nay chúng ta sẽ phân tích và giải quyết 1 trong số các rắc rối đó.

Vấn đề: Ảnh bị cache, khi cập nhật ảnh mới avatar, mặc dù hệ thống báo thành công nhưng khi load lại ( F5 trình duyệt ...) thì ảnh nhìn thấy vẫn là ảnh cũ

1. Cách tái hiện:

  • Upload ảnh lên hệ thống. (tham khảo http://railscasts.com/episodes/253-carrierwave-file-uploads)
  • Upload 1 ảnh (NAM) có tên avatar.jpg , (F5) chúng ta sẽ thấy ảnh avatar đã được upload thành công
  • Upload 1 ảnh khác (NỮ), cũng có tên avatar.jpg, hệ thống báo cập nhật thành công, tuy nhiên khi (F5) chúng ta sẽ thấy ảnh avatar cũ (NAM), và phải đợi khoảng 1 ngày sau chúng ta mới thấy được ảnh avatar mới (NỮ).

2. Nguyên nhân

Nguyên nhân là cả 2 ảnh NAM, NỮ đều có chung URL DOMAIN.COM/uploads/user/avatar/avatar.jpg

a) Nguyên nhân thứ 1: (Do trình duyệt lưu cache)

Khi xem ảnh NAM, trình duyệt sẽ cache nó, dù khi ảnh được chuyển thảnh NỮ trình duyệt vẫn hiển thị ra ảnh đang được cache (NAM)

b) Nguyên nhân thứ 2: Do CDN chưa cập nhật file ảnh mới

Khi hệ thống của chúng ta sử dụng CDN, cụ thể là Cloudfront (http://blog.davidelner.com/rails-asset-pipeline-serve-assets-easily-and-cheaply-from-cloudfront/) , sau khi ảnh được upload lên AWS S3, ảnh sẽ tự được clone sang các Edge Server của Cloudfront trên khắp thế giới. Sau khi cập nhật ảnh mới có cùng URL lên S3, phải đến 24h sau, cloudfront mới cập nhật toàn bộ ảnh đang được lưu trên các Edge Server

http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html

Specifying the Minimum Time that CloudFront Caches Objects for RTMP Distributions

For RTMP distributions, CloudFront keeps objects in edge caches for 24 hours by default. You can add Cache-Control or Expires headers to your objects to change the amount of time that CloudFront keeps objects in edge caches before it forwards another request to the origin. The minimum duration is 3600 seconds (one hour). If you specify a lower value, CloudFront uses 3600 seconds.

Dù có config để giảm thời gian cache xuống thì chỉ giảm được xuống 3600s, có nghĩa là sau 1h khi chúng ta upload file mới toàn bộ các Edge Server mới cập nhật ảnh mới

3. Giải pháp

a) Config để trình duyệt reload cache

b) Invalidating Objects, đẩy file mới lên tất cả các Edge Server

http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html

Tuy nhiên, cách này sẽ tốn chi phí

Paying for Object Invalidation

The first 1,000 invalidation paths that you submit per month are free; you pay for each invalidation path over 1,000 in a month. An invalidation path can be for a single object (such as /images/logo.jpg) or for multiple objects (such as /images/*). A path that includes the * wildcard counts as one path even if it causes CloudFront to invalidate thousands of objects.

This limit of 1000 invalidation paths per month applies to the total number of invalidation paths across all of the distributions that you create with one AWS account. For example, if you use the AWS account [email protected] to create three distributions, and you submit 600 invalidation paths for each distribution in a given month (for a total of 1,800 invalidation paths), AWS will charge you for 800 invalidation paths in that month. For specific information about invalidation pricing, see Amazon CloudFront Pricing. For more information about invalidation paths, see Invalidation paths.

c) Tạo ra tên file random khi image thay đổi

Giải pháp a, b phải thực hiện cả 2 mới giải quyết được vấn đề (Nếu vẫn muốn Object Invalidation , bạn có thể tham khảo ở đây https://qiita.com/chisso/items/872248b2d8e141422475). Chính vì vậy, mình quyết định chọn giải pháp c

4. Giải quyết vấn đề

Chúng ta sẽ implement để đổi tên file ảnh mỗi khi tạo mới, cập nhật ảnh avatar

Gemfile

gem "carrierwave"
gem "mini_magick"
gem "fog"
gem "aws-sdk", "~> 3"

config/initializers/carrierwave.rb

require "carrierwave/orm/activerecord"

CarrierWave.configure do |config|
  if ENV["CDN_UPLOADER"] == "true"
    config.fog_credentials = {
      provider:                "AWS",
      aws_access_key_id:       ENV["AWS_ACCESS_KEY_ID"],
      aws_secret_access_key:   ENV["AWS_SECRET_ACCESS_KEY"],
      region:                  ENV["AWS_REGION"],
      path_style:              true
    }
    config.storage :fog
    config.fog_directory  = ENV['S3_BUCKET_NAME']
    config.fog_public = Settings.carrierwave.fog_public
    config.fog_authenticated_url_expiration = eval(Settings.carrierwave.fog_expiration)
    config.fog_attributes = {
      "Cache-Control" => "max-age=#{eval(Settings.carrierwave.fog_cache_control).to_i}"
    }
    config.asset_host = ENV["CDN_ASSET_HOST"]
  else
    config.storage :file
    config.asset_host = ENV["LOCAL_ASSET_HOST"]
  end
end

module CarrierWave
  module MiniMagick
    def auto_orient
      manipulate! do |img|
        img.auto_orient
        img = yield(img) if block_given?
        img
      end
    end
  end
end

config/settings.yml

carrierwave:
  fog_cache_control: 1.hour
  fog_expiration: 1.hour
  fog_public: true
avatar:
  version:
    thumb: [280, 280]
    small_thumb: [50, 50]

app/models/user.rb

class User < ApplicationRecord
  mount_uploader :avatar, AvatarUploader
end

app/uploaders/avatar_uploader.rb

class AvatarUploader < CarrierWave::Uploader::Base

  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  include CarrierWave::MiniMagick
  include UploaderFilename

  # Choose what kind of storage to use for this uploader:
  # storage :file
  # storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  # def default_url(*args)
  #   # For Rails 3.1+ asset pipeline compatibility:
  #   # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
  #
  #   "/images/fallback/" + [version_name, "default.png"].compact.join('_')
  # end

  # Process files as they are uploaded:
  # process scale: [200, 300]
  #
  # def scale(width, height)
  #   # do something
  # end

  # Create different versions of your uploaded files:
  version :thumb do
    process resize_to_fill: Settings.avatar.version.thumb
  end

  version :small_thumb do
    process resize_to_fill: Settings.avatar.version.small_thumb
  end

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  def extension_whitelist
    %w(jpg jpeg png)
  end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  # def filename
  #   "something.jpg" if original_filename
  # end

end

app/uploaders/uploader_filename.rb

module UploaderFilename
  def filename
    if original_filename
      if model && model.read_attribute(mounted_as).present? && !model.attribute_changed?(mounted_as)
        model.read_attribute(mounted_as)
      else
        new_filename
      end
    end
  end

  def new_filename
    "#{secure_token}.#{file.extension}" if original_filename.present?
  end

  protected
  def secure_token
    var = :"@#{mounted_as}_secure_token"
    model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
  end
end

5. Kết luận

Như vậy mỗi khi update avatar, tên file sẽ được random (Dạng 1df094eb-c2b1-4689-90dd-790046d38025.jpg), URL sẽ được thay đổi, dẫn tới việc sau khi ảnh upload lên S3, các request lấy ảnh từ CDN, cloudfront sẽ nhận được ảnh avatar mới nhất.

Vấn đề đã được giải quyết.

Tài liệu tham khảo https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Create-random-and-unique-filenames-for-all-versioned-files

Hi vọng bài viết này hữu ích. ❤️