InfoGrab DocsInfoGrab Docs

Redis 개발 가이드라인

요약

GitLab은 다음과 같은 별개의 용도로 Redis를 사용합니다. 대부분의 환경(GDK 포함)에서는 이 용도들이 모두 같은 Redis 인스턴스를 가리킵니다. GitLab.com에서는 별도의 Redis 인스턴스를 사용합니다.

Redis 인스턴스#

GitLab은 다음과 같은 별개의 용도로 Redis를 사용합니다.

  • 캐싱(대부분 Rails.cache를 통해 사용합니다).
  • Sidekiq을 사용하는 job 처리 큐.
  • 공유 애플리케이션 상태 관리.
  • CI 트레이스 청크 저장.
  • Sidekiq 동시 실행 제한을 위한 지연 job 저장.
  • ActionCable 용 Pub/Sub 큐 백엔드.
  • 속도 제한 상태 저장.
  • 세션.

대부분의 환경(GDK 포함)에서는 이 용도들이 모두 같은 Redis 인스턴스를 가리킵니다.

GitLab.com에서는 별도의 Redis 인스턴스를 사용합니다. 구성에 대한 자세한 내용은 Redis SRE 가이드를 참고합니다.

모든 애플리케이션 프로세스는 동일한 Redis 서버를 사용하도록 구성되므로, PostgreSQL이 적합하지 않은 경우 프로세스 간 통신에 Redis를 사용할 수 있습니다. 예를 들어 일시적인 상태나 읽는 횟수보다 쓰는 횟수가 훨씬 많은 데이터가 여기에 해당합니다.

Geo를 사용하면 각 Geo 사이트는 독립된 Redis 데이터베이스를 갖습니다.

새 Redis 인스턴스 추가에 대한 개발 문서가 있습니다.

키 명명#

Redis는 계층 구조가 없는 평면 네임스페이스이므로, 충돌을 피하려면 키 이름에 주의해야 합니다. 보통은 콜론으로 구분한 요소를 사용해 애플리케이션 수준에서 구조를 흉내 냅니다. 예를 들면 projects:1:somekey와 같습니다.

Redis 사용처를 목적별로 구분하고 GitLab.com 같은 고가용성 구성에서는 이들이 서로 다른 Redis 서버에 매핑될 수 있지만, 기본 Omnibus와 GDK 설정은 단일 Redis 서버를 공유합니다. 따라서 키는 모든 분류를 통틀어 항상 전역적으로 고유해야 합니다.

Redis 키 이름에는 대개 변하지 않는 식별자를 쓰는 편이 낫습니다. 예를 들어 전체 경로 대신 프로젝트 ID를 사용합니다. 전체 경로를 사용하면 프로젝트 이름이 바뀌었을 때 그 키를 더 이상 조회하지 않습니다. 이름 변경으로 키의 내용이 무효가 된다면, 키가 바뀌는 데 의존하기보다 해당 항목을 만료시키는 훅을 추가하는 편이 낫습니다.

멀티 키 명령#

GitLab은 에픽 878에서 도입된 캐시 관련 워크로드 유형에 대해 Redis Cluster를 지원합니다.

이 때문에 이름 짓기에 제약이 하나 더 생깁니다. 여러 키가 같은 Redis 서버에 있어야 하는 작업(예: Redis에 저장된 두 집합의 차이 계산)을 수행할 때는, 변할 수 있는 부분을 중괄호로 감싸 같은 서버에 놓이도록 키 이름을 지정해야 합니다. 예를 들면 다음과 같습니다.

project:{1}:set_a
project:{1}:set_b
project:{2}:set_c

set_a와 set_b는 같은 Redis 서버에 있는 것이 보장되지만, set_c는 그렇지 않습니다.

현재 개발 환경과 테스트 환경에서는 RedisClusterValidator로 이를 검증하며, 이 검증기는 cache와 shared_state Redis 인스턴스에 대해 활성화되어 있습니다.

