Redis 개발 지침
Redis 인스턴스
GitLab은 다음과 같은 목적으로 Redis를 사용합니다:
- 캐싱 (
Rails.cache
를 통해 주로 사용됨). - Sidekiq을 사용한 작업 처리 큐.
- 공유 애플리케이션 상태 관리.
- CI 추적 청크 저장.
- ActionCable을 위한 Pub/Sub 큐 백엔드.
- 속도 제한 상태 저장.
- 세션.
대부분의 환경(포함하여 GDK)에서 이 모든 것들은 동일한 Redis 인스턴스를 가리킵니다.
GitLab.com에서는 별도의 Redis 인스턴스를 사용합니다. 자세한 내용은 Redis SRE 가이드를 참조하세요.
각 애플리케이션 프로세스는 동일한 Redis 서버를 사용하도록 구성되어 있으므로, PostgreSQL이 적합하지 않은 경우에 프로세스 간 통신에 사용될 수 있습니다. 예를 들면, 일시적 상태 또는 읽히는 것보다 훨씬 더 자주 쓰이는 데이터 등이 있습니다.
Geo가 활성화된 경우, 각 Geo 노드는 독립적인 Redis 데이터베이스를 얻습니다.
새로운 Redis 인스턴스를 추가하는 데 대한 개발 문서가 있습니다.
키 네이밍
Redis는 계층 구조를 가진 평면 네임스페이스이므로 충돌을 피하기 위해 키 네임에 주의해야 합니다. 일반적으로 우리는 콜론으로 구분된 요소를 사용하여 응용 프로그램 수준에서 구조를 제공하기 위해 사용합니다. 예를 들면 projects:1:somekey
와 같은 예가 있을 수 있습니다.
우리는 Redis 사용을 목적에 따라 다른 카테고리로 분리하지만, GitLab.com과 같은 고가용성 구성에서 이러한 목적을 별도의 Redis 서버에 매핑할 수 있습니다. 이는 키가 모든 카테고리 전체에서 항상 전역적으로 고유해야 한다는 것을 의미합니다.
Redis 키 네임에는 일반적으로 변경할 수 없는 식별자인 프로젝트 ID를 사용하는 것이 일반적으로 좋습니다. 예를 들어 전체 경로 대신 프로젝트 ID를 사용하는 것이 좋습니다. 경로가 사용되는 경우 프로젝트가 이름을 바꾸면 키를 더 이상 확인하지 않게 됩니다. 키 내용이 이름 변경으로 인해 무효화된 경우 키가 변경되기를 의지하는 대신, 항목의 만료를 포함하는 후크를 포함하는 것이 더 좋습니다.
멀티 키 명령
GitLab은 캐시 관련 워크로드 타입에 대해 Redis Cluster를 지원하며, 이는 에픽 878에서 소개되었습니다.
이는 이름에 추가적인 제약을 부과합니다: GitLab이 동일한 Redis 서버에 여러 키를 유지해야 하는 작업을 수행할 때, 예를 들어 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
의 경우, .pipelined
메서드를 사용하도록 패치하여 read_multi_get
에서 발견된 MGET
명령을 처리합니다.
파이프라인의 최소 크기는 1000명령이며, GITLAB_REDIS_CLUSTER_PIPELINE_BATCH_LIMIT
환경 변수를 사용하여 조정할 수 있습니다.
구조화된 로깅에서의 Redis
GitLab 팀원을 위한 참고: GitLab.com에서 Redis 구조화된 로깅 필드를 다루는 방법을 보여주는 기본 및 고급 비디오가 있습니다.
웹 요청 및 Sidekiq 작업에 대한 구조화된 로깅은 각 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
를 내림차순으로 정렬하기만 하면 됩니다.
느린 로그
GitLab.com에서는 Redis 느린 로그 항목이 pubsub-redis-inf-gprd*
색인에 redis.slowlog
태그에 사용 가능합니다.
이는 오랜 시간이 걸리는 명령어를 보여주고 성능에 영향을 줄 수 있는 문제일 수 있습니다.
fluent-plugin-redis-slowlog
프로젝트는 Redis의 slowlog
항목을 가져와 Fluentd(및 궁극적으로 Elasticsearch)에 전달하는 역할을 합니다.
전체 키스페이스 분석
Redis 키스페이스 분석기 프로젝트에는 Redis 인스턴스의 전체 키 디렉터리과 메모리 사용량을 덤프하고, 그 디렉터리을 분석하여 결과에서 잠재적으로 민감한 데이터를 제거하는 도구가 포함되어 있습니다. 가장 빈번한 키 패턴이나 가장 많은 메모리를 사용하는 키를 찾는 데 사용할 수 있습니다.
현재 GitLab.com Redis 인스턴스에서는 자동으로 실행되지 않으며 필요에 따라 매뉴얼으로 실행됩니다.
N+1 호출 문제
RedisCommands::Recorder
는 Redis N+1 호출 문제를 테스트에서 감지하는 도구입니다.
Redis는 주로 캐싱 목적으로 사용됩니다. 일반적으로 캐시 호출은 가벼우며 Redis 인스턴스에 영향을 줄만한 충분한 부하를 생성할 수 없습니다. 그러나 이를 모르고 비용이 많이 드는 캐시 다시 계산을 트리거할 수 있습니다. 이 도구를 사용하여 Redis 호출을 분석하고, 그에 대한 예상치 못한 한계를 정의하세요.
테스트 생성
이는 ActiveSupport::Notifications
Instrumenter로 구현되어 있습니다.
단일 Redis 호출만 하는 테스트를 생성할 수 있습니다:
it 'N+1 Redis 호출을 피합니다' do
control = RedisCommands::Recorder.new { visit_page }
expect(control.count).to eq(1)
end
또는 특정 Redis 호출의 수를 확인하는 테스트를 생성할 수 있습니다:
it 'N+1 sadd Redis 호출을 피합니다' do
control = RedisCommands::Recorder.new { visit_page }
expect(control.by_command(:sadd).count).to eq(1)
end
특정 Redis 호출만 캡처하도록 패턴을 제공할 수도 있습니다:
it 'forks_count 키에 대한 N+1 Redis 호출을 피합니다' do
control = RedisCommands::Recorder.new(pattern: 'forks_count') { visit_page }
expect(control.count).to eq(1)
end
또한 Redis 호출의 상한선을 정의하기 위해 exceed_redis_calls_limit
및 exceed_redis_command_calls_limit
와 같은 특수 매처를 사용할 수 있습니다:
it 'N+1 Redis 호출을 피합니다' do
control = RedisCommands::Recorder.new { visit_page }
expect(control).not_to exceed_redis_calls_limit(1)
end
it 'N+1 sadd Redis 호출을 피합니다' do
control = RedisCommands::Recorder.new { visit_page }
expect(control).not_to exceed_redis_command_calls_limit(:sadd, 1)
end
이러한 테스트들은 Redis 호출과 관련된 N+1 문제를 식별하는 데 도움을 줄 뿐 아니라 그에 대한 수정이 의도한 대로 작동하는지 확인합니다.
참고
유틸리티 클래스
특정 용도로 도움이 되는 몇 가지 추가 클래스가 있습니다. 이들은 대부분 Redis 사용을 세부적으로 제어하기 위한 것으로, Rails 캐시 래퍼와 함께 사용되지 않습니다: Rails 캐시 또는 이러한 클래스 및 리터럴 Redis 명령어 중 하나만 사용합니다.
우리는 미래의 Rails에 대한 최적화 혜택을 누리기 위해 Rails.cache
를 선호합니다. Ruby 객체는 Redis에 기록될 때 마샬링됩니다, 따라서 거대한 객체나 믿을 수 없는 사용자 입력을 저장하지 않도록 주의해야 합니다.
일반적으로 우리는 다음 중 적어도 하나일 때에만 이러한 클래스를 사용할 것입니다:
- 캐시되지 않는 Redis 인스턴스에서 데이터를 조작하고 싶을 때.
-
Rails.cache
가 우리가 수행하려는 작업을 지원하지 않을 때.
Gitlab::Redis::{Cache,SharedState,Queues}
이러한 클래스들은 Redis 인스턴스를 래핑하기 위해 Gitlab::Redis::Wrapper
를 사용하여 직접 작업할 수 있도록 편리하게 만듭니다. 일반적으로 클래스에 대한 .with
호출 후 Redis 연결을 제공하는 블록을 취합니다. 예를 들면:
# 공유 상태(영속적) Redis에서 `key`의 값을 가져옵니다
Gitlab::Redis::SharedState.with { |redis| redis.get(key) }
# `value`가 `key` 집합의 멤버인지 확인합니다
Gitlab::Redis::Cache.with { |redis| redis.sismember(key, value) }
Gitlab::Redis::Boolean
Redis에서 모든 값은 문자열입니다.
Gitlab::Redis::Boolean
는 부욜린을 일관되게 인코딩하고 디코딩합니다.
Gitlab::Redis::HLL
Redis의 PFCOUNT
,
PFADD
및
PFMERGE
명령은
하이퍼로그로그(HyperLogLogs)를 다루는데, 이는 적은 메모리 사용량으로 고유 요소의 개수를 추정하는 데이터 구조입니다. 더 많은 정보는 Redis에서의 HyperLogLogs에서 확인할 수 있습니다.
Gitlab::Redis::HLL
은 하이퍼로그로그에 값 추가 및 개수 세는 데 편리한 인터페이스를 제공합니다.
Gitlab::SetCache
특정 항목이 항목 그룹에 있는지 효율적으로 확인해야 하는 경우, Redis 집합을 사용할 수 있습니다.
Gitlab::SetCache
은 SISMEMBER
명령을 사용하는 #include?
메서드와 집합 내 모든 항목을 가져오는 #read
를 제공합니다.
이것은 브랜치 이름과 같은 리포지터리 데이터를 캐시하는 데 집합을 사용하기 위해
RepositorySetCache
에서 사용됩니다.
배경 마이그레이션
Redis 기반의 마이그레이션은 특정 키 패턴을 위해 전체 Redis 인스턴스를 스캔하는 SCAN
명령을 사용합니다.
큰 Redis 인스턴스의 경우, 마이그레이션이 일반적이거나 배포 후 마이그레이션의 제한 시간을 초과할 수 있습니다.
RedisMigrationWorker
는 백그라운드 마이그레이션으로 장기간 실행되는 Redis 마이그레이션을 수행합니다.
클래스를 만들어 백그라운드 마이그레이션을 수행하려면:
module Gitlab
module BackgroundMigration
module Redis
class BackfillCertainKey
def perform(keys)
# 키 정리 또는 채우기 로직 구현
end
def scan_match_pattern
# `SCAN` 명령의 매치 패턴 정의
end
def redis
# 정확한 Redis 인스턴스 정의
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