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

Rails의 프래그먼트 캐싱

Rails 애플리케이션이 요청을 수락하면 컨트롤러는 일반적으로 모델에 요청된 데이터를 요청합니다. 그런 다음 모델은 데이터베이스에서 이를 가져와 컨트롤러로 다시 전달합니다. 컨트롤러는 마침내 사람이 읽을 수 있는 형식으로 데이터를 나타내는 뷰를 렌더링합니다.

어떤 상황에서는 보기를 렌더링하는 것이 비용이 많이 드는 작업입니다. 예를 들어 상점에서 사용 가능한 모든 제품 목록을 표시할 때와 같이 보기에 많은 데이터를 표시해야 하는 경우 특히 그렇습니다. 이와 같은 경우, 특히 데이터가 너무 자주 변경되지 않는 경우 반환된 뷰의 일부를 캐싱하면 작업 속도를 높일 수 있습니다.

<블록 인용>

👋 이 기사가 마음에 드시면 Ruby(on Rails) 성능에 대해 더 많이 썼습니다. Ruby 성능 모니터링 체크리스트를 확인하세요.

로컬 캐싱 테스트

캐싱은 앱에서 항상 새로운 응답을 받을 수 있도록 개발 시 기본적으로 꺼져 있습니다. 로컬에서 캐싱을 테스트하려면 개발 구성에서 이를 켜야 합니다.

Rails 5에서는 명령줄에서 일시적으로 캐싱을 켤 수 있습니다. 이것은 메모리 저장소를 사용합니다. 즉, 캐시된 조각이 웹 서버의 Ruby 프로세스에서 메모리에 보관됩니다.

$ rails dev:cache
Development mode is now being cached.

동일한 명령을 실행하여 캐싱을 다시 끌 수 있습니다.

프래그먼트 캐싱

한 페이지에 상점의 모든 제품을 표시하는 페이지가 있다고 가정해 보겠습니다. 이를 위해 제품을 보여주는 색인 보기가 있습니다.

# app/views/products/index.html.erb
<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Description</th>
      <th>Image url</th>
      <th>Price</th>
      <th colspan="3"></th>
    </tr>
  </thead>
 
  <tbody>
    <% @products.each do |product| %>
      <%= render product %>
    <% end %>
  </tbody>
</table>

각 제품에 대해 _product.html.erb 부분이 렌더링되어 제품 세부 정보와 함께 테이블 행을 표시합니다.

# app/views/products/_product.html.erb
<tr>
  <td><%= product.title %></td>
  <td><%= product.description %></td>
  <td><%= product.image_url %></td>
  <td><%= product.price %></td>
  <td><%= link_to 'Show', product %></td>
  <td><%= link_to 'Edit', edit_product_path(product) %></td>
  <td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>

데이터베이스에 25개 제품이 있는 개발 모드에서 로컬로 페이지를 요청하는 데 약 300밀리초가 걸립니다. 거의 모든 시간이 부분 렌더링에 사용됩니다.

대부분의 경우 번들 자산, 적은 로깅 및 더 빠른 웹 서버로 인해 프로덕션에서 응답이 더 빠를 것입니다. 개발 중인 숫자는 정확하지 않지만 요청의 어느 부분이 가장 느린지 보여주므로 속도를 높일 수 있습니다.

Started GET "/products" for ::1 at 2018-03-13 12:16:08 +0100
Processing by ProductsController#index as HTML
  Rendering products/index.html.erb within layouts/application
  Product Load (0.4ms)  SELECT "products".* FROM "products"
  Rendered products/_product.html.erb (1.4ms)
  Rendered products/_product.html.erb (0.4ms)
  Rendered products/_product.html.erb (0.4ms)
  Rendered products/_product.html.erb (0.3ms)
  Rendered products/_product.html.erb (0.5ms)
  Rendered products/_product.html.erb (2.0ms)
  Rendered products/_product.html.erb (0.9ms)
  Rendered products/_product.html.erb (0.4ms)
  Rendered products/_product.html.erb (0.5ms)
  Rendered products/_product.html.erb (0.5ms)
  Rendered products/_product.html.erb (0.6ms)
  Rendered products/_product.html.erb (0.6ms)
  Rendered products/_product.html.erb (0.6ms)
  Rendered products/_product.html.erb (0.6ms)
  Rendered products/_product.html.erb (0.7ms)
  Rendered products/_product.html.erb (0.6ms)
  Rendered products/_product.html.erb (0.7ms)
  Rendered products/_product.html.erb (0.6ms)
  Rendered products/_product.html.erb (0.5ms)
  Rendered products/_product.html.erb (0.7ms)
  Rendered products/_product.html.erb (0.6ms)
  Rendered products/_product.html.erb (0.9ms)
  Rendered products/_product.html.erb (0.6ms)
  Rendered products/_product.html.erb (0.5ms)
  Rendered products/_product.html.erb (0.6ms)
  Rendered products/index.html.erb within layouts/application (257.5ms)
