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

Rails 성능:캐싱이 올바른 선택은 언제입니까?

프로그래밍 용어에서 캐싱은 향후 빠른 검색을 위해 값(또는 값)을 저장하는 것을 의미합니다. 일반적으로 어떤 이유로 계산 속도가 느린 값으로 이 작업을 수행합니다. 예를 들어 검색을 위해 외부 API를 입력해야 하거나 생성을 위해 많은 숫자를 처리해야 합니다.

캐시된 값은 종종 memcached 또는 Redis와 같은 별도의 서버에 저장됩니다. 디스크나 RAM에 저장할 수 있습니다. 코드에서 값비싼 함수를 여러 번 호출하는 것을 피하기 위해 변수 내부에 데이터를 '캐시'하는 경우가 많습니다.

data = some_calculation()
a(data)
b(data)

얻는 모든 속도에 대한 절충점은 오래된 데이터를 사용하고 있다는 것입니다. 캐시된 데이터가 '오래'되어 더 이상 정확하지 않으면 어떻게 됩니까? 캐시를 '무효화'하려면 지워야 합니다.

캐싱에 대한 반대론

<블록 인용>

오래된 속담에 따르면 컴퓨터 과학에는 단 두 가지 어려운 문제가 있습니다. 1. 물건 이름 짓기2. 캐시 무효화3. 하나씩 오류

캐시 무효화가 어려운 이유는 무엇입니까? 캐시된 값은 본질적으로 실제 값을 '숨깁니다'. '실제' 값이 변경될 때마다 사용자(예, 프로그래머)는 캐시를 '무효화'하여 업데이트되도록 해야 합니다.

텍스트 편집기에 '단어 수' 위젯을 추가한다고 가정합니다. 사용자가 입력할 때 단어 수를 업데이트해야 합니다. 가장 간단한 방법은 모든 키 입력에 대해 단어를 다시 계산하는 것이지만 이는 너무 느립니다. 다른 접근 방식이 있습니다:

  1. 파일을 로드할 때 단어 수를 센다.
  2. 이 단어 수를 변수에 저장합니다(또는 '캐시').
  3. 변수의 내용을 화면에 표시합니다.

이 구현은 훨씬 빠르지만 캐시된 '단어 수'는 입력할 때 변경되지 않습니다. 그렇게 하려면 단어 수가 변경될 것으로 예상할 때마다 캐시를 ​​'무효화'해야 합니다.

이제 키 입력이 이루어지면 단어(예:공백)를 감지하고 단어 카운터를 증가시킵니다. 물론 사용자가 단어를 삭제할 때도 감소합니다. 쉬운. 완료. 다음 티켓.

...하지만 잠깐만, 사용자가 클립보드에 텍스트를 잘라낼 때 단어 수를 업데이트하는 것을 기억했습니까? 텍스트를 붙여넣는 경우는 어떻습니까? 맞춤법 검사기가 오타를 두 단어로 나누는 경우는 어떻습니까?

여기서 문제는 값을 업데이트하지 않는다는 것인데, 이는 매우 사소합니다. 문제는 모든 위치에서 업데이트해야 한다는 점을 기억해야 한다는 것입니다. . 이러한 업데이트 중 하나만 누락되면 캐시 무효화 문제가 발생하여 사용자에게 오래된 값을 표시하게 됩니다.

이를 염두에 두고 캐싱을 추가하면 기술적인 복잡성과 잠재적인 버그 소스가 발생한다는 것을 알 수 있습니다. 물론 이러한 문제는 해결할 수 있지만 해결책으로 캐싱으로 넘어가기 전에 유념해야 할 사항입니다.

캐싱 없는 속도

테이블에서 캐싱을 제거하면 애플리케이션 속도를 높이는 것은 가능한 것보다 느린 시스템인 성능 병목 현상을 식별하고 수정하는 것입니다. 우리는 그것들을 세 가지 전체 범주로 그룹화할 수 있습니다:

  1. 데이터베이스 쿼리(너무 많거나 너무 느림)
  2. 렌더링 보기
  3. 애플리케이션 코드(예:과중한 계산 수행)