앞으로 더 많은 Redis 유형에서 Redis Cluster를 도입할 수 있도록, 필요한 곳에는 해시 태그를 사용할 것을 적극 권장합니다. 예를 들어 Namespace 모델은 설정 캐시 키에 해시 태그를 사용합니다.

멀티 키 명령을 실행할 때는 명령을 나눠 각 노드로 보내고 응답을 모으는 .pipelined 메서드를 사용할 수 있습니다. 다만 Redis Cluster는 슬롯을 넘나드는 트랜잭션을 지원하지 않으므로 트랜잭션에는 사용할 수 없습니다.

Rails.cache의 경우 read_multi_get에 있는 MGET 명령을 패치해 .pipelined 메서드를 사용하도록 처리합니다. 파이프라인의 최소 크기는 명령 1000개로 설정되어 있으며, GITLAB_REDIS_CLUSTER_PIPELINE_BATCH_LIMIT 환경 변수로 조정할 수 있습니다.

구조화된 로깅에서의 Redis#

GitLab 팀 구성원 대상: 기본 영상과 심화 영상에서 GitLab.com의 Redis 구조화 로깅 필드를 다루는 방법을 확인할 수 있습니다.

웹 요청과 Sidekiq job에 대한 구조화된 로깅에는 Redis 인스턴스별 소요 시간, 호출 횟수, 쓴 바이트 수, 읽은 바이트 수 필드와 전체 Redis 인스턴스 합계가 포함됩니다. 특정 요청에 대한 예시는 다음과 같습니다.

필드 값
json.queue_duration_s 0.01
json.redis_cache_calls 1
json.redis_cache_duration_s 0
json.redis_cache_read_bytes 109
json.redis_cache_write_bytes 49
json.redis_calls 2
json.redis_duration_s 0.001
json.redis_read_bytes 111
json.redis_shared_state_calls 1
json.redis_shared_state_duration_s 0
json.redis_shared_state_read_bytes 2
json.redis_shared_state_write_bytes 206
json.redis_write_bytes 255

이 필드들은 모두 인덱싱되므로 프로덕션에서 Redis 사용량을 조사하기가 쉽습니다. 예를 들어 캐시에서 가장 많은 데이터를 읽은 요청을 찾으려면 redis_cache_read_bytes를 내림차순으로 정렬하면 됩니다.

슬로우 로그#

Note

GitLab.com에서 슬로우 로그를 보는 방법을 설명한 영상(GitLab 내부)이 있습니다

GitLab.com에서는 Redis 슬로우 로그 항목을 pubsub-redis-inf-gprd* 인덱스에서 redis.slowlog 태그로 확인할 수 있습니다. 여기에는 실행에 오래 걸려 성능 문제가 될 수 있는 명령이 표시됩니다.

fluent-plugin-redis-slowlog 프로젝트는 Redis의 slowlog 항목을 가져와 Fluentd(최종적으로는 Elasticsearch)로 전달하는 역할을 합니다.

전체 키스페이스 분석#

Redis Keyspace Analyzer 프로젝트에는 Redis 인스턴스의 전체 키 목록과 메모리 사용량을 덤프하고, 결과에서 민감할 수 있는 데이터를 제거하면서 그 목록을 분석하는 도구가 들어 있습니다. 가장 자주 쓰이는 키 패턴이나 메모리를 가장 많이 사용하는 키 패턴을 찾는 데 사용할 수 있습니다.

현재 이 도구는 GitLab.com Redis 인스턴스에 대해 자동으로 실행되지 않으며, 필요할 때 수동으로 실행합니다.

N+1 호출 문제#

히스토리

RedisCommands::Recorder는 테스트에서 Redis N+1 호출 문제를 탐지하는 도구입니다.

Redis는 캐싱 용도로 자주 쓰입니다. 보통 캐시 호출은 가볍고 Redis 인스턴스에 영향을 줄 만큼의 부하를 만들지 않습니다. 그러나 모르는 사이에 비용이 큰 캐시 재계산을 유발할 수는 있습니다. 이 도구로 Redis 호출을 분석하고 호출 횟수의 예상 한계를 정의합니다.

테스트 만들기#

