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 서버에 매핑될 수 있습니다. 기본 Omnibus 및 GDK 설정은 단일 Redis 서버를 공유합니다. 이는 모든 범주에서 키가 항상 전역적으로 고유해야 함을 의미합니다.
Redis 키 이름에서는 불변 식별자 - 예를 들어 전 프로젝트 ID -을 사용하는 것이 일반적으로 더 좋습니다. 전체 경로를 사용하는 경우, 프로젝트 이름이 변경되면 키는 더 이상 참조되지 않습니다. 키의 내용이 이름 변경으로 인해 무효화된다면, 키 변경에 의존하는 대신 항목을 만료시키는 후크를 포함하는 것이 더 좋습니다.
다중 키 명령
GitLab은 캐시 관련 작업 부하 유형에 대해 Redis Cluster를 지원하며, 이는 epic 878에서 도입되었습니다.
이것은 명명에 대한 추가 제약을 부과합니다: GitLab이 동일한 Redis 서버에서 여러 키를 유지해야 하는 작업을 수행하는 경우 - 예를 들어 Redis에서 두 집합을 비교하는 경우 - 변경 가능한 부분을 중괄호로 묶어 키가 이를 보장해야 합니다.
예:
plaintext
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 Cluster의 더 많은 Redis 유형에 대한 채택을 촉진하기 위해 필요한 경우 해시 태그를 사용하는 것이 강력히 권장됩니다. 예를 들어, 네임스페이스 모델은 구성 캐시 키에 해시 태그를 사용합니다.
다중 키 명령을 수행하려면 개발자는 .pipelined
메서드를 사용하여 각 노드로 명령을 분할하고 전송하여 응답을 집계할 수 있습니다.
그러나 이는 트랜잭션에는 작동하지 않습니다, 왜냐하면 Redis Cluster는 크로스 슬롯 트랜잭션을 지원하지 않기 때문입니다.
Rails.cache
의 경우, read_multi_get
에서 발견된 MGET
명령을 패치하여 .pipelined
메서드를 사용하여 처리합니다.
파이프라인의 최소 크기는 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에서 느린 로그를 보는 방법에 대한 비디오가 있습니다 (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 '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
exceed_redis_calls_limit
및 exceed_redis_command_calls_limit
라는 특별한 매처를 사용하여 Redis 호출 수에 대한 상한을 정의할 수 있습니다:
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.cache
래퍼와 함께 사용되지는 않을 것입니다: 우리는 Rails.cache
또는 이러한 클래스와 리터럴 Redis 명령 중 하나를 사용합니다.
우리는 Rails.cache
를 사용하는 것을 선호하므로 앞으로 Rails에서 수행될 최적화 혜택을 누릴 수 있습니다. 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
는 HyperLogLogs에서 값을 추가하고 계산하는 편리한 인터페이스를 제공합니다.
Gitlab::SetCache
항목이 항목 그룹에 있는지 효율적으로 확인해야 할 경우, 우리는 Redis 세트를 사용할 수 있습니다.
Gitlab::SetCache
는 SISMEMBER
명령을 사용하고,
세트의 모든 항목을 가져오기 위해 #read
메서드를 제공합니다.
이것은
RepositorySetCache
에 의해 사용되어 브랜치 이름과 같은 저장소 데이터를 캐시하기 위해 세트를 사용하는 편리한 방법을 제공합니다.
백그라운드 마이그레이션
Redis 기반 마이그레이션은 SCAN
명령을 사용하여 특정 키 패턴에 대해 전체 Redis 인스턴스를 스캔하는
작업을 포함합니다. 큰 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