Giải quyết vấn đề khi caching resource, CDN caching
. 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-djeZ1BRJl ...
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 john@example.com 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(awidth, 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.