이 도구는 ActiveSupport::Notifications 계측기로 구현되어 있습니다.

대상 코드가 Redis 호출을 한 번만 하는지 검증하는 테스트를 작성할 수 있습니다.

it 'avoids N+1 Redis calls' do
  control = RedisCommands::Recorder.new { visit_page }

  expect(control.count).to eq(1)
end

또는 특정 Redis 호출의 횟수를 검증하는 테스트를 작성할 수 있습니다.

it 'avoids N+1 sadd Redis calls' do
  control = RedisCommands::Recorder.new { visit_page }

  expect(control.by_command(:sadd).count).to eq(1)
end

패턴을 지정해 특정 Redis 호출만 수집할 수도 있습니다.

it 'avoids N+1 Redis calls to forks_count key' do
  control = RedisCommands::Recorder.new(pattern: 'forks_count') { visit_page }

  expect(control.count).to eq(1)
end

exceed_redis_calls_limit와 exceed_redis_command_calls_limit 전용 매처를 사용해 Redis 호출 횟수의 상한을 정의할 수도 있습니다.

it 'avoids N+1 Redis calls' do
  control = RedisCommands::Recorder.new { visit_page }

  expect(control).not_to exceed_redis_calls_limit(1)
end
it 'avoids N+1 sadd Redis calls' do
  control = RedisCommands::Recorder.new { visit_page }

  expect(control).not_to exceed_redis_command_calls_limit(:sadd, 1)
end

이런 테스트는 Redis 호출과 관련된 N+1 문제를 찾아내고, 그에 대한 수정이 의도대로 동작하는지 확인하는 데 도움이 됩니다.

참고#

캐싱#

Rails.cache가 사용하는 Redis 인스턴스에는 키 축출 정책을 구성할 수 있습니다. 보통 LRU 이며, 메모리 한계에 도달하면 "가장 오래 사용되지 않은" 캐시 항목이 축출(삭제)됩니다.

Rails.cache가 사용하는 GitLab.com의 Redis 인스턴스 redis-cluster-cache에 대해서는 키 축출 설정을 참고합니다. 이 Redis 인스턴스는 최대 메모리 한계에 도달해서는 안 됩니다. maxmemory에서 키 축출이 일어나면 축출이 진행되는 동안 지연이 발생하기 때문입니다. 자세한 내용은 이 이슈를 참고합니다. redis-cluster-cache의 현재 메모리 사용량도 확인할 수 있습니다.

이 캐시의 데이터는 설정된 만료 시간보다 일찍 사라질 수 있으므로, 진짜 캐시 성격의 일시적인 데이터에만 Rails.cache를 사용합니다.

캐시가 아니라 Redis에 안정적으로 보존해야 하는 데이터에는 Gitlab::Redis::SharedState를 사용할 수 있습니다.

유틸리티 클래스#

특정 용도를 돕는 추가 클래스가 몇 가지 있습니다. 대부분 Redis 사용을 세밀하게 제어하기 위한 것이므로 Rails.cache 래퍼와 함께 쓰지 않습니다. Rails.cache를 쓰거나, 이 클래스들과 Redis 명령을 직접 쓰는 방식 중 하나를 택합니다.

앞으로 Rails에 적용될 최적화의 이점을 누리기 위해 Rails.cache 사용을 선호합니다. Ruby 객체는 Redis에 쓸 때 마셜링되므로, 아주 큰 객체나 신뢰할 수 없는 사용자 입력을 저장하지 않도록 주의해야 합니다.

보통은 다음 중 하나 이상에 해당할 때만 이 클래스들을 사용합니다.

  1. 캐시가 아닌 Redis 인스턴스의 데이터를 조작하려는 경우입니다.
  2. Rails.cache가 수행하려는 작업을 지원하지 않는 경우입니다.

Gitlab::Redis::{Cache,SharedState,Queues}#

이 클래스들은 Gitlab::Redis::Wrapper 를 사용해 Redis 인스턴스를 감싸므로 인스턴스를 직접 다루기가 편합니다. 일반적인 사용법은 클래스에서 .with를 호출하는 것이며, 이 메서드는 Redis 연결을 넘겨주는 블록을 받습니다. 예를 들면 다음과 같습니다.

