웹 응용 프로그램 에서 자주 사용 하 는 각종 cache 상세 설명

본 고 는 Nginx,Rails,Mysql,Redis 를 예 로 들 어 다른 웹 서버,언어,데이터베이스,캐 시 서비스 와 유사 하 다.
다음은 3 층 의 설명도 로 후속 인용 에 편리 합 니 다.

1.클 라 이언 트 캐 시
한 클 라 이언 트 는 항상 같은 자원 을 방문 합 니 다.예 를 들 어 브 라 우 저 로 사이트 의 첫 페이지 를 방문 하거나 같은 글 을 보 거나 app 으로 같은 api 를 방문 합 니 다.만약 에 이 자원 이 그 가 이전에 방문 한 것 과 아무런 변화 가 없 으 면 http 규범 중의 304 Not Modified 응답 헤드(http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5)를 이용 하여 클 라 이언 트 의 캐 시 를 직접 사용 할 수 있 습 니 다.서버 에서 내용 을 다시 만 들 필요 가 없습니다.
레일 스에 fresh 가 내장 되 어 있 습 니 다.when 이 방법 은 코드 한 줄 로 완성 할 수 있 습 니 다.

class ArticlesController
 def show
  @article = Article.find(params[:id])
  fresh_when :last_modified => @article.updated_at.utc, :etag => @article
 end
end
다음 사용자 가 다시 방문 할 때 request header 에 있 는 If-Modified-Since 와 If-None-Match 를 비교 합 니 다.일치 하면 response body 가 생 성 되 지 않 고 304 로 돌아 갑 니 다.
그러나 이렇게 하면 문제 가 발생 할 수 있 습 니 다.만약 에 저희 사이트 내 비게 이 션 에 사용자 정보 가 있다 고 가정 하면 한 사용자 가 로그 인하 지 않 은 주제 에 방문 한 다음 에 로그 인 한 후에 방문 하면 페이지 에 로그 인 되 지 않 은 상태 가 표 시 됩 니 다.또는 app 에서 글 을 방문 하여 소장 하고 다음 에 이 글 에 들 어가 면 소장 되 지 않 은 상 태 를 표시 합 니 다.이 문 제 를 해결 하 는 방법 은 매우 간단 합 니 다.사용자 와 관련 된 변 수 를 etag 의 계산 에 추가 합 니 다.

  fresh_when :etag => [@article.cache_key, current_user.id]
  fresh_when :etag => [@article.cache_key, current_user_favorited]
또 하나의 구 덩이 를 들 어 nginx 가 gzip 을 열 어 rails 가 실행 한 결 과 를 압축 하면 rails 가 출력 한 etag header 를 제거 할 것 이 라 고 말 했다.nginx 의 개발 자 는 rfc 규범 에 따라 proxypass 방식 처 리 는 반드시 이렇게 해 야 합 니 다(내용 이 바 뀌 었 기 때 문).그러나 저 는 개인 적 으로 그 럴 필요 가 없다 고 생각 하여 거 친 방법 으로 src/http/modules/ngx 를 직접http_gzip_filter_module.c 이 파일 에 있 는 이 줄 코드 설명 을 지우 고 nginx 를 다시 컴 파일 합 니 다.

  //ngx_http_clear_etag(r); 
또는 nginx 소스 코드 를 바 꾸 지 않 고 gzip 을 떨 어 뜨리 고 압축 을 Rack 미들웨어 로 처리 할 수 있 습 니 다.

  config.middleware.use Rack::Deflater
controller 에 fresh 지정 하 는 것 외 에when 이외,rails 프레임 워 크 는 기본적으로 Rack::ETag middleware 를 사용 합 니 다.etag 가 없 는 response 에 etag 를 자동 으로 추가 하지만 freshwhen 에 비해 자동 etag 가 절약 할 수 있 는 것 은 클 라 이언 트 시간 일 뿐 입 니 다.서버 측은 모든 코드 를 실행 하고 curl 로 비교 합 니 다.
Rack::ETag 자동 가입 etag:

curl -v http://localhost:3000/articles/1
< Etag: "bf328447bcb2b8706193a50962035619"
< X-Runtime: 0.286958
curl -v http://localhost:3000/articles/1 --header 'If-None-Match: "bf328447bcb2b8706193a50962035619"'
< X-Runtime: 0.293798
 fresh_when:

 
