Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Ruby on Rails 애플리케이션의 HTTP 캐싱

캐싱을 설명하는 일반적인 방법은 나중에 빠르게 검색할 수 있도록 일부 데이터를 저장하는 것입니다. 때때로 이것은 다시 계산할 필요가 없도록 계산된 데이터를 저장하는 것을 의미하지만 다시 가져올 필요가 없도록 데이터를 로컬에 저장하는 것을 참조할 수도 있습니다. 운영 체제가 하드 드라이브나 SSD에서 다시 가져올 필요가 없도록 자주 액세스하는 데이터를 RAM에 유지하려고 하기 때문에 컴퓨터는 이 작업을 지속적으로 수행합니다.

마찬가지로 브라우저는 이미 다운로드한 리소스를 재사용하려고 합니다. 새 웹사이트를 처음 방문할 때 직접 본 적이 있을 것입니다. 브라우저에서 모든 항목을 풀다운해야 하므로 초기 로드 시간이 더 오래 걸립니다. 모든 이미지, 자바 스크립트 및 스타일 시트를 포함하여 필요합니다. 재미있는 사실은 CNN 홈페이지를 새로 다운로드할 때 브라우저가 1993년경의 오리지널 Doom 게임보다 더 많은 데이터를 가져온다는 것입니다. 호기심을 위해 이 블로그 게시물을 작성하는 시점에서 CNN은 내 컴퓨터에 원래 Doom 설치 프로그램은 ~2.2MB인 반면 ~15MB, 광고 차단기가 활성화된 상태입니다.

브라우저가 이 데이터를 캐시하려면 서버와 약간의 조정이 필요합니다. 브라우저는 캐시할 수 있는 항목과 기간을 알아야 합니다. 그렇지 않으면 서버에 사용 가능한 최신 버전이 있을 때 이전 콘텐츠가 표시될 수 있습니다. 이 기사에서는 이러한 클라이언트-서버 캐싱 조정이 수행되는 방식과 이를 변경하기 위해 Rails가 제공하는 방법을 살펴보겠습니다.

Ruby on Rails가 이를 처리하는 방법에 중점을 두고 있지만 실제 메커니즘은 HTTP 사양의 일부입니다. 다시 말해서, 우리가 여기서 말하는 캐싱은 인터넷의 인프라에 구워져 현대 웹사이트와 프레임워크가 개발되는 방식의 초석이 됩니다. Rails, 단일 페이지 애플리케이션(SPA) 및 정적 사이트와 같은 다양한 프레임워크는 모두 이러한 메커니즘을 사용하여 성능을 향상시킬 수 있습니다.

HTTP 요청-응답