# Get the value of `key` from the shared state (persistent) Redis
Gitlab::Redis::SharedState.with { |redis| redis.get(key) }

# Check if `value` is a member of the set `key`
Gitlab::Redis::Cache.with { |redis| redis.sismember(key, value) }

Gitlab::Redis::Cache는 Rails.cache와 같은 Redis 인스턴스를 공유하므로, 키 축출 정책이 구성되어 있으면 그 정책을 따릅니다. 진짜 캐시 성격이면서 없어져도 다시 만들 수 있는 데이터에 이 클래스를 사용합니다.

이 클래스를 사용할 때는 키에 항상 TTL을 설정합니다. 기본 TTL 이 8시간인 Rails.cache와 달리 이 클래스는 기본 TTL을 설정하지 않기 때문입니다. 일반적인 캐싱에는 8시간 TTL을 검토합니다. 하루 근무 시간과 맞아떨어지므로, 같은 콘텐츠에 대해 사용자가 하루에 한 번만 캐시 미스를 겪게 됩니다.

캐시에 큰 워크로드를 추가할 예정이거나 프로덕션 영향이 확실하지 않다면 #g_durability에 문의합니다.

Gitlab::Redis::SharedState에는 키 축출 정책을 구성하지 않습니다. 다시 만들 수 없고 설정된 만료 시간까지 보존되어야 하는 데이터에 이 클래스를 사용합니다. 이 클래스도 키에 기본 TTL을 설정하지 않으므로, 사용할 때는 거의 항상 키에 TTL을 설정해야 합니다.

Gitlab::Redis::Boolean#

Redis에서는 모든 값이 문자열입니다. Gitlab::Redis::Boolean 은 불리언 값이 일관되게 인코딩되고 디코딩되도록 보장합니다.

Gitlab::Redis::HLL#

Redis의 PFCOUNT, PFADD, PFMERGE 명령은 적은 메모리로 고유 요소 개수를 추정할 수 있는 자료 구조인 HyperLogLog를 대상으로 동작합니다. 자세한 내용은 Redis의 HyperLogLog를 참고합니다.

Gitlab::Redis::HLL 은 HyperLogLog에 값을 추가하고 개수를 세는 간편한 인터페이스를 제공합니다.

Gitlab::SetCache#

어떤 항목이 항목 집합에 들어 있는지 효율적으로 확인해야 할 때는 Redis 집합을 사용할 수 있습니다. Gitlab::SetCache 는 SISMEMBER 명령을 사용하는 #include? 메서드와, 집합의 모든 항목을 가져오는 #read 메서드를 제공합니다.

이 클래스는 RepositorySetCache에서 브랜치 이름 같은 리포지터리 데이터를 집합으로 캐시하는 데 편리하게 사용됩니다.

백그라운드 마이그레이션#

Redis 기반 마이그레이션은 SCAN 명령으로 Redis 인스턴스 전체를 훑어 특정 키 패턴을 찾습니다. Redis 인스턴스가 크면 마이그레이션이 일반 마이그레이션이나 배포 후 마이그레이션의 시간 제한을 넘길 수 있습니다. RedisMigrationWorker 는 오래 걸리는 Redis 마이그레이션을 백그라운드 마이그레이션으로 수행합니다.

클래스를 만들어 백그라운드 마이그레이션을 수행하는 방법은 다음과 같습니다.

module Gitlab
  module BackgroundMigration
    module Redis
      class BackfillCertainKey
        def perform(keys)
        # implement logic to clean up or backfill keys
        end

        def scan_match_pattern
        # define the match pattern for the `SCAN` command
        end

        def redis
        # define the exact Redis instance
        end
      end
    end
  end
end

배포 후 마이그레이션으로 워커를 트리거하는 방법은 다음과 같습니다.

class ExampleBackfill < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  MIGRATION='BackfillCertainKey'

  def up
    queue_redis_migration_job(MIGRATION)
  end