Completed 200 OK in 295ms(Views: 290.4ms | ActiveRecord: 0.4ms)

이러한 모든 부분을 렌더링하는 시간을 절약하기 위해 Rails의 내장 fragment caching을 사용할 수 있습니다. , 렌더링된 뷰의 일부를 조각으로 저장합니다. 후속 요청의 경우 다시 렌더링하는 대신 미리 저장된 조각이 사용됩니다.

조각을 캐시하려면 cache를 사용하여 블록으로 래핑합니다. 도우미.

<table>
  # ...
  <tbody>
    <% @products.each do |product| %>
      <% cache(product) do %>
        <%= render product %>
      <% end %>
    <% end %>
  </tbody>
</table>

이러한 제품을 캐싱하면 응답 속도가 빨라지는지 확인하기 위해 페이지를 두 번 요청합니다. 두 번째 요청은 보기의 각 제품이 미리 렌더링되어 캐시에 이미 저장되어 있으므로 훨씬 더 빠르게 실행되어야 합니다.

Started GET "/products" for ::1 at 2018-03-13 12:17:29 +0100
Processing by ProductsController#index as HTML
  Rendering products/index.html.erb within layouts/application
  Product Load (0.4ms)  SELECT "products".* FROM "products"
  Rendered products/index.html.erb within layouts/application (21.2ms)
Completed 200 OK in 55ms (Views: 50.8ms | ActiveRecord: 0.4ms)

효과가 있었다! 두 번째 요청은 첫 번째 요청보다 5배 이상 빨랐습니다. 로그에는 캐시에서 직접 로드되기 때문에 부분 렌더링이 표시되지 않습니다.

캐시 만료

cache를 호출할 때 위의 예에서 도우미는 product를 전달했습니다. 개체를 캐시 종속성으로 . 이를 통해 도우미는 캐시된 조각의 내용이 제품 개체에 종속되어 있음을 알 수 있습니다.

내부적으로 제품 개체에는 #cache_key가 있습니다. 캐시된 조각에 대한 키를 빌드하는 데 사용되는 메서드입니다. 전체 조각의 키는 다음과 같습니다.

views/products/42-20180302103130041320/75dda06d36880e8b0ae6cac0a44fb56d

캐시 키는 다음 두 부분으로 구성됩니다.

  • "view/products"는 캐시 클래스입니다. .
  • 42 제품의 ID입니다.
  • 20180302103130041320 제품의 업데이트 날짜
  • 입니다.
  • 75dda06d36880e8b0ae6cac0a44fb56d 템플릿 트리의 다이제스트입니다.

이 키는 제품이 업데이트되거나 템플릿의 모든 항목이 변경될 때마다 변경됩니다. 그럴 때 캐시에 누락이 발생하여 조각이 다시 렌더링되고 새 조각이 저장됩니다. 이렇게 하면 구성 요소가 변경되더라도 조각이 최신 상태로 유지됩니다.

프래그먼트 캐싱을 넘어서

이 예에서 논의된 캐시된 조각은 약간의 속도 향상을 가져오지만 캐싱에는 오늘 논의한 것보다 더 많은 것이 있습니다. 러시아 인형 캐싱과 같은 전략이나 데이터베이스 쿼리 결과 캐싱과 같은 낮은 수준의 방법을 사용하면 더 큰 속도 향상을 달성할 수 있습니다.

물론 AppSignal Academy의 나중 에피소드에서 이에 대해 다룰 것입니다. 구체적으로 배우고 싶은 것이 있습니까? 주저하지 말고 @AppSignal로 알려주십시오. 물론 이 기사가 어떻게 마음에 들었는지, 또는 더 알고 싶은 다른 주제가 있는지 알고 싶습니다.