curl -v http://localhost:3000/articles/1 --header 'If-None-Match: "bf328447bcb2b8706193a50962035619"'
< X-Runtime: 0.033884
2.Nginx 캐 시
일부 자원 은 사용자 상태 와 상 관 없 이 많이 호출 될 수 있 으 며 변경 되 지 않 습 니 다.예 를 들 어 뉴스 app 의 목록 api,쇼핑 몰 에서 ajax 요청 분류 메뉴 는 Nginx 로 캐 시 를 하 는 것 을 고려 할 수 있 습 니 다.
주로 두 가지 실현 방법 이 있다.
A.동적 요청 정적 파일 화
rails 요청 이 완료 되면 결 과 를 정적 파일 로 저장 하고 후속 요청 은 nginx 에서 정적 파일 내용 을 직접 제공 합 니 다.afterfilter 구현:

class CategoriesController < ActionController::Base
 after_filter :generate_static_file, :only => [:index]

 def index
  @categories = Category.all
 end

 def generate_static_file
  File.open(Rails.root.join('public', 'categories'), 'w') do |f|
   f.write response.body
  end
 end
end
또한 모든 분류 업데이트 시 이 파일 을 삭제 하고 캐 시가 새로 고침 되 지 않도록 해 야 합 니 다.

class Category < ActiveRecord::Base
 after_save :delete_static_file
 after_destroy :delete_static_file

 def delete_static_file
  File.delete Rails.root.join('public', 'categories')
 end
end
Rails 4 전에 정적 파일 캐 시 생 성 을 처리 할 때 내 장 된 cachespage,rails 4 이후 하나의 독립 gem actionpack-pagecaching,수 동 코드 와 비교 해 보면,

class CategoriesController < ActionController::Base
 caches_page :index

 def update
  #...
  expire_page action: 'index'
 end
end

서버 가 한 대 밖 에 없다 면 이 방법 은 간단 하고 실 용적 이지 만 여러 대의 서버 가 있다 면 업데이트 분 류 는 자신 만 의 서버 캐 시 를 새로 고 칠 수 있 는 문제 가 발생 할 수 있 습 니 다.nfs 로 정적 자원 디 렉 터 리 를 공유 하여 해결 하거나 두 번 째 로 해결 할 수 있 습 니 다.
B.집중 캐 시 서비스 로 정적 화
우선 Nginx 가 캐 시 에 직접 접근 할 수 있 도록 해 야 합 니 다.

 upstream redis {
  server redis_server_ip:6379;
 }

 upstream ruby_backend {
  server unicorn_server_ip1 fail_timeout=0;
  server unicorn_server_ip2 fail_timeout=0;
 }

 location /categories {
  set $redis_key $uri;
  default_type  text/html;
  redis_pass redis;
  error_page 404 = @httpapp;
 }

 location @httpapp {
  proxy_pass http://ruby_backend;
 }
Nginx 는 먼저 요청 한 uri 를 key 로 redis 에 가 져 옵 니 다.가 져 오지 못 하면(404)유 니 콘 에 게 전송 하여 처리 한 다음 generate 를 바 꿉 니 다.static_file 과 deletestatic_file 방법:

 redis_cache.set('categories', response.body)
 redis_cache.del('categories')
이렇게 하면 집중 관 리 를 제외 하고 캐 시 의 실효 시간 도 설정 할 수 있 습 니 다.실효 성 이 없 는 데 이 터 를 업데이트 할 때 새로 고침 체 제 를 처리 하지 않 고 간단하게 고정 시간 에 한 번 새로 고침 할 수 있 습 니 다.

 redis_cache.setex('categories', 3.hours.to_i, response.body)
3.전체 페이지 캐 시
Nginx 캐 시 는 매개 변수 자원 이나 사용자 상태 요청 을 처리 할 때 처리 하기 가 매우 어렵 습 니 다.이 럴 때 전체 페이지 캐 시 를 사용 할 수 있 습 니 다.
예 를 들 어 페이지 요청 목록 은 page 인 자 를 cache 에 추가 할 수 있 습 니 다.path:

class CategoriesController
 caches_action :index, :expires_in => 1.day, :cache_path => proc {"categories/index/#{params[:page].to_i}"}
end
예 를 들 어 우 리 는 rss 출력 에 대해 8 시간 만 캐 시 해 야 합 니 다.

class ArticlesController
 caches_action :index, :expires_in => 8.hours, :if => proc {request.format.rss?}
end
예 를 들 어 로그 인 하지 않 은 사용자 에 대해 우 리 는 첫 페이지 를 캐 시 할 수 있 습 니 다.

class HomeController
 caches_action :index, :expires_in => 3.hours, :if => proc {!user_signed_in?}