end

Redis 개발 가이드라인

GitLab v19.4
원문 보기

요약

GitLab은 다음과 같은 별개의 용도로 Redis를 사용합니다. 대부분의 환경(GDK 포함)에서는 이 용도들이 모두 같은 Redis 인스턴스를 가리킵니다. GitLab.com에서는 별도의 Redis 인스턴스를 사용합니다.

Redis 인스턴스#

GitLab은 다음과 같은 별개의 용도로 Redis를 사용합니다.

  • 캐싱(대부분 Rails.cache를 통해 사용합니다).
  • Sidekiq을 사용하는 job 처리 큐.
  • 공유 애플리케이션 상태 관리.
  • CI 트레이스 청크 저장.
  • Sidekiq 동시 실행 제한을 위한 지연 job 저장.
  • ActionCable 용 Pub/Sub 큐 백엔드.
  • 속도 제한 상태 저장.
  • 세션.

대부분의 환경(GDK 포함)에서는 이 용도들이 모두 같은 Redis 인스턴스를 가리킵니다.

GitLab.com에서는 별도의 Redis 인스턴스를 사용합니다. 구성에 대한 자세한 내용은 Redis SRE 가이드를 참고합니다.

모든 애플리케이션 프로세스는 동일한 Redis 서버를 사용하도록 구성되므로, PostgreSQL이 적합하지 않은 경우 프로세스 간 통신에 Redis를 사용할 수 있습니다. 예를 들어 일시적인 상태나 읽는 횟수보다 쓰는 횟수가 훨씬 많은 데이터가 여기에 해당합니다.

Geo를 사용하면 각 Geo 사이트는 독립된 Redis 데이터베이스를 갖습니다.

새 Redis 인스턴스 추가에 대한 개발 문서가 있습니다.

키 명명#

Redis는 계층 구조가 없는 평면 네임스페이스이므로, 충돌을 피하려면 키 이름에 주의해야 합니다. 보통은 콜론으로 구분한 요소를 사용해 애플리케이션 수준에서 구조를 흉내 냅니다. 예를 들면 projects:1:somekey와 같습니다.

Redis 사용처를 목적별로 구분하고 GitLab.com 같은 고가용성 구성에서는 이들이 서로 다른 Redis 서버에 매핑될 수 있지만, 기본 Omnibus와 GDK 설정은 단일 Redis 서버를 공유합니다. 따라서 키는 모든 분류를 통틀어 항상 전역적으로 고유해야 합니다.

Redis 키 이름에는 대개 변하지 않는 식별자를 쓰는 편이 낫습니다. 예를 들어 전체 경로 대신 프로젝트 ID를 사용합니다. 전체 경로를 사용하면 프로젝트 이름이 바뀌었을 때 그 키를 더 이상 조회하지 않습니다. 이름 변경으로 키의 내용이 무효가 된다면, 키가 바뀌는 데 의존하기보다 해당 항목을 만료시키는 훅을 추가하는 편이 낫습니다.

멀티 키 명령#

GitLab은 에픽 878에서 도입된 캐시 관련 워크로드 유형에 대해 Redis Cluster를 지원합니다.

이 때문에 이름 짓기에 제약이 하나 더 생깁니다. 여러 키가 같은 Redis 서버에 있어야 하는 작업(예: Redis에 저장된 두 집합의 차이 계산)을 수행할 때는, 변할 수 있는 부분을 중괄호로 감싸 같은 서버에 놓이도록 키 이름을 지정해야 합니다. 예를 들면 다음과 같습니다.

project:{1}:set_a
project:{1}:set_b
project:{2}:set_c

set_a와 set_b는 같은 Redis 서버에 있는 것이 보장되지만, set_c는 그렇지 않습니다.

현재 개발 환경과 테스트 환경에서는 RedisClusterValidator로 이를 검증하며, 이 검증기는 cache와 shared_state Redis 인스턴스에 대해 활성화되어 있습니다.

