ReactiveCaching

ReactiveCaching concern은 백그라운드에서 일부 데이터를 가져와 Rails 캐시에 저장하여 요청이 있는 한 항상 최신 상태로 유지합니다. reactive_cache_lifetime 동안 데이터가 요청되지 않으면 새로 고침을 멈추고 제거됩니다.

예시

class Foo < ApplicationRecord
  include ReactiveCaching
  
  after_save :clear_reactive_cache!
  
  def calculate_reactive_cache(param1, param2)
    # 여기서 비용이 많이 드는 작업을 수행합니다. 이 메서드의 반환 값이 캐시됩니다
  end
  
  def result
    # `with_reactive_cache`에 임의의 인수를 전달할 수 있습니다. `calculate_reactive_cache`가 동일한 인수로 호출됩니다.
    with_reactive_cache(param1, param2) do |data|
      # ...
    end
  end
end

이 예시에서 #result가 처음 호출되면 nil을 반환합니다. 그러나 #calculate_reactive_cache를 호출하여 초기 캐시 수명을 10분으로 설정하는 백그라운드 워커를 대기열에 넣습니다.

작동 방식

처음 #with_reactive_cache가 호출되면 백그라운드 작업이 대기열에 넣어지고 with_reactive_cachenil을 반환합니다. 백그라운드 작업은 #calculate_reactive_cache를 호출하고 그 반환 값을 저장합니다. 또한 reactive_cache_refresh_interval 후 다시 백그라운드 작업을 대기열에 넣습니다. 따라서 저장된 값을 항상 최신 상태로 유지합니다. 계산은 절대 동시에 실행되지 않습니다.

값이 캐시된 상태에서 #with_reactive_cache를 호출하면 #with_reactive_cache에 주어진 블록을 호출하여 캐시된 값이 생성됩니다. 또한 reactive_cache_lifetime 값을 사용하여 캐시의 수명을 연장합니다.

수명이 만료되면 더는 백그라운드 작업을 대기열에 넣지 않으며 #with_reactive_cache를 다시 호출하면 nil을 반환하여 프로세스를 처음부터 다시 시작합니다.

ReactiveCaching의 하드 제한 설정하기

성능을 유지하기 위해 ReactiveCaching을 포함하는 클래스에서 하드 캐싱 제한을 설정해야 합니다. 설정 방법은 여기 예제를 참조하세요.

자세한 정보는 내부 이슈 Redis (또는 ReactiveCache) 소프트 및 하드 제한을 참조하세요.

사용 시기

  • 외부 API로의 요청을 수행해야 하는 경우(예: k8s API에 대한 요청)에 사용합니다. 응용프로그램 서버 워커가 외부 요청 기간 동안 차단되지 않는 것이 좋습니다.
  • 모델이 많은 데이터베이스 호출이나 다른 시간이 많이 소요되는 계산을 수행해야 하는 경우 사용합니다.

사용 방법

모델 및 통합에서

ReactiveCaching concern은 모델 및 통합(app/models/integrations)에서 사용될 수 있습니다.

  1. 모델에 concern 포함하기:

    include ReactiveCaching
    

    통합에 concern 포함하기:

    include Integrations::ReactivelyCached
    
  2. 모델이나 통합에서 calculate_reactive_cache 메서드 구현하기.
  3. 모델이나 통합에서 캐시된 값이 필요한 경우 with_reactive_cache 호출하기.
  4. self.reactive_cache_work_type 설정하기.

컨트롤러에서