end
4.세 션 캐 시
앞의 두 가지 캐 시 에 사용 할 수 있 는 장면 이 제한 되 어 있다 면 세 션 캐 시 는 적용 성 이 가장 넓다.
장면 1:우 리 는 각 페이지 의 광고 코드 를 사용 하여 서로 다른 광 고 를 표시 해 야 합 니 다.만약 에 세 션 캐 시 를 사용 하지 않 으 면 모든 페이지 가 광고 코드 를 조회 하고 일정한 시간 을 들 여 html 코드 를 생 성 해 야 합 니 다.

- if advert = Advert.where(:name => request.controller_name + request.action_name, :enable => true).first
 div.ad
  = advert.content
세 션 캐 시 를 추가 하면 이 검색 을 줄 일 수 있 습 니 다.

- cache "adverts/#{request.controller_name}/#{request.action_name}", :expires_in => 1.day do
 - if advert = Advert.where(:name => request.controller_name + request.action_name, :enable => true).first
  div.ad
   = advert.content
장면 2:글 을 읽 으 면 글 의 내용 이 비교적 오래 변 하지 않 을 수 있 습 니 다.자주 변 하 는 것 은 글 의 평론 일 수 있 습 니 다.글 의 주체 부분 에 세 션 캐 시 를 추가 할 수 있 습 니 다.

- cache "articles/#{@article.id}/#{@article.updated_at.to_i}" do
 div.article
  = @article.content.markdown2html
markdown 문법 을 html 로 변환 하 는 시간 을 절약 하 였 습 니 다.여기 서 글 의 마지막 업데이트 시간 을 cache key 의 일부분 으로 합 니 다.글 내용 이 바 뀌 면 캐 시 는 자동 으로 효력 을 잃 고 기본 activerecord 의 cachekey 방법 도 updatedat,당신 도 더 많은 인 자 를 추가 할 수 있 습 니 다.예 를 들 어 article 에 댓 글 수가 있 는 conter cache 는 댓 글 수 를 업데이트 할 때 글 시간 을 업데이트 하지 않 고 이 conter 도 key 의 일부분 에 추가 할 수 있 습 니 다.
장면 3:복잡 한 페이지 구조의 생 성
데이터 구조 가 비교적 복잡 한 페이지 는 생 성 할 때 대량의 조회 와 html 렌 더 링 을 피 할 수 없습니다.세 션 캐 시 를 사용 하면 이 부분의 시간 을 크게 절약 할 수 있 습 니 다.우리 사이트 여행기 페이지http://chanyouji.com/trips/109123(작은 광 고 를 허용 하고 데 이 터 를 가 져 오 십시오)로 말 하면:
날씨 데이터,사진 데이터,텍스트 데이터 등 을 가 져 오 는 동시에 meta,keyword 등 seo 데 이 터 를 생 성 해 야 합 니 다.이 내용 들 은 다른 동적 내용 과 교차 되 고 세 션 캐 시 는 여러 개 로 나 눌 수 있 습 니 다.

- cache "trips/show/seo/#{@trip.fragment_cache_key}", :expires_in => 1.day do
 title #{trip_name @trip}
 meta name="description" content="..."
 meta name="keywords" content="..."

body
 div
  ...
- cache "trips/show/viewer/#{@trip.fragment_cache_key}", :expires_in => 1.day do
 - @trip.eager_load_all
팁,트 립 대상 에 eager 를 추가 하 였 습 니 다.load_all 방법,캐 시가 명중 되 지 않 았 을 때 검색 할 때 n+1 문제 가 발생 하지 않도록 합 니 다.

 def eager_load_all
  ActiveRecord::Associations::Preloader.new([self], {:trip_days => [:weather_station_data, :nodes => [:entry, :notes => [:photo, :video, :audio]]]}).run
 end

팁 1:조건 이 있 는 세 션 캐 시
와 cachesaction 과 달리 rails 가 가지 고 있 는 세 션 캐 시 는 지원 되 지 않 습 니 다.예 를 들 어 로그 인 하지 않 은 사용자 가 세 션 캐 시 를 사용 하고 싶 지만 로그 인 사용자 가 사용 하지 않 으 면 쓰기 가 귀 찮 습 니 다.helper 를 바 꾸 면 됩 니 다.

 def cache_if (condition, name = {}, cache_options = {}, &block)
  if condition
   cache(name, cache_options, &block)
  else
   yield
  end
 end

- cache_if !user_signed_in?, "xxx", :expires_in => 1.day do
팁 2:관련 대상 의 자동 업데이트
상용 대상 updateat 시간 스탬프 는 cache key 로 서 관련 대상 에 touch 옵션 을 추가 하여 관련 대상 의 시간 스탬프 를 자동 으로 업데이트 할 수 있 습 니 다.예 를 들 어 우 리 는 글 댓 글 을 업데이트 하거나 삭제 할 때 자동 으로 업데이트 할 수 있 습 니 다.