앞으로 더 많은 Redis 유형에서 Redis Cluster를 도입할 수 있도록, 필요한 곳에는 해시 태그를 사용할 것을 적극 권장합니다. 예를 들어 Namespace 모델은 설정 캐시 키에 해시 태그를 사용합니다.

멀티 키 명령을 실행할 때는 명령을 나눠 각 노드로 보내고 응답을 모으는 .pipelined 메서드를 사용할 수 있습니다. 다만 Redis Cluster는 슬롯을 넘나드는 트랜잭션을 지원하지 않으므로 트랜잭션에는 사용할 수 없습니다.

Rails.cache의 경우 read_multi_get에 있는 MGET 명령을 패치해 .pipelined 메서드를 사용하도록 처리합니다. 파이프라인의 최소 크기는 명령 1000개로 설정되어 있으며, GITLAB_REDIS_CLUSTER_PIPELINE_BATCH_LIMIT 환경 변수로 조정할 수 있습니다.

구조화된 로깅에서의 Redis#

GitLab 팀 구성원 대상: 기본 영상과 심화 영상에서 GitLab.com의 Redis 구조화 로깅 필드를 다루는 방법을 확인할 수 있습니다.

웹 요청과 Sidekiq job에 대한 구조화된 로깅에는 Redis 인스턴스별 소요 시간, 호출 횟수, 쓴 바이트 수, 읽은 바이트 수 필드와 전체 Redis 인스턴스 합계가 포함됩니다. 특정 요청에 대한 예시는 다음과 같습니다.

필드 값
json.queue_duration_s 0.01
json.redis_cache_calls 1
json.redis_cache_duration_s 0
json.redis_cache_read_bytes 109
json.redis_cache_write_bytes 49
json.redis_calls 2
json.redis_duration_s 0.001
json.redis_read_bytes 111
json.redis_shared_state_calls 1
json.redis_shared_state_duration_s 0
json.redis_shared_state_read_bytes 2
json.redis_shared_state_write_bytes 206
json.redis_write_bytes 255

이 필드들은 모두 인덱싱되므로 프로덕션에서 Redis 사용량을 조사하기가 쉽습니다. 예를 들어 캐시에서 가장 많은 데이터를 읽은 요청을 찾으려면 redis_cache_read_bytes를 내림차순으로 정렬하면 됩니다.

슬로우 로그#

Note

GitLab.com에서 슬로우 로그를 보는 방법을 설명한 영상(GitLab 내부)이 있습니다

GitLab.com에서는 Redis 슬로우 로그 항목을 pubsub-redis-inf-gprd* 인덱스에서 redis.slowlog 태그로 확인할 수 있습니다. 여기에는 실행에 오래 걸려 성능 문제가 될 수 있는 명령이 표시됩니다.

fluent-plugin-redis-slowlog 프로젝트는 Redis의 slowlog 항목을 가져와 Fluentd(최종적으로는 Elasticsearch)로 전달하는 역할을 합니다.

전체 키스페이스 분석#

Redis Keyspace Analyzer 프로젝트에는 Redis 인스턴스의 전체 키 목록과 메모리 사용량을 덤프하고, 결과에서 민감할 수 있는 데이터를 제거하면서 그 목록을 분석하는 도구가 들어 있습니다. 가장 자주 쓰이는 키 패턴이나 메모리를 가장 많이 사용하는 키 패턴을 찾는 데 사용할 수 있습니다.

현재 이 도구는 GitLab.com Redis 인스턴스에 대해 자동으로 실행되지 않으며, 필요할 때 수동으로 실행합니다.

N+1 호출 문제#

히스토리

RedisCommands::Recorder는 테스트에서 Redis N+1 호출 문제를 탐지하는 도구입니다.

Redis는 캐싱 용도로 자주 쓰입니다. 보통 캐시 호출은 가볍고 Redis 인스턴스에 영향을 줄 만큼의 부하를 만들지 않습니다. 그러나 모르는 사이에 비용이 큰 캐시 재계산을 유발할 수는 있습니다. 이 도구로 Redis 호출을 분석하고 호출 횟수의 예상 한계를 정의합니다.

테스트 만들기#

