새로운 Redis 인스턴스 추가
GitLab은 여러 Redis 인스턴스를 사용할 수 있습니다.
이 인스턴스들은 기능적으로 파티션되어 있어서, 예를 들어, 한 Redis 인스턴스에서 CI 추적 조각을 저장하면서 다른 인스턴스에 세션을 저장할 수 있습니다.
때때로 우리는 새로운 Redis 인스턴스를 추가하고자 할 수 있습니다. 일반적으로 이것은 기존 인스턴스 중 하나에서 분리된 기능적 파티션이 될 것입니다. 이 문서는 기존 데이터를 처리하는 새로운 Redis 인스턴스를 추가하는 접근 방식을 이전 예시를 바탕으로 설명합니다:
이 문서는 새로운 Redis 인스턴스를 준비하고 구성하는 운영 측면을 자세히 다루지는 않지만, 예시 에픽에는 이전 접근 방식에 대한 정보가 포함되어 있습니다.
단계 1: 새로운 인스턴스 구성 지원
새로운 인스턴스의 기능을 전환하기 전에, 우리는 그것을 구성하고 코드베이스에서 참조할 수 있는 방법을 지원해야 합니다. 주요 설치 유형을 지원해야 합니다:
대체 인스턴스
애플리케이션 코드에서 새로운 인스턴스가 구성되지 않았을 경우에 대비하여 대체 인스턴스를 정의해야 합니다.
예를 들어, GitLab 인스턴스가 이미 별도의 공유 상태 Redis를 구성한 경우, 공유 상태 Redis에서 데이터를 파티션하고 있는 경우, 새로운 인스턴스의 구성은 공유 상태 Redis의 기본값으로 설정되어야 합니다. 그렇지 않으면, 새로운 Redis 인스턴스가 사용 가능해지는 즉시 이를 구성하지 않은 인스턴스가 중단될 수 있습니다.
.config_fallback
메서드 정의하기를
Gitlab::Redis::Wrapper
에서 정의하여 이 인스턴스가 구성되지 않았을 경우 사용할 인스턴스를
정의할 수 있습니다. 만약 우리가 Foo
인스턴스를 추가하고,
그 인스턴스가 SharedState
로 대체되어야 한다면, 아래와 같이 할 수 있습니다:
module Gitlab
module Redis
class Foo < ::Gitlab::Redis::Wrapper
# Foo에 저장된 데이터는 이전에 SharedState에 저장되었습니다.
def self.config_fallback
SharedState
end
end
end
end
trace_chunks_spec.rb
와 같은 사양을 추가하여 이 대체가 올바르게 작동하는지
확인해야 합니다.
단계 2: 새로운 인스턴스에 대한 쓰기 및 읽기 지원
새로운 인스턴스로 마이그레이션할 때, 데이터가 어느 곳에 있는지를 고려해야 합니다:
- ‘오래된’(원본) 인스턴스.
- 우리가 방금 지원을 추가한 새로운 인스턴스.
결과적으로 일부 조건에 따라 두 인스턴스 모두에서 읽기 및 쓰기를 지원해야 할 수도 있습니다.
사용해야 할 정확한 조건은 마이그레이션할 데이터에 따라 다릅니다. 위의 추적 조각 경우처럼, 데이터가 저장된 위치를 나타내는 데이터베이스 열이 이미 있었습니다 (다른 저장 옵션이 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.create_using_pool(self.pool, config_fallback.pool, store_name)
end
end
end
end
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
는 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가 기본 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 |
메소드가 없습니다. Redis 보조 저장소에서 메소드를 실행하도록 되돌아갑니다. |
메트릭
메트릭 이름 | 유형 | 레이블 | 설명 |
---|---|---|---|
gitlab_redis_multi_store_pipelined_diff_error_total |
Prometheus Counter |
command , instance_name
|
Redis MultiStore pipelined 명령어 간 차이 |
gitlab_redis_multi_store_method_missing_total |
Prometheus Counter |
command , instance_name
|
클라이언트 측 Redis MultiStore 메소드 누락 총계 |
4단계: 마이그레이션 후 정리
우리는 마이그레이션 경로를 유지할지 또는 제거할지 선택할 수 있으며, 이는 자가 관리 인스턴스가 이 마이그레이션을 수행할 것으로 예상되는지 여부에 따라 다릅니다.
gitlab-com/gl-infra/scalability#1131에서는 이 주제에 대한 토론이 포함되어 있으며, 여기서 기능 플래그는 그 경우와 마찬가지로 자가 관리 인스턴스가 이 기능을 지원할 필요가 있다고 가정할 경우 마이그레이션 코드를 지원하는 유지 관리 비용이 이점을 초과한다고 판단할 수 있습니다.
마이그레이션 코드를 유지하기로 결정하면:
- 마이그레이션 단계를 문서화해야 합니다.
- 기능 플래그를 사용했다면, 이를 ops 유형 기능 플래그로 설정해야 하며, 이러한 플래그는 장기적으로 유지됩니다.
그렇지 않으면 플래그를 제거하고 프로젝트를 종료할 수 있습니다.