GitLab 유틸리티
GitLab v19.1개발을 더 쉽게 하기 위해 다음과 같은 여러 유틸리티를 개발했습니다. merge_hash.rb를 참고하세요. 오버라이드 메서드에 오타가 있는 경우. 검사는 다음 중 하나에 해당할 때만 수행됩니다. 오버라이드 메서드가 클래스에 정의된 경우, 또는:
개발을 더 쉽게 하기 위해 다음과 같은 여러 유틸리티를 개발했습니다.
MergeHash#
merge_hash.rb를 참고하세요.
- 해시, 배열, 기타 객체가 될 수 있는 요소들의 배열을 깊게(deep) 병합합니다.
Gitlab::Utils::MergeHash.merge(
[{ hello: ["world"] },
{ hello: "Everyone" },
{ hello: { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] } },
"Goodbye", "Hallo"]
)
결과:
[
{
hello:
[
"world",
"Everyone",
{ greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] }
]
},
"Goodbye"
]
- 해시의 모든 키와 값을 배열로 추출합니다.
Gitlab::Utils::MergeHash.crush(
{ hello: "world", this: { crushes: ["an entire", "hash"] } }
)
결과:
[:hello, "world", :this, :crushes, "an entire", "hash"]
Override#
override.rb를 참고하세요.
- 이 유틸리티는 하나의 메서드가 다른 메서드를 오버라이드하는지 여부를 확인하는 데 도움이 됩니다.
Java의
@Override어노테이션이나 Scala의override키워드와 동일한 개념입니다. 단, 프로덕션 런타임 오버헤드를 피하기 위해ENV['STATIC_VERIFICATION']이 설정된 경우에만 이 검사를 실행합니다. 이 유틸리티는 다음을 확인하는 데 유용합니다.
오버라이드 메서드에 오타가 있는 경우.
- 오버라이드된 메서드의 이름을 변경하여 기존 오버라이드 메서드가 더 이상 관련이 없게 된 경우.
간단한 예시는 다음과 같습니다.
class Base
def execute
end
end
class Derived < Base
extend ::Gitlab::Utils::Override
override :execute # Override check happens here
def execute
end
end
모듈에서도 동작합니다.
module Extension
extend ::Gitlab::Utils::Override
override :execute # Modules do not check this immediately
def execute
end
end
class Derived < Base
prepend Extension # Override check happens here, not in the module
end
검사는 다음 중 하나에 해당할 때만 수행됩니다.
오버라이드 메서드가 클래스에 정의된 경우, 또는:
- 오버라이드 메서드가 모듈에 정의되어 있고 클래스나 모듈에 prepend된 경우.
클래스 또는 prepend된 모듈만 실제로 메서드를 오버라이드할 수 있기 때문입니다. 모듈을 다른 곳에 include하거나 extend하는 것은 오버라이드를 수행할 수 없습니다.
ActiveSupport::Concern, prepend, class_methods와의 상호작용#
클래스 메서드를 포함하는 ActiveSupport::Concern을 사용하면 ActiveSupport::Concern이 일반적인 Ruby 모듈처럼 동작하지 않기 때문에 예상한 결과를 얻지 못합니다.
prepend를 활성화하기 위해 ActiveSupport::Concern의 패치로 이미 Prependable을 사용하고 있기 때문에, override 및 class_methods와의 상호작용 방식에 영향이 있습니다.
우회 방법으로는 ClassMethods를 정의하는 Prependable 모듈에 extend합니다.
이를 통해 위에서 언급한 맥락에서 사용되는 class_methods를 검증하기 위해 override를 사용할 수 있습니다.
이 우회 방법은 검증을 실행할 때만 적용되며, 애플리케이션 자체를 실행할 때는 적용되지 않습니다.
다음은 이 우회 방법의 효과를 보여주는 코드 블록 예시입니다.
module Base
extend ActiveSupport::Concern
class_methods do
def f
end
end
end
module Derived
include Base
end
# Without the workaround
Base.f # => NoMethodError
Derived.f # => nil
# With the workaround
Base.f # => nil
Derived.f # => nil
StrongMemoize#
strong_memoize.rb를 참고하세요.
- 값이
nil이거나false인 경우에도 값을 메모이제이션합니다.
우리는 종종 @value ||= compute를 사용합니다.
그러나 compute가 결국 nil을 반환할 수 있고 다시 계산하고 싶지 않은 경우에는 이 방법이 잘 동작하지 않습니다.
대신 defined?를 사용하여 값이 설정되었는지 여부를 확인할 수 있습니다.
이러한 패턴을 작성하는 것은 번거로우므로, StrongMemoize가 이러한 패턴을 사용하는 데 도움이 됩니다.
다음과 같은 패턴 대신:
class Find
def result
return @result if defined?(@result)
@result = search
end
end
다음과 같이 작성할 수 있습니다.
class Find
include Gitlab::Utils::StrongMemoize
def result
search
end
strong_memoize_attr :result
def enabled?
Feature.enabled?(:some_feature)
end
strong_memoize_attr :enabled?
end
파라미터가 있는 메서드에 strong_memoize_attr를 사용하는 것은 지원되지 않습니다.
override와 함께 사용하면 동작하지 않으며 잘못된 결과를 메모이제이션할 수 있습니다.
대신 strong_memoize_with를 사용하세요.
# bad
def expensive_method(arg)
# ...
end
strong_memoize_attr :expensive_method
# good
def expensive_method(arg)
strong_memoize_with(:expensive_method, arg) do
# ...
end
end
인수를 받는 메서드를 메모이제이션하는 데 도움이 되는 strong_memoize_with도 있습니다.
이는 가능한 인수 값의 수가 적거나, 루프에서 일관되게 반복되는 인수를 가진 메서드에 사용해야 합니다.
class Find
include Gitlab::Utils::StrongMemoize
def result(basic: true)
strong_memoize_with(:result, basic) do
search(basic)
end
end
end
- 메모이제이션 초기화
class Find
include Gitlab::Utils::StrongMemoize
end
Find.new.clear_memoization(:result)
RequestCache#
request_cache.rb를 참고하세요.
이 모듈은 RequestStore에서 값을 캐시하는 간단한 방법을 제공하며, 캐시 키는 클래스 이름, 메서드 이름, 선택적으로 커스터마이즈된 인스턴스 수준 값, 선택적으로 커스터마이즈된 메서드 수준 값, 그리고 선택적인 메서드 인수를 기반으로 합니다.
인스턴스 수준의 커스터마이즈된 값만 사용하는 간단한 예시는 다음과 같습니다.
class UserAccess
extend Gitlab::Cache::RequestCache
request_cache_key do
[user&.id, project&.id]
end
request_cache def can_push_to_branch?(ref)
# ...
end
end
이 방법으로 can_push_to_branch?의 결과는 캐시 키를 기반으로 RequestStore.store에 캐시됩니다.
RequestStore가 현재 활성화되어 있지 않으면 해시에 저장되고 인스턴스 변수에 저장되어 캐시 로직이 동일하게 유지됩니다.
다른 메서드에 대해 다른 전략을 설정할 수도 있습니다.
class Commit
extend Gitlab::Cache::RequestCache
def author
User.find_by_any_email(author_email)
end
request_cache(:author) { author_email }
end
ReactiveCaching#
ReactiveCaching 문서를 참고하세요.
TokenAuthenticatable#
TokenAuthenticatable 문서를 참고하세요.
CircuitBreaker#
Gitlab::CircuitBreaker는 서킷 브레이커 보호가 필요한 코드를 실행해야 하는 모든 클래스에 래핑할 수 있습니다.
서킷 브레이커 기능으로 코드 블록을 감싸는 run_with_circuit 메서드를 제공하여 연쇄 장애를 방지하고 시스템 복원력을 향상시킵니다.
서킷 브레이커 패턴에 대한 자세한 내용은 다음을 참고하세요.
CircuitBreaker 사용#
CircuitBreaker 래퍼를 사용하려면 다음과 같이 합니다.
class MyService
def call_external_service
Gitlab::CircuitBreaker.run_with_circuit('ServiceName') do
# Code that interacts with external service goes here
raise Gitlab::CircuitBreaker::InternalServerError # if there is an issue
end
end
end
call_external_service 메서드는 외부 서비스와 상호작용하는 예시 메서드입니다.
외부 서비스와 상호작용하는 코드를 run_with_circuit으로 감싸면 해당 메서드는 서킷 브레이커 내에서 실행됩니다.
메서드는 InternalServerError 에러를 발생시켜야 하며, 이 에러는 코드 블록 실행 중 발생하면 에러 임계값에 집계됩니다.
서킷 브레이커는 에러 수와 요청 비율을 추적하고, 설정된 에러 임계값 또는 볼륨 임계값에 도달하면 서킷을 엽니다.
서킷이 열려 있으면 이후 요청은 코드 블록을 실행하지 않고 빠르게 실패하며, 서킷 브레이커는 서킷을 다시 닫기 전에 서비스의 가용성을 테스트하기 위해 주기적으로 소수의 요청을 통과시킵니다.
구성#
캐시 키로 사용되는 고유한 서킷마다 서비스 이름을 지정해야 합니다.
이는 서킷을 식별하는 CamelCase 문자열이어야 합니다.
서킷 브레이커에는 서킷별로 재정의할 수 있는 기본값이 있습니다. 예를 들면 다음과 같습니다.
Gitlab::CircuitBreaker.run_with_circuit('ServiceName', options = { volume_threshold: 5 }) do
...
end
기본값은 다음과 같습니다.
-
exceptions:[Gitlab::CircuitBreaker::InternalServerError] -
error_threshold:50 -
volume_threshold:10 -
sleep_window:90 -
time_window:60