이 도구는 ActiveSupport::Notifications 계측기로 구현되어 있습니다.

대상 코드가 Redis 호출을 한 번만 하는지 검증하는 테스트를 작성할 수 있습니다.

it 'avoids N+1 Redis calls' do
  control = RedisCommands::Recorder.new { visit_page }

  expect(control.count).to eq(1)
end

또는 특정 Redis 호출의 횟수를 검증하는 테스트를 작성할 수 있습니다.

it 'avoids N+1 sadd Redis calls' do
  control = RedisCommands::Recorder.new { visit_page }

  expect(control.by_command(:sadd).count).to eq(1)
end

패턴을 지정해 특정 Redis 호출만 수집할 수도 있습니다.

it 'avoids N+1 Redis calls to forks_count key' do
  control = RedisCommands::Recorder.new(pattern: 'forks_count') { visit_page }

  expect(control.count).to eq(1)
end

exceed_redis_calls_limit와 exceed_redis_command_calls_limit 전용 매처를 사용해 Redis 호출 횟수의 상한을 정의할 수도 있습니다.

it 'avoids N+1 Redis calls' do
  control = RedisCommands::Recorder.new { visit_page }

  expect(control).not_to exceed_redis_calls_limit(1)
end
it 'avoids N+1 sadd Redis calls' do
  control = RedisCommands::Recorder.new { visit_page }

  expect(control).not_to exceed_redis_command_calls_limit(:sadd, 1)
end

이런 테스트는 Redis 호출과 관련된 N+1 문제를 찾아내고, 그에 대한 수정이 의도대로 동작하는지 확인하는 데 도움이 됩니다.

참고#

캐싱#

Rails.cache가 사용하는 Redis 인스턴스에는 키 축출 정책을 구성할 수 있습니다. 보통 LRU 이며, 메모리 한계에 도달하면 "가장 오래 사용되지 않은" 캐시 항목이 축출(삭제)됩니다.

Rails.cache가 사용하는 GitLab.com의 Redis 인스턴스 redis-cluster-cache에 대해서는 키 축출 설정을 참고합니다. 이 Redis 인스턴스는 최대 메모리 한계에 도달해서는 안 됩니다. maxmemory에서 키 축출이 일어나면 축출이 진행되는 동안 지연이 발생하기 때문입니다. 자세한 내용은 이 이슈를 참고합니다. redis-cluster-cache의 현재 메모리 사용량도 확인할 수 있습니다.

이 캐시의 데이터는 설정된 만료 시간보다 일찍 사라질 수 있으므로, 진짜 캐시 성격의 일시적인 데이터에만 Rails.cache를 사용합니다.

캐시가 아니라 Redis에 안정적으로 보존해야 하는 데이터에는 Gitlab::Redis::SharedState를 사용할 수 있습니다.

유틸리티 클래스#

특정 용도를 돕는 추가 클래스가 몇 가지 있습니다. 대부분 Redis 사용을 세밀하게 제어하기 위한 것이므로 Rails.cache 래퍼와 함께 쓰지 않습니다. Rails.cache를 쓰거나, 이 클래스들과 Redis 명령을 직접 쓰는 방식 중 하나를 택합니다.

앞으로 Rails에 적용될 최적화의 이점을 누리기 위해 Rails.cache 사용을 선호합니다. Ruby 객체는 Redis에 쓸 때 마셜링되므로, 아주 큰 객체나 신뢰할 수 없는 사용자 입력을 저장하지 않도록 주의해야 합니다.

보통은 다음 중 하나 이상에 해당할 때만 이 클래스들을 사용합니다.

  1. 캐시가 아닌 Redis 인스턴스의 데이터를 조작하려는 경우입니다.
  2. Rails.cache가 수행하려는 작업을 지원하지 않는 경우입니다.

Gitlab::Redis::{Cache,SharedState,Queues}#

이 클래스들은 Gitlab::Redis::Wrapper 를 사용해 Redis 인스턴스를 감싸므로 인스턴스를 직접 다루기가 편합니다. 일반적인 사용법은 클래스에서 .with를 호출하는 것이며, 이 메서드는 Redis 연결을 넘겨주는 블록을 받습니다. 예를 들면 다음과 같습니다.