ReactiveCaching을 사용하는 모델이나 서비스 메서드를 호출하는 컨트롤러 엔드포인트는 백그라운드 작업이 완료될 때까지 기다리지 않아야 합니다.

  • ReactiveCaching을 사용하는 모델이나 서비스 메서드를 호출하는 API는 캐시가 계산되고 있을 때(#with_reactive_cachenil을 반환하는 경우) 202 accepted를 반환해야 합니다.
  • 또한 폴링 간격 헤더를 설정해야 하며 Gitlab::PollingInterval.set_header를 사용합니다.
  • API의 소비자는 API를 폴링하기를 기대합니다.
  • 서버 부하를 줄이기 위해 ETag 캐싱을 구현하는 것도 고려할 수 있습니다.

모델이나 서비스에서 구현해야 하는 메서드

이러한 메서드는 ReactiveCaching을 포함하는 모델/서비스에서 구현해야 하는 메서드입니다.

#calculate_reactive_cache (필수)

  • 이 메서드는 구현되어야 합니다. 해당 메서드의 반환 값이 캐시됩니다.
  • 캐시를 채우어야 할 때 ReactiveCaching에서 호출됩니다.
  • with_reactive_cache에 전달된 인수도 calculate_reactive_cache로 전달됩니다.

#reactive_cache_updated (선택 사항)

  • 이 메서드는 필요한 경우 구현할 수 있습니다.
  • ReactiveCaching concern이 캐시가 업데이트될 때마다 이 메서드가 호출됩니다. 새 캐시 값이 이전 캐시 값과 동일한 경우에는 이 메서드가 호출되지 않습니다. 새 값이 캐시에 저장된 경우에만 호출됩니다.
  • 캐시가 업데이트될 때마다 작업을 수행할 수 있습니다.

모델이나 서비스에서 호출하는 메서드

이러한 메서드는 ReactiveCaching에서 제공되며 모델/서비스에서 호출해야 합니다.

#with_reactive_cache (필수)

  • 필요한 경우 with_reactive_cache를 호출하여 calculate_reactive_cache의 결과를 얻을 수 있어야 합니다.
  • with_reactive_cache에 블록을 지정할 수 있습니다. with_reactive_cache에는 임의의 인수를 전달할 수도 있습니다. with_reactive_cache에 전달된 인수는 캐시 키 이름에 추가됩니다.
  • 결과가 이미 캐시되어 있는 경우 with_reactive_cache를 호출하면 블록이 호출되어 캐시된 값이 생성됩니다. 또한 캐시가 제한시간인 reactive_cache_lifetime 값으로 재설정됩니다.
  • 결과가 아직 캐시되지 않은 경우 with_reactive_cachenil을 반환합니다. 또한 calculate_reactive_cache를 호출하고 결과를 캐시하는 백그라운드 작업이 대기열에 넣습니다.
  • 백그라운드 작업이 완료되고 결과가 캐시되면 다음 with_reactive_cache 호출에서 캐시된 값을 얻을 수 있습니다.
  • 아래 예시에서 datawith_reactive_cache에 주어진 블록에 전달되는 캐시된 값입니다.

    class Foo < ApplicationRecord
      include ReactiveCaching
        
      def calculate_reactive_cache(param1, param2)
        # 여기서 비용이 많이 드는 작업을 수행합니다. 이 메서드의 반환 값이 캐시됩니다
      end
        
      def result
        with_reactive_cache(param1, param2) do |data|
          # ...
        end
      end
    end
    

#clear_reactive_cache! (선택 사항)

  • 이 메서드는 캐시를 만료/지우기 위해 호출할 수 있습니다. 예를 들어, 모델이 수정된 후에 캐시를 지우기 위해 모델의 after_save 콜백에서 호출할 수 있습니다.
  • 이 메서드를 with_reactive_cache에 전달된 인수와 동일한 매개변수로 호출해야 합니다. 왜냐하면 매개변수가 캐시 키의 일부이기 때문입니다.

#without_reactive_cache (선택 사항)

  • 이는 디버깅 목적으로 사용할 수 있는 편의 메서드입니다.
  • 이 메서드는 현재 프로세스에서 백그라운드 워커가 아닌 현재 프로세스에서 calculate_reactive_cache를 호출합니다.

구성 가능한 옵션

조정할 수 있는 몇 가지 class_attribute 옵션이 있습니다.

self.reactive_cache_key

  • 이 속성의 값은 dataalive 캐시 키 이름의 접두어입니다. with_reactive_cache에 전달된 매개변수가 캐시 키 이름의 나머지 부분을 형성합니다.
  • 기본적으로 이 키는 모델의 이름과 레코드의 ID를 사용합니다.

    self.reactive_cache_key = -> (record) { [model_name.singular, record.id] }
    
  • data 캐시 키는 "ExampleModel:1:arg1:arg2"이고 alive 캐시 키는 "ExampleModel:1:arg1:arg2:alive"이며, 여기서 ExampleModel은 모델의 이름이고 1은 레코드의 ID이며 arg1arg2with_reactive_cache에 전달된 매개변수입니다.
  • 통합(app/models/integrations/)에 이 concern을 포함하는 경우 기본값을 재정의해야 합니다.

    self.reactive_cache_key = ->(integration) { [integration.class.model_name.singular, integration.project_id] }
    

    reactive_cache_key가 위와 정확히 동일한 경우 기존의 Integrations::ReactivelyCached concern을 사용할 수 있습니다.

self.reactive_cache_lease_timeout

  • ReactiveCachingGitlab::ExclusiveLease를 사용하여 캐시 계산이 여러 워커에 의해 동시에 실행되지 않도록 보장합니다.
  • 이 속성은 Gitlab::ExclusiveLease의 타임아웃입니다.
  • 기본값은 2분이지만 다른 타임아웃이 필요한 경우 재정의할 수 있습니다.
self.reactive_cache_lease_timeout = 2.minutes

self.reactive_cache_refresh_interval

  • 이는 캐시가 새로 고침되는 주기입니다.
  • 기본값은 1분입니다.
self.reactive_cache_refresh_interval = 1.minute

self.reactive_cache_lifetime

  • 이는 캐시 요청이 없는 경우 캐시가 지워지는 기간입니다.
  • 기본값은 10분입니다. 이 캐시 값에 대한 요청이 10분 동안 없으면 캐시가 만료됩니다.
  • 캐시가 만료되기 전에 해당 캐시 값이 요청되면 캐시의 타임아웃이 reactive_cache_lifetime로 재설정됩니다.
self.reactive_cache_lifetime = 10.minutes

self.reactive_cache_hard_limit

  • 이는 ReactiveCaching에서 캐싱을 허용하는 최대 데이터 크기입니다.
  • 기본값은 1메가바이트입니다. 이 값을 초과하는 데이터는 캐시되지 않으며 Sentry에서 ReactiveCaching::ExceededReactiveCacheLimit를 조용히 발생시킵니다.
self.reactive_cache_hard_limit = 5.megabytes

self.reactive_cache_work_type

  • 이는 calculate_reactive_cache 메서드에 의해 수행되는 작업의 유형입니다. 이 속성을 기반으로 해당 작업을 처리할 올바른 워커를 선택할 수 있습니다. 작업이 외부 요청을 수행하는 경우 (예: 쿠버네티스, Sentry) :external_dependency로 설정하고, 그렇지 않으면 :no_dependency로 설정하세요.

self.reactive_cache_worker_finder

  • 이는 백그라운드 워커가 calculate_reactive_cache를 호출할 수 있는 개체를 찾거나 생성하는 데 사용되는 메서드입니다.
  • 기본적으로 모델 기본 키를 사용하여 개체를 찾습니다.

    self.reactive_cache_worker_finder = ->(id, *_args) do
      find_by(primary_key => id)
    end
    
  • 기본 동작은 사용자 정의 reactive_cache_worker_finder를 정의함으로써 재정의할 수 있습니다.

    class Foo < ApplicationRecord
      include ReactiveCaching
        
      self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
        
      def self.from_cache(var1, var2)
        # 이 메서드는 "bar1"과 "bar2"를 매개변수로 백그라운드 워커에 의해 호출됩니다.
        new(var1, var2)
      end
        
      def initialize(var1, var2)
        # ...
      end
        
      def calculate_reactive_cache(var1, var2)
        # 여기서 비용이 많이 드는 작업이 수행됩니다. 이 메서드의 반환 값은 캐시됩니다.
      end
        
      def result
        with_reactive_cache("bar1", "bar2") do |data|
          # ...
        end
      end
    end
    
    • 이 예제에서 기본 키 ID는 reactive_cache_worker_finder로 전달되며 with_reactive_cache에 전달된 매개변수와 함께 전달됩니다.
    • 사용자 정의 reactive_cache_worker_finderwith_reactive_cache에 전달된 매개변수로 .from_cache를 호출합니다.