성능에 대해 작업할 때 진전을 이루기 위해 알아야 할 두 가지 기술이 있습니다. 프로파일링과 벤치마킹입니다.

프로파일링

프로파일링은 어디인지 아는 방법입니다. 앱에 문제가 있습니다. 템플릿 렌더링이 느리기 때문에 이 페이지가 느린가요? 아니면 데이터베이스에 백만 번 충돌하기 때문에 느린가요?

Ruby on Rails의 경우 앱 가장자리에 멋진 위젯을 추가하는 rack-mini-profiler를 추천합니다. 실행된 데이터베이스 쿼리 수, 소요 시간, 부분 렌더링 수와 같이 보고 있는 페이지를 렌더링하는 데 걸린 작업에 대한 좋은 개요를 제공합니다.

프로덕션용(프로 팁:rack-mini-profiler 생산에서 잘 작동합니다. 관리자나 개발자와 같은 특정 사용자에게만 나타나는지 확인하십시오.) 페이지 성능을 모니터링하는 Skylight, New Relic 및 Scout를 비롯한 온라인 서비스가 있습니다.

일반적으로 인용되는 대상 <= 100ms 이것은 실제 인터넷 사용에서 사용자가 감지하기 어렵기 때문에 페이지 렌더링에 좋습니다. 목표는 여러 요인에 따라 달라집니다. 한 때 성능이 좋지 않은 레거시 응용 프로그램을 작업할 때 <=1초를 목표로 삼았는데, 이는 좋지는 않지만 처음 시작할 때보다 훨씬 낫습니다.

벤치마킹

어디 문제는 벤치마크를 사용하여 최적화가 성능에 어떤 영향을 미쳤는지 확인할 수 있다는 것입니다. 개인적으로 나는 코드의 차이점을 사람이 쉽게 읽을 수 있는 방법을 제공하기 때문에 이러한 종류의 작업에 벤치마크-ips gem을 사용하는 것을 좋아합니다.

간단한 예로서 다음은 문자열 연결과 문자열 보간을 비교한 것입니다.

require 'benchmark/ips'

@a = "abc"
@b = "def"
Benchmark.ips do |x|
  x.report("Concatenation") { @a + @b }
  x.report("Interpolation") { "#{@a}#{@b}" }
  x.compare!
end

결과:

Warming up --------------------------------------
       Concatenation   316.022k i/100ms
       Interpolation   282.422k i/100ms
Calculating -------------------------------------
       Concatenation     10.353M (± 7.4%) i/s -     51.512M in   5.016567s
       Interpolation      6.615M (± 6.8%) i/s -     33.043M in   5.023636s

Comparison:
       Concatenation: 10112435.3 i/s
       Interpolation:  6721867.3 i/s - 1.50x  slower

이것은 우리에게 사람이 읽을 수 있는 훌륭한 결과를 제공하며 보간은 연결보다 1.5배 느립니다(적어도 작은 문자열의 경우). 이러한 이유로 개선하려는 방법을 복사하여 새 이름을 지정하는 것도 좋습니다. 그런 다음 빠른 비교를 실행하여 진행하면서 성능이 향상되고 있는지 확인할 수 있습니다.

성능 문제 수정

이 시점에서 우리는 앱의 어떤 부분이 느린지 압니다. 개선 사항이 발생했을 때 이를 측정할 수 있는 벤치마크가 있습니다. 이제 성능을 최적화하는 실제 작업만 하면 됩니다. 선택하는 기술은 문제가 있는 위치(데이터베이스, 보기 또는 애플리케이션)에 따라 다릅니다.

데이터베이스 성능

데이터베이스 관련 성능 문제의 경우 몇 가지 살펴봐야 할 사항이 있습니다. 첫째, 두려운 'N+1 쿼리'를 피하십시오. 이와 같은 상황은 보기에서 컬렉션을 렌더링할 때 자주 발생합니다. 예를 들어, 10개의 블로그 게시물이 있는 사용자가 있고 해당 사용자와 그의 모든 게시물을 표시하려고 합니다. 순진한 첫 번째 컷은 다음과 같을 수 있습니다.

