ReactiveCaching 사용하기
GitLab v19.1이 문서는 reactive_caching.rb를 참조합니다. ReactiveCaching concern은 백그라운드에서 일부 데이터를 가져와 Rails 캐시에 저장하고, 데이터가 요청되는 동안 최신 상태를 유지하는 데 사용됩니다.
이 문서는 reactive_caching.rb를 참조합니다.
ReactiveCaching concern은 백그라운드에서 일부 데이터를 가져와 Rails 캐시에 저장하고,
데이터가 요청되는 동안 최신 상태를 유지하는 데 사용됩니다. 데이터가
reactive_cache_lifetime 동안 요청되지 않으면 갱신이 중지되고 제거됩니다.
예시#
class Foo < ApplicationRecord
include ReactiveCaching
after_save :clear_reactive_cache!
def calculate_reactive_cache(param1, param2)
# Expensive operation here. The return value of this method is cached
end
def result
# Any arguments can be passed to `with_reactive_cache`. `calculate_reactive_cache`
# will be called with the same arguments.
with_reactive_cache(param1, param2) do |data|
# ...
end
end
end
이 예시에서 #result가 처음 호출되면 nil을 반환합니다. 그러나
#calculate_reactive_cache를 호출하고 초기 캐시 수명을 10분으로 설정하는
백그라운드 워커를 큐에 추가합니다.
ReactiveCaching 작동 방식#
#with_reactive_cache가 처음 호출되면 백그라운드 job이 큐에 추가되고
with_reactive_cache는 nil을 반환합니다. 백그라운드 job은 #calculate_reactive_cache를
호출하고 그 반환값을 저장합니다. 또한 reactive_cache_refresh_interval 후에 다시 실행되도록
백그라운드 job을 다시 큐에 추가합니다. 따라서 저장된 값을 최신 상태로 유지합니다.
계산은 동시에 실행되지 않습니다.
값이 캐시된 상태에서 #with_reactive_cache를 호출하면 #with_reactive_cache에 제공된
블록이 호출되어 캐시된 값이 yield됩니다. 또한 캐시의 수명이 reactive_cache_lifetime 값만큼
연장됩니다.
수명이 만료된 후에는 더 이상 백그라운드 job이 큐에 추가되지 않으며,
#with_reactive_cache를 다시 호출하면 nil을 반환하여 전체 프로세스가 다시 시작됩니다.
ReactiveCaching에 하드 제한 설정#
성능을 유지하려면 ReactiveCaching을 포함하는 클래스에 하드 캐싱 제한을 설정해야 합니다.
설정 방법 예시를 참조하세요.
자세한 내용은 내부 이슈 Redis (or ReactiveCache) soft and hard limits를 읽어보세요.
사용 시기#
-
외부 API에 요청을 해야 하는 경우(예: k8s API에 대한 요청). 외부 요청 기간 동안 애플리케이션 서버 워커를 블로킹 상태로 유지하는 것은 권장되지 않습니다.
-
모델이 많은 데이터베이스 호출이나 기타 시간이 많이 소요되는 계산을 수행해야 하는 경우.
사용 방법#
모델 및 인테그레이션에서#
ReactiveCaching concern은 모델과 인테그레이션
(app/models/integrations)에서 모두 사용할 수 있습니다.
모델 또는 인테그레이션에 concern을 포함합니다.
모델에 concern을 포함하려면:
include ReactiveCaching
인테그레이션에 concern을 포함하려면:
include Integrations::ReactivelyCached
모델 또는 인테그레이션에서 calculate_reactive_cache 메서드를 구현합니다.
캐시된 값이 필요한 모델 또는 인테그레이션에서 with_reactive_cache를 호출합니다.
reactive_cache_work_type을 적절하게 설정합니다.
컨트롤러에서#
ReactiveCaching을 사용하는 모델 또는 서비스 메서드를 호출하는 컨트롤러 엔드포인트는
백그라운드 워커가 완료될 때까지 기다려서는 안 됩니다.
-
ReactiveCaching을 사용하는 모델 또는 서비스 메서드를 호출하는 API는 캐시가 계산되는 중일 때(#with_reactive_cache가nil을 반환할 때)202 accepted를 반환해야 합니다. -
또한
Gitlab::PollingInterval.set_header로 폴링 인터벌 헤더를 설정해야 합니다. -
API의 소비자는 API를 폴링할 것으로 예상됩니다.
-
폴링으로 인한 서버 부하를 줄이기 위해 ETag 캐싱 구현을 고려할 수도 있습니다.
모델 또는 서비스에서 구현할 메서드#
다음은 ReactiveCaching을 포함하는 모델/서비스에서 구현해야 하는 메서드입니다.
#calculate_reactive_cache (필수)#
-
이 메서드는 반드시 구현해야 합니다. 반환값이 캐시됩니다.
-
캐시를 채워야 할 때
ReactiveCaching에 의해 호출됩니다. -
with_reactive_cache에 전달된 모든 인수는calculate_reactive_cache에도 전달됩니다.
#reactive_cache_updated (선택)#
-
이 메서드는 필요한 경우 구현할 수 있습니다.
-
캐시가 업데이트될 때마다
ReactiveCachingconcern에 의해 호출됩니다. 캐시가 갱신 중이고 새 캐시 값이 이전 캐시 값과 동일하면 이 메서드는 호출되지 않습니다. 새 값이 캐시에 저장될 때만 호출됩니다. -
캐시가 업데이트될 때마다 작업을 수행하는 데 사용할 수 있습니다.
모델 또는 서비스에서 호출하는 메서드#
다음은 ReactiveCaching이 제공하는 메서드로 모델/서비스에서 호출해야 합니다.
#with_reactive_cache (필수)#
calculate_reactive_cache의 결과가 필요한 곳에서 with_reactive_cache를 호출해야 합니다.
with_reactive_cache에 블록을 제공할 수 있습니다. with_reactive_cache는 임의의 수의 인수를 받을 수도 있습니다.
with_reactive_cache에 전달된 인수는 calculate_reactive_cache에 전달됩니다.
with_reactive_cache에 전달된 인수는 캐시 키 이름에 추가됩니다.
결과가 이미 캐시된 상태에서 with_reactive_cache가 호출되면
블록이 호출되어 캐시된 값이 yield되고, 블록의 반환값이 with_reactive_cache에 의해 반환됩니다.
또한 캐시의 타임아웃이 reactive_cache_lifetime 값으로 초기화됩니다.
결과가 아직 캐시되지 않은 경우 with_reactive_cache는 nil을 반환합니다.
또한 백그라운드 job을 큐에 추가하여 calculate_reactive_cache를 호출하고 결과를 캐시합니다.
백그라운드 job이 완료되고 결과가 캐시되면, 다음번 with_reactive_cache 호출 시
캐시된 값을 가져옵니다.
아래 예시에서 data는 with_reactive_cache에 제공된 블록에 yield되는 캐시된 값입니다.
class Foo < ApplicationRecord
include ReactiveCaching
def calculate_reactive_cache(param1, param2)
# Expensive operation here. The return value of this method is cached
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#
이 속성의 값은 data 및 alive 캐시 키 이름의 접두사입니다.
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, arg1과 arg2는
with_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메서드가 수행하는 작업의 유형입니다. 이 속성을 기반으로 캐싱 job을 처리할 올바른 워커를 선택할 수 있습니다. 작업이 외부 요청(예: Kubernetes, Sentry)을 수행하는 경우:external_dependency로 설정해야 하며, 그렇지 않으면:no_dependency또는no_dependency_low_urgency로 설정합니다.
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)
# This method will be called by the background worker with "bar1" and
# "bar2" as arguments.
new(var1, var2)
end
def initialize(var1, var2)
# ...
end
def calculate_reactive_cache(var1, var2)
# Expensive operation here. The return value of this method is cached
end
def result
with_reactive_cache("bar1", "bar2") do |data|
# ...
end
end
end
이 예시에서 기본 키 ID는 with_reactive_cache에 전달된 파라미터와 함께
reactive_cache_worker_finder에 전달됩니다.
- 커스텀
reactive_cache_worker_finder는with_reactive_cache에 전달된 파라미터와 함께.from_cache를 호출합니다.
캐시 키 변경#
ReactiveCaching 작동 방식으로 인해,
calculate_reactive_cache 메서드의 파라미터를 변경하는 것은 Sidekiq 워커의 파라미터를 변경하는 것과 같습니다.
따라서 동일한 규칙을
따라야 합니다.
예를 들어, calculate_reactive_cache 메서드에 새 파라미터가 추가되는 경우:
-
기본값을 가진 인수를
calculate_reactive_cache메서드에 추가합니다(릴리스 M). -
새 인수를
with_reactive_cache메서드의 모든 호출에 추가합니다(릴리스 M+1). -
기본값을 제거합니다(릴리스 M+2).