# Get the value of `key` from the shared state (persistent) Redis
Gitlab::Redis::SharedState.with { |redis| redis.get(key) }

# Check if `value` is a member of the set `key`
Gitlab::Redis::Cache.with { |redis| redis.sismember(key, value) }

Gitlab::Redis::Cache는 Rails.cache와 같은 Redis 인스턴스를 공유하므로, 키 축출 정책이 구성되어 있으면 그 정책을 따릅니다. 진짜 캐시 성격이면서 없어져도 다시 만들 수 있는 데이터에 이 클래스를 사용합니다.

이 클래스를 사용할 때는 키에 항상 TTL을 설정합니다. 기본 TTL 이 8시간인 Rails.cache와 달리 이 클래스는 기본 TTL을 설정하지 않기 때문입니다. 일반적인 캐싱에는 8시간 TTL을 검토합니다. 하루 근무 시간과 맞아떨어지므로, 같은 콘텐츠에 대해 사용자가 하루에 한 번만 캐시 미스를 겪게 됩니다.

캐시에 큰 워크로드를 추가할 예정이거나 프로덕션 영향이 확실하지 않다면 #g_durability에 문의합니다.

Gitlab::Redis::SharedState에는 키 축출 정책을 구성하지 않습니다. 다시 만들 수 없고 설정된 만료 시간까지 보존되어야 하는 데이터에 이 클래스를 사용합니다. 이 클래스도 키에 기본 TTL을 설정하지 않으므로, 사용할 때는 거의 항상 키에 TTL을 설정해야 합니다.

Gitlab::Redis::Boolean#

Redis에서는 모든 값이 문자열입니다. Gitlab::Redis::Boolean 은 불리언 값이 일관되게 인코딩되고 디코딩되도록 보장합니다.

Gitlab::Redis::HLL#

Redis의 PFCOUNT, PFADD, PFMERGE 명령은 적은 메모리로 고유 요소 개수를 추정할 수 있는 자료 구조인 HyperLogLog를 대상으로 동작합니다. 자세한 내용은 Redis의 HyperLogLog를 참고합니다.

Gitlab::Redis::HLL 은 HyperLogLog에 값을 추가하고 개수를 세는 간편한 인터페이스를 제공합니다.

Gitlab::SetCache#

어떤 항목이 항목 집합에 들어 있는지 효율적으로 확인해야 할 때는 Redis 집합을 사용할 수 있습니다. Gitlab::SetCache 는 SISMEMBER 명령을 사용하는 #include? 메서드와, 집합의 모든 항목을 가져오는 #read 메서드를 제공합니다.

이 클래스는 RepositorySetCache에서 브랜치 이름 같은 리포지터리 데이터를 집합으로 캐시하는 데 편리하게 사용됩니다.

백그라운드 마이그레이션#

Redis 기반 마이그레이션은 SCAN 명령으로 Redis 인스턴스 전체를 훑어 특정 키 패턴을 찾습니다. Redis 인스턴스가 크면 마이그레이션이 일반 마이그레이션이나 배포 후 마이그레이션의 시간 제한을 넘길 수 있습니다. RedisMigrationWorker 는 오래 걸리는 Redis 마이그레이션을 백그라운드 마이그레이션으로 수행합니다.

클래스를 만들어 백그라운드 마이그레이션을 수행하는 방법은 다음과 같습니다.

module Gitlab
  module BackgroundMigration
    module Redis
      class BackfillCertainKey
        def perform(keys)
        # implement logic to clean up or backfill keys
        end

        def scan_match_pattern
        # define the match pattern for the `SCAN` command
        end

        def redis
        # define the exact Redis instance
        end
      end
    end
  end
end

배포 후 마이그레이션으로 워커를 트리거하는 방법은 다음과 같습니다.

class ExampleBackfill < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  MIGRATION='BackfillCertainKey'

  def up
    queue_redis_migration_job(MIGRATION)
  end
end