ReactiveCaching

ReactiveCaching 관심사는 백그라운드에서 일부 데이터를 가져와 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를 호출하면 제공된 블록을 호출하여 캐시된 값을 얻습니다. 또한 reactive_cache_lifetime 값으로 캐시의 수명을 연장합니다.

수명이 만료되면 더 이상 백그라운드 작업이 예약되지 않고 #with_reactive_cache를 다시 호출하면 nil을 반환하여 프로세스가 처음부터 다시 시작됩니다.

ReactiveCaching에 하드 제한 설정

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

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

사용 시기

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

사용 방법

모델 및 통합에서

ReactiveCaching 관심사는 모델뿐만 아니라 통합(app/models/integrations)에서도 사용할 수 있습니다.

  1. 모델에 관심사를 포함합니다.

    모델에 관심사를 포함하려면:

    include ReactiveCaching
    

    통합에 관심사를 포함하려면:

    include Integrations::ReactivelyCached
    
  2. 모델이나 통합에서 calculate_reactive_cache 메서드를 구현합니다.
  3. 모델이나 통합에서 캐시된 값이 필요한 경우 with_reactive_cache를 호출합니다.
  4. 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 관심사가 캐시를 업데이트할 때마다 호출됩니다. 새 캐시 값이 이전 캐시 값과 동일한 경우에는 호출되지 않습니다. 새 값이 캐시에 저장될 때만 호출됩니다.
  • 캐시가 업데이트될 때마다 작업을 수행할 수 있습니다.

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

이들은 ReactiveCaching에서 제공되는 메서드로서 모델/서비스에서 호출되어야 합니다.

#with_reactive_cache (필수)

  • with_reactive_cachecalculate_reactive_cache의 결과가 필요한 곳에서 호출되어야 합니다.
  • with_reactive_cache에는 블록을 제공할 수 있습니다. with_reactive_cache는 또한 임의의 수의 인수를 받을 수 있습니다. with_reactive_cache에 전달된 인수는 calculate_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에 전달된 매개변수입니다.
  • 이 concern을 통합(app/models/integrations/)에 포함시키는 경우, 기본값을 재정의해야 합니다:

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

    만약 reactive_cache_key가 위와 정확히 같다면, 기존의 Integrations::ReactivelyCached concern을 사용할 수 있습니다.

self.reactive_cache_lease_timeout

  • ReactiveCaching은 캐시 계산이 여러 워커에 의해 동시에 실행되지 않도록 Gitlab::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 메서드에 의해 수행되는 작업의 유형입니다. 이 속성에 따라 올바른 워커를 선택하여 캐싱 작업을 처리할 수 있습니다. 작업이 외부 요청을 수행하는 경우 (예: Kubernetes, 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를 호출합니다.