당신은 적어도 높은 수준에서 요청-응답 라이프사이클에 익숙할 것입니다. 웹사이트에서 링크를 클릭하면 브라우저가 해당 콘텐츠에 대한 요청을 서버에 보내고 서버가 해당 콘텐츠를 다시 보냅니다(참고로 I 여기에서 많은 복잡성을 의도적으로 생략했습니다.

이 왕복 트랜잭션에서 전송되는 실제 데이터를 조금 파헤쳐 보겠습니다. 각 HTTP 메시지에는 헤더와 본문이 있습니다(와 혼동하지 마십시오. 및 HTML 태그). 요청 헤더는 액세스하려는 경로와 사용할 HTTP 메서드(예:GET/PUT/PATCH/POST)를 서버에 알려줍니다. 필요한 경우 브라우저의 개발자 도구나 curl과 같은 명령줄 도구를 사용하여 이러한 헤더를 살펴볼 수 있습니다. :

# curl -v honeybadger.io
...
> GET / HTTP/1.1
> Host: honeybadger.io
> User-Agent: curl/7.64.1
> Accept: */*

출력의 이 첫 번째 부분은 요청 헤더입니다. GET을(를) 발행합니다. honeybadger.io로 . 그 다음에는 서버가 다시 보낸 내용("응답 헤더")이 나옵니다.

>
< HTTP/1.1 301 Moved Permanently
< Cache-Control: public, max-age=0, must-revalidate
< Content-Length: 39
< Content-Security-Policy: frame-ancestors 'none'
...
< Content-Type: text/plain

응답에는 HTTP 코드(예:200 성공 또는 404 찾을 수 없음). 이 예에서는 curl이 http에 접속하려고 하기 때문에 영구 리디렉션(301)입니다. 보안 https로 리디렉션되는 URL URL. 응답 헤더에는 text/plain인 콘텐츠 유형도 포함됩니다. 여기에 있지만 몇 가지 다른 일반적인 옵션은 text/html입니다. , 텍스트/css , 텍스트/자바스크립트application/json .

응답 본문은 헤더를 따릅니다. 우리의 경우 301 리디렉션에는 본문이 필요하지 않습니다. curl -v https://www.honeybadger.io로 다시 시도한다면 , 브라우저에서 소스를 보는 것과 동일하게 여기에 홈페이지 콘텐츠가 표시됩니다.

이를 직접 실험하고 싶다면 다음 두 가지 팁이 있습니다.

  1. 컬이 있는 응답 헤더만 표시하려면(예:요청 헤더 또는 응답 본문 없음) -I를 사용하세요. 옵션, curl -I localhost:3000 .
  2. 기본적으로 Rails는 개발 환경에서 캐시하지 않습니다. rails dev:cache를 실행해야 할 수도 있습니다. 먼저.

캐시 제어 HTTP 헤더

캐싱에 관한 한 우리가 관심을 갖는 주요 헤더는 Cache-Control입니다. 헤더. 이것은 Rails 서버의 응답을 캐시할 수 있는 머신과 캐시된 데이터가 만료되는 시기를 결정하는 데 도움이 됩니다. Cache-Control 내에서 헤더에는 여러 필드가 있으며 대부분은 선택 사항입니다. 여기에서 가장 관련성이 높은 항목을 살펴보겠지만 자세한 내용은 w3.org에서 공식 HTTP 사양을 확인할 수 있습니다.

다음은 바로 사용할 수 있는 기본 Rails 응답 헤더의 샘플입니다.

< Content-Type: text/html; charset=utf-8
< Etag: W/"b41ce6c6d4bde17fd61a09e36b1e52ad"
< Cache-Control: max-age=0, private, must-revalidate

최대 연령

최대 연령 필드는 응답이 유효한 시간(초)을 포함하는 정수입니다. 기본적으로 보기에 대한 Rails 응답은 0으로 설정됩니다(즉, 응답은 즉시 만료되고 브라우저는 항상 새 버전을 받아야 함).

공개/비공개

공개 포함 또는 비공개 헤더에서 응답을 캐시할 수 있는 서버를 설정합니다. 헤더에 private가 포함된 경우 , 요청하는 클라이언트(예:브라우저)에 의해서만 캐시되며 콘텐츠 전송 네트워크(CDN) 또는 프록시와 같이 해당 서버에 도달하기 위해 통과했을 수 있는 다른 서버가 아닙니다. 헤더에 public이 포함된 경우 , 그러면 이러한 중개 서버가 응답을 캐시할 수 있습니다. Rails는 각 헤더를 private로 설정합니다. 기본적으로.

재검증해야 함

Rails는 또한 must-revalidate를 설정합니다. 기본적으로 필드. 이것은 클라이언트가 서버에 접속하여 캐시된 버전이 사용되기 전에 여전히 유효한지 확인해야 함을 의미합니다. 캐시된 버전이 유효한지 확인하기 위해 클라이언트와 서버는 ETag를 사용합니다.

ETag

ETag는 서버가 클라이언트에 응답을 보낼 때 추가하는 선택적 HTTP 헤더입니다. 일반적으로 이것은 응답 자체에 대한 일종의 체크섬입니다. 클라이언트(즉, 브라우저)가 이 리소스를 다시 요청해야 할 때 If-None-Match에 수신한 Etag(이전에 캐시된 응답이 있다고 가정)를 포함합니다. HTTP 헤더. 그러면 서버가 304로 응답할 수 있습니다. HTTP 코드("수정되지 않음") 및 빈 본문. 즉, 서버의 버전이 변경되지 않았으므로 클라이언트는 캐시된 버전을 사용해야 합니다.

강한 ETag와 약한 ETag의 두 가지 유형이 있습니다(약한 태그는 W/로 표시됩니다. 접두사). 그들은 같은 방식으로 작동하지만 강력한 ETag는 리소스의 두 복사본(서버의 버전과 로컬 캐시의 버전)이 100% 바이트 단위로 동일하다는 것을 의미합니다. 그러나 약한 ETag는 두 복사본이 바이트 단위로 동일하지 않을 수 있지만 캐시된 버전을 계속 사용할 수 있음을 나타냅니다. 이것의 일반적인 예는 Rails의 csrf_meta_tags입니다. 지속적으로 변경되는 토큰을 생성하는 도우미; 따라서 애플리케이션에 정적 페이지가 있더라도 CSRF(Cross-Site-Request-Forgery) 토큰으로 인해 새로 고칠 때 바이트 단위로 동일하지 않습니다. Rails는 기본적으로 약한 ETag를 사용합니다.

Rails의 ETag

Rails는 뷰에서 자동으로 ETag를 처리합니다. 나가는 헤더에 ETag를 포함하고 들어오는 ETag 헤더를 확인하고 304를 반환하는 미들웨어가 있습니다. 적절한 경우 (수정되지 않음) 코드. 그러나 특히 Rails는 뷰를 동적으로 생성하기 때문에 해당 뷰에 대한 ETag를 파악하기 전에 모든 렌더링 작업을 수행해야 합니다. 즉, ETag가 일치하더라도 캐시된 버전이 있는 경우 렌더링 단계를 완전히 건너뛸 수 있는 뷰 캐싱과 달리 네트워크를 통해 데이터를 보내는 데 걸리는 시간과 대역폭만 절약할 수 있습니다. 그러나 Rails는 생성된 ETag를 조정할 수 있는 몇 가지 방법을 제공합니다.

구식?

ETag 변경으로 인해 끊임없이 변화하는 CSRF 토큰을 극복하는 한 가지 방법은 stale?를 사용하는 것입니다. ActionController의 도우미 . 이를 통해 ETag(강함 또는 약함)를 직접 설정할 수 있습니다. 그러나 ActiveRecord 모델과 같은 개체를 간단히 전달할 수도 있으며 개체의 updated_at를 기반으로 ETag를 계산합니다. 타임스탬프 또는 최대 updated_at 사용 컬렉션을 통과한 경우:

class UsersController < ApplicationController
  def index
    @users = User.includes(:posts).all

    render :index if stale?(@users)
  end
end

curl로 페이지를 치면 결과를 볼 수 있습니다.

# curl -I localhost:3000 -- first page load
ETag: W/"af9ae8f2d66b9b6c4d0513f185638f1a"
# curl -I localhost:3000 -- reload (change due to CSRF token)
ETag: W/"f06158417f290334f47ea2124e08d89d"

-- Add stale? to controller code

# curl -I localhost:3000 -- reload
ETag: W/"04b9b99835c359f36551720d8e3ca6fe" -- now using `@users` to generate ETag
# curl -I localhost:3000 -- reload
ETag: W/"04b9b99835c359f36551720d8e3ca6fe" -- no change

이렇게 하면 클라이언트가 전체 페이로드를 다시 다운로드해야 하는 시기를 더 잘 제어할 수 있지만 여전히 매번 서버에 확인해야 합니다. 캐시가 여전히 유효한지 여부를 확인합니다. 이 검사를 완전히 건너뛰고 싶다면 어떻게 해야 할까요? 여기서 max-age 헤더의 필드가 들어옵니다.

expires_in 및 http_cache_forever

Rails는 ActionController에서 몇 가지 도우미 메서드를 제공합니다. max-age 조정 필드:expires_inhttp_cache_forever . 둘 다 이름을 기준으로 예상한 대로 작동합니다.

class UsersController < ApplicationController
  def index
    @users = User.includes(:posts).all

    expires_in 10.minutes
  end
end
# curl -I localhost:3000
Cache-Control: max-age=600, private

Rails는 max-age를 설정했습니다. 600(초로 10분)으로 변경하고 필수 재검증 제거 필드. 비공개를 변경할 수도 있습니다. public:true를 전달하여 필드 명명된 인수.

http_cache_forever 대부분 expires_in의 래퍼일 뿐입니다. max-age를 설정합니다. 100년으로 하고 블록 소요:

class UsersController < ApplicationController
  def index
    @users = User.includes(:posts).all

    http_cache_forever(public: true) do
      render :index
    end
  end
end
# curl -I localhost:3000
Cache-Control: max-age=3155695200, public

이러한 종류의 매우 장기적인 캐싱은 Rails 자산에 파일 내용의 해시인 "지문"이 추가되고 packs/js/application-4028feaf5babc1c1617b.js<와 같은 파일 이름을 생성하는 이유입니다. /코드> . 끝에 있는 "지문"은 파일의 내용을 파일 이름과 효과적으로 연결합니다. 내용이 변경되면 파일 이름이 변경됩니다. 이는 브라우저가 이 파일을 영원히 안전하게 캐시할 수 있음을 의미합니다. 조금이라도 변경되면 지문이 변경되기 때문입니다. 브라우저에 관한 한 다운로드해야 하는 완전히 별도의 파일입니다.

영향력 영역

이제 몇 가지 캐싱 옵션을 다루었으므로 제 조언이 다소 이상해 보일 수 있지만 이 문서에 있는 방법을 사용하지 않는 것이 좋습니다. ETag 및 HTTP 캐싱에 대해 알아두면 좋은 정보이며 Rails는 특정 문제를 해결하기 위한 몇 가지 특정 도구를 제공합니다. 그러나 주의해야 할 점은 이 모든 캐싱이 애플리케이션 외부에서 발생하므로 대부분 제어할 수 없다는 것입니다. 이 시리즈의 이전 부분에서 다룬 것처럼 Rails에서 뷰 캐싱 또는 저수준 캐싱을 사용하고 무효화 문제가 발생하면 옵션이 있습니다. 터치할 수 있습니다. 모델을 만들고 업데이트된 코드를 푸시하거나 Rails.cache에 도달할 수도 있습니다. 필요한 경우 콘솔에서 직접 수행할 수 있지만 HTTP 캐싱에서는 그렇지 않습니다. 개인적으로는 Rails.cache.clear를 실행해야 합니다. 프로덕션에서는 사용자가 브라우저 캐시를 지울 때까지 사이트가 중단되는 문제에 직면합니다(고객 서비스 팀에서도 당신을 좋아할 것입니다).

결론

이것이 Rails의 캐싱에 대한 시리즈의 끝입니다. 유용하고 유익한 정보였기를 바랍니다. 캐싱에 대한 나의 조언은 다음과 같습니다. 성능 문제가 발생하면 먼저 자주 발생하는 방법을 찾으십시오. 아마도 메모할 수 있습니다. 요청 간에 지속되는 값이 필요하십니까? 많이 사용되는 방법은 저수준 캐싱을 사용할 수 있습니다. 또는 특정 방법이 아닐 수도 있습니다. 모든 중첩 부분을 처리하여 속도를 늦추는 것입니다. 이 경우 뷰 레벨 캐싱이 도움이 될 수 있습니다. Rails는 이러한 각 문제를 대상으로 하는 "날카로운 칼"을 제공하며 언제 사용해야 하는지만 알면 됩니다.