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
    

    체크는 다음 두 경우에만 발생합니다:

    • 덮어쓰는 메서드가 클래스에 정의되어 있는 경우 또는
    • 덮어쓰는 메서드가 모듈에 정의되어 있고, 클래스 또는 모듈에 추가되어 있는 경우

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

ActiveSupport::Concern, prependclass_methods와의 상호작용

클래스 메서드를 포함하는 ActiveSupport::Concern을 사용할 때, ActiveSupport::Concern이 일반적인 Ruby 모듈처럼 작동하지 않기 때문에 예상한 결과를 얻지 못할 수 있습니다.

우리는 이미 ActiveSupport::Concern에 대한 패치로 prepend를 활성화할 수 있는 ‘Prependable’을 가지고 있기 때문에, 이것이 overrideclass_methods와 어떻게 상호작용할지에 대한 결과가 있을 것입니다. 현재 검증 중일 때만 이 워크어라운드가 실행되며, 응용 프로그램 자체를 실행하는 동안에는 실행되지 않습니다.

다음은 이 워크어라운드의 영향을 보여주는 예시 코드 블록입니다: 다음 코드:

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

    # 좋지 않은 예
    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
    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