새 Redis 인스턴스 추가
GitLab v19.1GitLab은 여러 Redis 인스턴스를 활용할 수 있습니다. 때로는 새 Redis 인스턴스를 추가해야 할 수 있습니다. Trace Chunk 스토리지용 전용 Redis 인스턴스. Rate Limiting 데이터용 전용 Redis 인스턴스 생성.
GitLab은 여러 Redis 인스턴스를 활용할 수 있습니다. 이러한 인스턴스들은 기능적으로 분리되어 있어서, 예를 들어 CI 트레이스 청크를 하나의 Redis 인스턴스에 저장하고 세션은 다른 인스턴스에 저장할 수 있습니다.
때로는 새 Redis 인스턴스를 추가해야 할 수 있습니다. 일반적으로 이는 캐시나 공유 상태와 같은 기존 인스턴스 중 하나에서 기능적으로 분리되는 형태로 진행됩니다. 이 문서는 기존 데이터를 처리하는 새 Redis 인스턴스를 추가하기 위한 접근 방식을 설명하며, 이전 사례를 바탕으로 합니다:
이 문서는 새 Redis 인스턴스를 준비하고 설정하는 운영 측면을 자세히 다루지는 않지만, 예시 에픽에는 이전 접근 방식에 대한 정보가 포함되어 있습니다.
Step 1: 새 인스턴스 설정 지원#
새 인스턴스로 기능을 전환하기 전에, 코드베이스에서 새 인스턴스를 설정하고 참조할 수 있어야 합니다. 주요 설치 유형을 모두 지원해야 합니다:
-
Self-compiled 설치 (개발 환경 포함) - 예시 머지 리퀘스트
-
Linux 패키지 설치 - 예시 머지 리퀘스트
-
Helm charts - 예시 머지 리퀘스트
폴백 인스턴스#
애플리케이션 코드에서, 새 인스턴스가 설정되지 않은 경우를 위한 폴백 인스턴스를 정의해야 합니다. 예를 들어, GitLab 인스턴스가 이미 별도의 공유 상태 Redis를 설정했고 공유 상태 Redis에서 데이터를 분리하는 경우, 새 인스턴스의 설정은 해당 인스턴스가 없을 때 공유 상태 Redis의 설정을 기본값으로 사용해야 합니다. 그렇지 않으면 새 Redis 인스턴스가 사용 가능해지는 즉시, 이를 설정하지 않은 인스턴스가 중단될 수 있습니다.
모든 Redis 인스턴스의 기본 클래스인 Gitlab::Redis::Wrapper에
.config_fallback 메서드를 정의하여
이 인스턴스가 설정되지 않은 경우에 사용할 인스턴스를 정의할 수 있습니다.
SharedState로 폴백해야 하는 Foo 인스턴스를 추가하는 경우, 다음과 같이 할 수 있습니다:
module Gitlab
module Redis
class Foo < ::Gitlab::Redis::Wrapper
# The data we store on Foo used to be stored on SharedState.
def self.config_fallback
SharedState
end
end
end
end
또한 이 폴백이 올바르게 작동하는지 확인하기 위해
trace_chunks_spec.rb와 같은 스펙을 추가해야 합니다.
Step 2: 새 인스턴스에 쓰기 및 읽기 지원#
새 인스턴스로 마이그레이션할 때, 데이터가 다음 위치에 있는 경우를 처리해야 합니다:
-
'기존' (원본) 인스턴스.
-
방금 지원을 추가한 새 인스턴스.
따라서 어떤 조건에 따라 두 인스턴스 모두에서 읽고 쓰는 것을 지원해야 할 수 있습니다.
사용할 정확한 조건은 마이그레이션할 데이터에 따라 다릅니다. 위의 트레이스 청크 사례에서는 이미 데이터가 저장된 위치를 나타내는 데이터베이스 칼럼이 있었습니다 (Redis 외에도 다른 스토리지 옵션이 있기 때문입니다).
데이터의 수명이 매우 짧고(최대 몇 분) 중요하지 않은 경우에는 이 단계가 적용되지 않을 수 있습니다. 그런 경우에는 소량의 데이터 손실을 감수하고 설정 변경만으로 전환하기로 결정할 수도 있습니다.
데이터가 저장된 위치를 표시하는 더 자연스러운 방법이 없다면, 피처 플래그를 사용하는 것이 편리할 수 있습니다:
-
적용을 위해 애플리케이션 재시작이 필요하지 않습니다.
-
모든 애플리케이션 인스턴스(Sidekiq, API, 웹 등)에 동시에 적용됩니다.
-
점진적 롤아웃을 지원합니다. 이상적으로는 액터(프로젝트, 그룹, 사용자 등) 단위로 지원하여 오류를 모니터링하고 롤백할 수 있습니다.
Step 3: 데이터 마이그레이션#
그런 다음 GitLab.com의 프로덕션 및 스테이징 환경에 새 인스턴스를 설정해야 합니다. 스테이징에서 이 변경 사항을 효과적으로 테스트하여 최소한 기본 사용이 계속 작동하는지 확인하는 것이 이상적입니다.
그 후 프로덕션에 변경 사항을 롤아웃할 수 있습니다. 이상적으로는 피처 플래그에 대한 표준 점진적 롤아웃 문서에 따라 점진적인 방식으로 진행하는 것이 좋습니다.
프로덕션에서 일정 기간 동안 새 인스턴스를 100% 사용하고 문제가 없을 경우 진행할 수 있습니다.
제안된 솔루션: 폴백 전략과 함께 MultiStore를 사용한 데이터 마이그레이션#
UX 관점에서 불편함 없이 사용자를 새 Redis 스토어로 마이그레이션할 방법이 필요합니다. 또한 새 인스턴스에 문제가 발생했을 때 "이전" Redis 인스턴스로 폴백하는 기능도 원합니다.
마이그레이션 요구 사항:
-
다운타임 없음.
-
데이터 저장 TTL이 만료될 때까지 저장된 데이터 손실 없음.
-
피처 플래그 또는 ENV 변수 혹은 이들의 조합을 사용한 부분 롤아웃.
-
전환 모니터링.
-
Prometheus 메트릭 적용.
-
새 인스턴스나 로직이 예상대로 동작하지 않을 경우 다운타임 없는 쉬운 롤백.
이는 제로 다운타임 DB 테이블 이름 변경과 다소 유사합니다. 두 Redis 인스턴스(구 + 신) 모두에 데이터를 써야 합니다. 새 인스턴스에서 읽지만, 새 전용 Redis 인스턴스에서 프리패칭이 실패하면 이전 인스턴스로 폴백해야 합니다. 새 인스턴스의 문제나 예외를 로깅하되, 이전 인스턴스로 폴백해야 합니다.
제안된 마이그레이션 전략은 MultiStore를 구현하고 사용하는 것입니다. 이 접근 방식은 세션 키를 위한 새 전용 Redis 인스턴스 추가에서 사용되었습니다. 또한 MultiStore에는 해당 스펙이 함께 제공됩니다.
MultiStore는 redis-rb ::Redis 인스턴스처럼 보입니다.
Step 1에서 추가한 새 Redis 인스턴스 클래스에서, ::Gitlab::Redis::MultiStoreWrapper를 대신 상속하고
multistore 클래스 메서드를 오버라이드하여 MultiStore를 정의합니다.
module Gitlab
module Redis
class Foo < ::Gitlab::Redis::MultiStoreWrapper
...
def self.multistore
MultiStore.create_using_pool(self.pool, config_fallback.pool, store_name)
end
end
end
end
MultiStore는 새 Redis 연결 풀을 primary 풀로, 이전 (폴백 인스턴스) 연결 풀을 secondary 풀로 제공하여 초기화됩니다.
세 번째 인수는 store_name으로, 여러 Redis 스토어에 대해 MultiStore 구현을 동시에 사용하는 경우 로그, 메트릭, 피처 플래그 이름에 사용됩니다.
기본적으로 MultiStore는 기본 Redis 스토어에서만 읽고 씁니다.
기본 Redis 스토어는 secondary_store(이전 폴백 인스턴스)입니다.
이를 통해 기본 동작을 변경하지 않고 MultiStore를 도입할 수 있습니다.
MultiStore는 실제 마이그레이션을 제어하기 위해 두 가지 피처 플래그를 사용합니다:
-
use_primary_and_secondary_stores_for_[store_name] -
use_primary_store_as_default_for_[store_name]
예를 들어, 새 Redis 인스턴스가 Gitlab::Redis::Foo라고 할 경우, 다음을 실행하여 두 피처 플래그를 생성할 수 있습니다:
bin/feature-flag use_primary_and_secondary_stores_for_foo
bin/feature-flag use_primary_store_as_default_for_foo
use_primary_and_secondary_stores_for_foo 피처 플래그를 활성화하면,
Gitlab::Redis::Foo는 MultiStore를 사용하여 새 Redis 인스턴스와
이전 (폴백 인스턴스) 모두에 씁니다.
모든 읽기 명령은 use_primary_store_as_default_for_foo 피처 플래그로 제어되는 기본 스토어에서만 수행됩니다.
use_primary_store_as_default_for_foo 피처 플래그를 활성화하면,
MultiStore는 primary_store(새 인스턴스)를 기본 Redis 스토어로 사용합니다.
pipelined 명령(pipelined 및 multi)의 경우, 두 스토어 모두에서 전체 작업을 실행한 후 결과를 비교합니다.
결과가 다르면 Gitlab::Redis::MultiStore:PipelinedDiffError 오류를 발생시키고,
gitlab_redis_multi_store_pipelined_diff_error_total Prometheus 카운터에 추적합니다.
새 스토어가 채워지는 일정 기간이 지난 후, 두 스토어의 상태를 비교하기 위해 외부 검증을 수행할 수 있습니다.
검증 결과가 만족스러우면, 트래픽을 새 Redis 스토어로 이동해도 안전합니다.
use_primary_and_secondary_stores_for_foo 피처 플래그를 비활성화할 수 있습니다.
이렇게 하면 MultiStore가 primary Redis 스토어(새 스토어)에서만 읽고 쓸 수 있게 되어 모든 트래픽이 새 Redis 스토어로 이동됩니다.
모든 트래픽을 primary 스토어로 이동하면 데이터 마이그레이션이 완료됩니다. MultiStore 구현을 안전하게 제거하고 새로 도입된 Redis 스토어 인스턴스를 계속 사용할 수 있습니다.
구현 세부 사항#
MultiStore는 읽기와 쓰기 Redis 명령을 별도로 구현합니다.
읽기 명령#
읽기 명령은 Gitlab::Redis::MultiStore::READ_COMMANDS 상수에 정의되어 있습니다.
쓰기 명령#
쓰기 명령은 Gitlab::Redis::MultiStore::WRITE_COMMANDS 상수에 정의되어 있습니다.
pipelined 명령#
이 명령들에 전달된 Ruby 블록은 각 스토어당 한 번씩, 총 두 번 실행됩니다. 따라서 수행되는 Redis 작업을 제외하고, 블록은 멱등성(idempotent)을 가져야 합니다.
-
pipelined -
multi
지원 목록에 없는 명령을 사용하는 경우, method_missing이 이전 Redis 인스턴스에 전달하고 이를 추적합니다.
이를 통해 예상치 못한 동작이 이전과 동일하게 동작하도록 보장합니다.
개발 또는 테스트 환경에서는 조기 탐지를 위해 오류가 발생합니다.
gitlab_redis_multi_store_method_missing_total 카운터와 Gitlab::Redis::MultiStore::MethodMissingError를 추적함으로써,
개발자는 마이그레이션을 진행하기 전에 누락된 Redis 명령에 대한 구현을 추가해야 합니다.
pipelined 및 multi 블록 내에서 변수 할당은 블록이 멱등성을 가져야 하므로 권장되지 않습니다.
이전에 마이그레이션 중 잘못된 애플리케이션 동작을 초래했던 비멱등성 블록을 제거한 수정 머지 리퀘스트를 참조하세요.
오류#
| 오류 | 메시지 |
|---|---|
| Gitlab::Redis::MultiStore::PipelinedDiffError | pipelined command executed on both stores successfully but results differ between them. |
| Gitlab::Redis::MultiStore::MethodMissingError | Method missing. Falling back to execute method on the Redis secondary store. |
메트릭#
| 메트릭 이름 | 유형 | 라벨 | 설명 |
|---|---|---|---|
| gitlab_redis_multi_store_pipelined_diff_error_total | Prometheus Counter | command, instance_name | Redis MultiStore pipelined command diff between stores |
| gitlab_redis_multi_store_method_missing_total | Prometheus Counter | command, instance_name | Client side Redis MultiStore method missing total |
Step 4: 마이그레이션 후 정리#
GitLab Self-Managed 인스턴스가 이 마이그레이션을 수행할 것으로 예상되는지 여부에 따라
마이그레이션 경로를 유지하거나 제거하도록 선택할 수 있습니다.
gitlab-com/gl-infra/scalability#1131에는
트레이스 청크 피처 플래그에 대한 이 주제에 관한 논의가 포함되어 있습니다.
self-managed 인스턴스가 이 기능적 파티션 없이도 잘 동작할 것으로 예상된다면,
마이그레이션 코드를 유지하는 유지 관리 비용이 self-managed 인스턴스가 원활하게 이 마이그레이션을 수행할 수 있도록 하는 이점보다 높다고 결정할 수도 있습니다.
마이그레이션 코드를 유지하기로 결정한 경우:
-
마이그레이션 단계를 문서화해야 합니다.
-
피처 플래그를 사용한 경우, 수명이 긴 플래그이므로 ops 유형 피처 플래그인지 확인해야 합니다.
그렇지 않으면 플래그를 제거하고 프로젝트를 완료할 수 있습니다.