# Controller
def show
  @user = User.find(params[:id])
end
# View
Name: <%= @user.name %>
Posts:
  <%= @user.posts each do |post| %>
    <div>Title: <%= post.title %></div>
  <% end %>

위에 표시된 접근 방식은 사용자(1개의 쿼리)를 가져온 다음 각 개별 게시물(N=10개의 쿼리)에 대한 쿼리를 실행하여 총 11개(또는 N+1)가 됩니다. 다행히도 Rails는 .includes(:post)를 추가하여 이 문제에 대한 간단한 솔루션을 제공합니다. 귀하의 ActiveRecord 쿼리에. 따라서 위의 예에서는 컨트롤러 코드를 다음과 같이 변경합니다.

def show
  @user = User.includes(:post).find(params[:id])
end

이제 사용자 을 가져올 것입니다. 하나의 데이터베이스 쿼리에서 자신의 모든 게시물 또는 게시물을 조회할 수 있습니다.

찾아야 할 또 다른 사항은 데이터베이스에 계산을 푸시할 수 있는 곳입니다. 이는 일반적으로 애플리케이션에서 동일한 작업을 수행하는 것보다 빠릅니다. 일반적인 형태는 다음과 같은 집계입니다.

total = Model.all.map(&:somefield).sum

이것은 데이터베이스에서 모든 레코드를 가져오는 것이지만 값의 실제 합은 Ruby에서 발생합니다. 다음과 같이 데이터베이스에서 계산을 수행하도록 하여 속도를 높일 수 있습니다.

total = Model.sum(:somefield)

두 열을 곱하는 것과 같이 더 복잡한 것이 필요할 수도 있습니다.

total = Model.sum('columna * columnb')

공통 데이터베이스는 이와 같은 기본 산술 및 합계 및 평균과 같은 공통 집계를 지원하므로 map(...).sum을 주의하십시오. 코드베이스에서 호출합니다.

실적 보기

템플릿 관련 성능 문제가 솔루션으로 캐싱에 더 적합하다고 말하지만, 먼저 제외해야 할 일부 낮은 결과가 여전히 있습니다.

일반적인 페이지 로드 시간의 경우 모든 Javascript 또는 CSS 라이브러리에 대해 축소된 소스를 사용하고 있는지 확인할 수 있습니다(적어도 프로덕션 서버에서).

또한 많은 수의 부분이 포함되어 있는지 주의하십시오. _widget.html.erb 템플릿을 처리하는 데 1ms가 걸리지만 페이지에 100개의 위젯이 있으면 이미 100ms가 지났습니다. 한 가지 해결책은 UI를 재고하는 것입니다. 한 번에 100개의 위젯을 화면에 표시하는 것은 일반적으로 훌륭한 사용자 경험이 아니며 페이지 매김 형식을 사용하거나 훨씬 더 과감한 UI/UX를 검토하는 것이 좋습니다.

애플리케이션 코드 성능

성능 문제가 보기 또는 데이터베이스 계층이 아니라 애플리케이션 코드 자체(즉, 데이터 조작)에 있는 경우 몇 가지 옵션이 있습니다. 하나는 위에서 설명한 쿼리로 또는 아마도 멋진 보석과 같은 것을 사용하여 데이터베이스 보기로 작업의 일부를 데이터베이스에 푸시할 수 있는지 확인하는 것입니다.

또 다른 옵션은 '무거운 리프팅'을 백그라운드 작업으로 옮기는 것입니다. 하지만 이 경우 값이 이제 비동기식으로 계산된다는 사실을 처리하기 위해 UI를 변경해야 할 수도 있습니다.

아직 캐싱이 필요합니다. 이제 무엇입니까?

이 모든 과정을 거치고 나면 아마도 캐싱이 필요한 솔루션이라고 결정했을 것입니다. 그럼 어떻게 해야 할까요? Ruby on Rails 내에서 사용할 수 있는 다양한 형태의 캐싱을 다루는 일련의 기사 중 첫 번째 기사이므로 계속 지켜봐 주시기 바랍니다.