새로운 Redis 인스턴스 추가하기
GitLab은 여러 Redis 인스턴스를 활용할 수 있습니다. 이러한 인스턴스들은 기능적으로 분할되어 있어, 예를 들어 하나의 Redis 인스턴스에서 CI trace chunks를 저장하고 다른 인스턴스에서 세션을 저장할 수 있습니다.
가끔씩 새로운 Redis 인스턴스를 추가하고 싶을 수 있습니다. 일반적으로 이는 기존 캐시나 공유 상태와 같은 기존 인스턴스에서 기능적으로 분할된 새로운 인스턴스를 추가하는 것입니다. 이 문서에서는 이전 예제를 기반으로 기존 데이터를 처리하는 새로운 Redis 인스턴스를 추가하는 접근 방법을 설명합니다.
이 문서는 새로운 Redis 인스턴스의 운영적인 측면에 대한 준비와 구성에 대해 상세하게 다루지는 않지만, 예제 작업은 이에 대한 이전 접근 방법의 정보를 포함하고 있습니다.
단계 1: 새 인스턴스 구성 지원
새로운 인스턴스를 사용하도록 어떤 기능을 전환하기 전에, 우리는 이를 구성하고 코드베이스에서 참조할 수 있도록 지원해야 합니다. 우리는 다음과 같은 주요 설치 유형을 지원해야 합니다:
후속 인스턴스
애플리케이션 코드에서, 새로운 인스턴스가 구성되지 않은 경우를 대비하여 후속 인스턴스를 정의해야 합니다. 예를 들어, 이미 별도의 공유 상태 Redis를 구성한 GitLab 인스턴스가 있고, 우리가 공유 상태 Redis에서 데이터를 분할하는 경우, 새 인스턴스의 구성은 존재하지 않을 때 공유 상태 Redis의 기본 구성으로 설정되어야 합니다. 그렇지 않으면 우리는 새로운 Redis 인스턴스를 구성하지 않은 인스턴스를 즉시 망가뜨릴 수 있습니다.
Gitlab::Redis::Wrapper
(모든 Redis 인스턴스의 기본 클래스)에 .config_fallback 메서드를 정의하여 이 인스턴스가 구성되지 않았을 때 사용될 인스턴스를 정의할 수 있습니다. 예를 들어, Foo
인스턴스를 추가하여 SharedState
로 fallback해야 한다면 다음과 같이 할 수 있습니다:
module Gitlab
module Redis
class Foo < ::Gitlab::Redis::Wrapper
# Foo에서 사용하는 데이터는 이전에 SharedState에 저장되었습니다.
def self.config_fallback
SharedState
end
end
end
end
또한, 이 fallback가 제대로 작동하는지 확인하기 위해 trace_chunks_spec.rb와 같은 스펙을 추가해야 합니다.
단계 2: 새 인스턴스에 쓰기 및 읽기 지원
새로운 인스턴스로 마이그레이션할 때, 데이터가 다음과 같이 있을 수 있는 경우에 대비해야 합니다:
- ‘옛날’ (원래) 인스턴스.
- 방금 추가한 새로운 인스턴스.
따라서 특정 조건에 따라 두 인스턴스에서 읽고 쓸 수 있도록 지원해야 할 수 있습니다.
사용할 정확한 조건은 마이그레이션할 데이터에 따라 다릅니다. 위에서 언급한 trace chunks 경우, 데이터가 어디에 저장되었는지를 나타내는 데이터베이스 열이 이미 있었습니다 (Redis 이외의 다른 저장 옵션이 있기 때문입니다).
이 단계는 데이터의 수명이 매우 짧고(최대 몇 분 정도), 중요하지 않은 경우에는 해당하지 않을 수 있습니다. 이 경우에는 데이터 손실이 발생해도 괜찮다고 판단할 수 있고, 구성만으로 전환하는 것이 적당할 수 있습니다.
데이터가 어디에 저장되었는지를 표시할 더 자연스러운 방법이 없는 경우, 기능 플래그를 사용하는 것이 편리할 수 있습니다:
- 적용하려면 애플리케이션 재시작이 필요하지 않습니다.
- 동시에 모든 애플리케이션 인스턴스(Sidekiq, API, 웹 등)에 적용됩니다.
- 증분 롤아웃을 지원합니다 - 이상적으로는 액터(프로젝트, 그룹, 사용자 등)별로 - 오류를 모니터링하고 쉽게 롤백할 수 있도록 합니다.
단계 3: 데이터 마이그레이션
그런 다음, 새로운 인스턴스를 GitLab.com의 프로덕션 및 스테이징 환경에 구성해야 합니다. 이 변경 사항을 스테이징에서 효과적으로 테스트할 수 있으면 좋겠지만 최소한 기본 사용이 계속되는지 확인하는 것은 가능해야 할 것입니다.
그 작업이 완료된 후, 우리는 프로덕션 환경에 변경 사항을 롤아웃할 수 있습니다. 이상적으로는 기능 플래그를 위한 표준 증분 롤아웃 문서에 따라 점진적으로 적용할 수 있어야 합니다.
새로운 인스턴스를 프로덕션 환경에서 일정 기간 동안 100% 사용하고 나아진 사항이 없다면, 계속 진행할 수 있어야 합니다.
제안된 솔루션: MultiStore 및 폴백 전략을 활용한 데이터 마이그레이션
UX 관점에서 불편함 없이 사용자를 새로운 Redis 저장소로 마이그레이션하는 방법이 필요합니다. 또한, 새 인스턴스에서 문제가 발생할 경우 “이전” Redis 인스턴스로 되돌아갈 수 있는 기능이 필요합니다.
마이그레이션 요구 사항:
- 다운 타임 없음.
- 데이터 저장 TTL이 만료될 때까지 저장된 데이터의 손실 없음.
- 기능 플래그 또는 ENV 변수 또는 둘의 조합을 사용한 부분적 롤아웃.
- 스위치 모니터링.
- Prometheus 지표가 준비됨.
- 새 인스턴스 또는 로직이 예상대로 작동하지 않을 경우 다운 타임 없이 손쉬운 롤백.
이는 제로 다운타임 DB 테이블 이름 변경과 다소 유사합니다. 우리는 데이터를 새로운(+) 기존 Redis 인스턴스로 씁니다. 우리는 새 인스턴스에서 읽지만, 새 전용 Redis 인스턴스를 사전 처리할 때 실패한다면 이전 인스턴스로 되돌아가야 합니다. 새 인스턴스의 문제 또는 예외를 기록해야 하지만, 여전히 이전 인스턴스로 되돌아가야 합니다.
제안된 마이그레이션 전략은 MultiStore를 구현하고 사용하는 것입니다. 우리는 세션 키를 위한 새로운 전용 Redis 인스턴스 추가를 이 접근 방식으로 구현했습니다. 또한 MultiStore에는 해당 스펙이 있습니다.
MultiStore는 redis-rb ::Redis
인스턴스와 비슷합니다.
단계 1에서 추가한 새 Redis 인스턴스 클래스에서는 ::Gitlab::Redis::MultiStoreWrapper
를 상속하고 multistore
클래스 메소드를 재정의하여 MultiStore를 정의해야 합니다.
module Gitlab
module Redis
class Foo < ::Gitlab::Redis::MultiStoreWrapper
...
def self.multistore
MultiStore.new(self.pool, config_fallback.pool, store_name)
end
end
end
end
MultiStore는 기본적으로 새 Redis 연결 풀을 주요 풀로, 이전 (폴백-인스턴스) 연결 풀을 보조 풀로 제공하여 초기화됩니다. 세 번째 인자는 동시에 MultiStore를 동일한 시간에 다른 Redis 저장소에 대해 사용할 경우 로그, 지표 및 기능 플래그 이름으로 사용되는 store_name
입니다.
기본적으로 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
는 기본 Redis 저장소로 primary_store
(새 인스턴스)를 사용합니다.
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는 새 Redis 저장소로부터 읽고 쓸 수 있게 되며, 모든 트래픽이 새 Redis 저장소로 이동됩니다.
모든 트래픽을 주 저장소로 옮긴 후에는 데이터 마이그레이션이 완료됩니다. MultiStore 구현을 안전하게 제거할 수 있으며, 새롭게 도입된 Redis 저장소 인스턴스를 계속 사용할 수 있습니다.
구현 세부 정보
MultiStore는 읽기 및 쓰기 Redis 명령을 따로 구현합니다.
읽기 명령어
읽기 명령어는 Gitlab::Redis::MultiStore::READ_COMMANDS
상수에 정의되어 있습니다.
쓰기 명령어
쓰기 명령어는 Gitlab::Redis::MultiStore::WRITE_COMMANDS
상수에 정의되어 있습니다.
pipelined
명령어
참고: 이러한 명령에 전달된 Ruby 블록은 각 저장소별로 한 번씩 실행됩니다.
따라서 Redis 작업을 제외하면 블록은 동일한 결과를 가져야 합니다.
pipelined
multi
지원되는 목록 외의 명령을 사용할 때, method_missing
은 이를 이전의 Redis 인스턴스로 전달하고 추적합니다.
이로써 예상치 못한 어떤 것도 이전과 같이 동작하도록 보장합니다. 개발 또는 테스트 환경에서는 일찍 에러가 발생하여 감지됩니다.
참고: gitlab_redis_multi_store_method_missing_total
카운터와 Gitlab::Redis::MultiStore::MethodMissingError
을 추적함으로써, 개발자는 마이그레이션을 진행하기 전에 누락된 Redis 명령에 대한 구현을 추가해야 합니다.
참고: pipelined
및 multi
블록 내에서 변수 할당은 권장되지 않습니다. 블록은 동일한 결과를 가져야 하므로 이 블록 안에서 변수 할당을 지양해야 합니다. 마이그레이션 중에 이전에 잘못된 애플리케이션 동작을 유발한 비-동형 블록을 제거한 수정 MR을 참조하세요.
오류
오류 | 메시지 |
---|---|
Gitlab::Redis::MultiStore::PipelinedDiffError
|
pipelined 명령이 두 저장소에서 성공적으로 실행되었지만 그들 간에 결과가 다릅니다.
|
Gitlab::Redis::MultiStore::MethodMissingError
| 메서드 누락. 레디스 보조 저장소에서 메서드를 실행하도록 다시 대체합니다. |
메트릭
메트릭 이름 | 유형 | 라벨 | 설명 |
---|---|---|---|
gitlab_redis_multi_store_pipelined_diff_error_total
| 프로메테우스 카운터 |
command , instance_name
| Redis MultiStore pipelined 명령의 저장소 간 차이
|
gitlab_redis_multi_store_method_missing_total
| 프로메테우스 카운터 |
command , instance_name
| 클라이언트 측 Redis MultiStore 메서드 누락 총계 |
단계 4: 마이그레이션 후 정리
우리는 이주 경로를 유지하거나 제거하는 것을 선택할 수 있습니다.
이것은 자체 관리형 인스턴스가 이 마이그레이션을 수행할 것으로 기대하는지에 따라 달라집니다.
gitlab-com/gl-infra/scalability#1131에는 트레이스 청크 기능 플래그에 대한 이 주제에 대한 토론이 포함되어 있습니다.
그 경우처럼 유지 관리 코드를 지원하는 유지 비용이 이 기능적 분할 없이도 자체 관리형 인스턴스가 이 마이그레이션을 수행할 것으로 예상된다면, 이것보다 높을 수 있습니다.
에 다음 사항이 있습니다: - 마이그레이션 단계를 문서화해야 합니다. - 기능 플래그를 사용한 경우 운영형 기능 플래그인지 확인해야 합니다. 이러한 플래그는 장기간 사용되는 플래그입니다.
그렇지 않으면 플래그를 제거하고 프로젝트를 마무리할 수 있습니다.