class Article
 has_many :comments
end

class Comment
 belongs_to :article, :touch => true
end
5.데이터 조회 캐 시
일반적으로 웹 응용 성능 병목 은 DB IO 에 나타 나 데이터 조회 캐 시 를 잘 하고 데이터 베 이 스 를 조회 하 는 횟수 를 줄 이면 전체적인 응답 시간 을 크게 향상 시 킬 수 있다.
데이터 조회 캐 시 는 2 가지 로 나 뉜 다.
A.같은 요청 주기 내의 캐 시
글 목록 을 보 여 주 는 예 를 들 어 글 제목 과 글 종 류 를 출력 합 니 다.해당 코드 는 다음 과 같 습 니 다.

# controller
 def index
  @articles = Article.first(10)
 end

# view
- @articles.each do |article|
 h1 = article.name
 span = article.category.name
유사 한 sql 조회 10 개가 발생 합 니 다:

SELECT `categories`.* FROM `categories` WHERE `categories`.`id` = ?
rails 에는 query cache(https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb)가 내장 되 어 있 습 니 다.같은 요청 주기 에 update/delete/insert 작업 이 없 으 면 같은 sql 조 회 를 캐 시 합 니 다.글 유형 이 같 으 면 데이터 베 이 스 를 실제로 조회 하 는 것 은 1 번 뿐 입 니 다.
문장 유형 이 다 르 면 N+1 조회 문제(흔히 볼 수 있 는 성능 병목)가 발생 한다.rails 가 추천 하 는 해결 방법 은 Eager Loading Associations(http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations)이다.
 

 def index
  @articles = Article.includes(:category).first(10)
 end
검색 어

SELECT `categories`.* FROM `categories` WHERE `categories`.`id` in (?,?,?...)
B.크로스 요청 주기 캐 시
같은 요청 주기 캐 시가 가 져 오 는 성능 최 적 화 는 매우 제한 적 이다.많은 경우 에 우 리 는 크로스 요청 주기 캐 시 를 사용 하여 자주 사용 하 는 데이터(예 를 들 어 User model)캐 시 를 사용 해 야 한다.active record 에 있어 통 일 된 조회 인 터 페 이 스 를 이용 하여 fetch cache 를 사용 하고 callback 을 이용 하여 expire cache 를 사용 하면 쉽게 실현 할 수 있 으 며 기 존의 gem 도 사용 할 수 있다.
예 를 들 면 idenitycache ( https://github.com/Shopify/identity_cache )

class User < ActiveRecord::Base
 include IdentityCache
end

class Article < ActiveRecord::Base
 include IdentityCache
 cached_belongs_to :user
end

#       

User.fetch(1)
Article.find(2).user
이 gem 의 장점 은 코드 가 간단 하고 cache 설정 이 유연 하 며 확장 이 편리 하 다 는 것 이다.단점 은 서로 다른 조회 방법 명(fetch)과 추가 적 인 관계 정 의 를 사용 해 야 한 다 는 것 이다.
데이터 캐 시 없 는 애플 리 케 이 션 에 빈 틈 없 이 캐 시 기능 을 추가 하려 면@hooopo 가 만 든 second 를 추천 합 니 다.level_cache ( https://github.com/hooopo/second_level_cache ) 。

class User < ActiveRecord::Base
 acts_as_cached(:version => 1, :expires_in => 1.week)
end
\#find 방법 을 사용 하면 캐 시 에 명중 합 니 다.
User.find(1)
\#다른 belongs 를 추가 로 사용 할 필요 가 없습니다정의
Article.find(2).user
실현 원 리 는 active record 바 텀 arel sql ast 처리(https://github.com/hooopo/second_level_cache/blob/master/lib/second_level_cache/arel/wheres.rb)를 확장 하 는 것 이다.
틈새 없 이 접속 하 는 것 이 장점 입 니 다.확장 이 어렵 고 소량의 필드 만 가 져 오 는 조회 에 캐 시 할 수 없 는 것 이 단점 입 니 다.
6.데이터베이스 캐 시
편집 중
이 6 가지 캐 시 는 클 라 이언 트 에서 서버 까지 서로 다른 위치 에 분포 되 어 있 으 며 절약 할 수 있 는 시간 도 많 고 적 게 순서대로 배열 되 어 있다.

좋은 웹페이지 즐겨찾기