GitLab 유틸리티

개발을 용이하게 돕기 위해 여러 유틸리티를 개발했습니다:

MergeHash

merge_hash.rb를 참조하세요:

  • 해시 배열을 깊게 병합합니다:

    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 # 여기에서 덮어쓰기 확인이 발생합니다
        def execute
        end
      end
      

      이는 모듈에 대해서도 동작합니다:

      module Extension
        extend ::Gitlab::Utils::Override
      
        override :execute # 모듈에 대해 바로 확인하지는 않습니다
        def execute
        end
      end
      
      class Derived < Base
        prepend Extension # 여기에서 덮어쓰기 확인이 발생하며, 모듈에서는 확인되지 않습니다
      end
      

      이 확인은 다음 중 하나가 되었을 때에만 발생합니다:

      • 덮어쓰기 메소드가 클래스에 정의된 경우 또는:
      • 덮어쓰기 메소드가 모듈에 정의되었으며, 클래스나 모듈에 선행(prepend)된 경우.

      왜냐하면 클래스나 선행된 모듈만이 실제로 메소드를 덮어쓸 수 있기 때문입니다. 모듈을 다른 모듈에 포함하거나 확장시키는 것은 아무것도 덮어쓸 수 없습니다.

ActiveSupport::Concern, prepend, 및 class_methods와의 상호작용

클래스 메소드를 포함하는 ActiveSupport::Concern을 사용할 때 일반적인 루비 모듈과는 다르게 동작하기 때문에 예상과 다른 결과를 얻을 수 있습니다.

이미 prepend를 활성화하기 위한 ActiveSupport::Concern 패치로 Prependable이 존재하므로, overrideclass_methods와 상호작용하는 방법에 영향을 미칩니다. 이 문제를 해결하기 위해 Prependable 모듈에 ClassMethods를 확장시킴으로써 이러한 상관관계를 확인할 수 있게 해줍니다.

이를 통해 안정성 검증을 실행하는 경우에만 class_methods를 확인하기 위해 override를 사용할 수 있습니다. 이 우회 방법은 애플리케이션 자체를 실행할 때가 아니라 검증을 실행하는 경우에만 적용됩니다.

이 우회 방법이 어떻게 작용하는지 보여주는 예시 코드는 다음과 같습니다:

module Base
  extend ActiveSupport::Concern

  class_methods do
    def f
    end
  end
end

module Derived
  include Base
end

# 우회 방법이 없을 때
Base.f    # => NoMethodError
Derived.f # => nil

# 우회 방법이 있을 때
Base.f    # => nil
Derived.f # => nil

StrongMemoize

strong_memoize.rb를 참조하세요:

  • 값이 nil 또는 false인 경우에도 값을 메모이즈합니다.

    우리는 종종 @value ||= compute를 사용합니다. 그러나 이 방법은 compute가 결국 nil을 반환할 때 재계산하고 싶지 않은 경우에는 잘 작동하지 않습니다. 대신, 값을 설정했는지 여부를 확인하려면 defined?를 사용할 수 있습니다. 이러한 패턴을 작성하는 것은 지루하며, StrongMemoize를 사용하면 이러한 패턴을 손쉽게 사용할 수 있습니다.

    다음과 같이 작성할 수 있습니다:

    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를 사용하십시오.

    # 나쁨
    def expensive_method(arg)
      # ...
    end
    strong_memoize_attr :expensive_method
    
    # 좋음
    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
    
  • 메모이제이션 지우기

    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에 대한 문서를 읽어보세요.

CircuitBreaker

Gitlab::CircuitBreaker는 회로 차단기 보호 기능으로 코드를 실행해야 하는 모든 클래스를 감싸도록 설계되었습니다. 이는 회로 차단기 기능을 갖춘 ‘run_with_circuit’ 메소드를 제공하여 침투적인 오류 및 시스템 내구성을 향상시키는데 도움이 됩니다. 회로 차단기 패턴에 대한 자세한 내용은 다음을 참조하세요.

CircuitBreaker 사용하기

CircuitBreaker 래퍼를 사용하려면 다음과 같이 합니다.

class MyService
  def call_external_service
    Gitlab::CircuitBreaker.run_with_circuit('ServiceName') do
      # 외부 서비스와 상호작용하는 코드를 여기에 작성합니다.

      raise Gitlab::CircuitBreaker::InternalServerError # 문제가 발생했을 경우에
    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