티스토리 뷰
항상 Single Redis Cluster / 단일 Primary 기반의 Replication 구조만 운영할 수는 없다.
장애 민감도가 낮거나, 투자할 수 있는 비용 또는 발생 트래픽이 적은 서비스는 Redis Cluster(또는 Dragonfly, Memcached 등 다양한 솔루션이 있겠지만 가장 평범한 예를 들어봅니다) / 단일 Primary 기반의 Replication 구조를 공용으로 만들어 사용하는 것이 도메인 / 서비스 별로 Redis Cluster / Primary - Secondary를 나누어 운영하는 것보다 인프라 인력에 대한 비용이나 유지 보수 측면에서 이점이 있다고 볼 수 있습니다.
- *EOL 도래 시 무중단 버전 업그레이드 같은 운영 작업 리소스, 운영 모니터링 등을 인프라 인력 비용으로 간주합니다.
이는 Redis 뿐만 아니라 MySQL, MongoDB 등 다양한 데이터 저장소나 Kafka, RabbitMQ 같은 메시지 큐 서비스 모두에게 해당한다고 볼 수 있죠. 하지만 서비스가 커짐에 따라 아무리 현재의 Single Cluster / Primary가 여러 Node의 집합 또는 연계로 이루어져 있더라도 상대적으로 장애에 취약하고 서비스 간의 결합도를 높이는 요인으로 볼 수 있습니다.
장기적으로 트래픽, 서비스와 기술 조직의 규모가 커짐에 따라 여러 Cluster로 나누어 운영하는 것이 서비스 단위로 장애를 격리하고 결합도를 낮추며 비용 최적화에도 도움이 됩니다. 여기서 비용 최적화의 대상은 서비스 별 워크로드, 트래픽에 따른 VM Spec의 최적화를 의미합니다.
- *여기서 워크로드는 서비스 / 팀 별 사용 사례를 의미하는 것으로 비즈니스에 필요한 핵심적인 일부 키만 캐싱하고자 하는 경우, 원천 데이터를 제공하고 있으며 전반적으로 모든 데이터가 고루 조회되고 있어 Cache Hit이 낮을 때 대부분의 데이터를 Cache에 적재하여 운용하는 경우라던지, Geospatial Search를 사용하는 경우 등 다양한 사용 사례가 있습니다.
저는 서비스가 망하지 않는 한 (내가 배포하지 않더라도) Cluster를 분리할 정도로 성장할 것이다 라는 생각으로 다른 서비스 / 도메인과의 의존성 없이 Redis를 사용해야 한다고 생각하며, 그 첫 번째 실천이 바로 적절한 Redis Key를 설계하는 것이라고 생각합니다.
Redis에는 Database라는 단위로 Key가 존재할 수 있는 논리적인 공간 (Namespace)를 할당할 수 있고, 각 서비스, Redis Client 마다 다른 Database를 바라볼 수 있는데 왜 Redis Key를 통해 논리적인 구분이 되어야 하나요 라고 이야기하실 수도 있겠습니다.
뭐 그렇게 논리적으로 격리해 처리하는 것도 의도된 방법이고 나쁘지 않겠다고 생각합니다만.. 저는 실무에서 그렇게 나누어 운영하는 경우를 본 적이 없다시피 합니다. (딱 1번 있는 경험도 제대로 문서화되지 않아 Key를 찾는데 애먹었던 기억만 남아있네요.)
- *개인적으로 Database를 쪼개어 관리하는 것 보다는 Redis Key의 이름을 도메인 / 기능 단위로 잘 구분하는 것이 운영하기에 더 좋은 방법이라고 생각합니다. Database로 나눈다고 해서 장애가 발생했을 때 격리되는 것도 아니구요.
Single Cluster / 단일 Primary 기반의 Replication 구조에서 Redis Key를 설계하는 방법
Redis Key를 설계하는 팀이나 조직마다 선호하는 패턴이나 약속은 다르겠지만 일반적으로 다음과 같이 구분자를 두는 것이 기본적인 방법이라고 생각합니다.
- 자주 변하지 않는 것 / 뒤에 붙을 용어, 개념을 포괄하는 것을 가장 앞에 둡니다.
- 도메인 또는 서비스 명, 기능 명, 데이터 명, 버전 등의 메타 데이터가 있다면 도메인 또는 서비스 명이 제일 앞에 두는 것이 좋다고 생각합니다.
- 이를 통해 키의 prefix 만 보더라도 찾고자하는 키가 포함되어 있는지 구분할 수 있고, 패턴 매칭의 용이성을 가집니다. (Scan을 통한 특정 Key 집합 삭제 등)
- e.g. COUPON:APPLIED_COUPON_LIST:V1 (도메인 / 데이터 / 버전)
- 특별한 이유가 없는 한, 모든 Cache에 버전을 부여합니다.
- Cache에 부여된 버전은 운영 편의성을 제공합니다. 같은 Cache 라고 하더라도 저장 / 변환하는 데이터 포맷이 변경되거나 압축 알고리즘이 적용 또는 바뀌었을 때, 기존에 저장된 Cache를 조회해 오는 것은 장애로 이어질 수 있습니다.
- 발생 트래픽이 높은 환경에서, 아직 만료되지 않은 V1은 이전 버전의 Application이 점유해 계속 서빙하고 신규 버전의 Application은 Warmup 또는 Look-Aside 방식을 통해 V2 Cache를 적재 / 제공한다면 안정적인 무중단 배포가 가능해집니다.
- *Warmup, Look-Aside 방식으로는 트래픽 대응을 할 수 없는 경우, V1 Key를 별도 Batch로 읽어와 V2로 데이터를 마이그레이션 하고 V2 Application을 배포할 수도 있습니다.
- 만약 신규 버전에서 예상치 못한 이슈가 발생하더라도 변경 없이 이전 버전으로 롤백만 수행하여 큰 장애를 방지할 수 있습니다.
- Cache에 부여된 버전은 운영 편의성을 제공합니다. 같은 Cache 라고 하더라도 저장 / 변환하는 데이터 포맷이 변경되거나 압축 알고리즘이 적용 또는 바뀌었을 때, 기존에 저장된 Cache를 조회해 오는 것은 장애로 이어질 수 있습니다.
- 가능하면 약어를 쓰지 않습니다.
- 가끔 글을 읽다보면 추상적인 기준으로 Key의 크기는 작아야한다. 라는 식으로 언급하는 글들이 많지만 제 경험상 문제가 있을 정도로 Key 길어지는 것은 쉽지 않습니다. (그걸 의도하지 않는 한) 사실 이해할 수 없는 Redis Key를 만들 바에 이해 가능한 Redis Key를 설계하는 것이 훨씬 좋습니다.
- 큰 Key를 감주하는 기준은 사용 시나리오에 따라 다릅니다. 높은 동시성, 낮은 지연 시간을 강조하는 서비스에서는 1KB도 매우 큰 키로 간주될 수 있지만 그렇지 않은 서비스에서는 10KB 정도로 사용하는 것이 괜찮을 수 있습니다.
이렇게 Redis Key를 설계한다면 추후 서비스 간의 점진적 분리나 별도 Cluster 구축 시에 Side Effect를 최소화하고 분리할 수 있습니다. (기가막힌 방법을 통해 그렇지 않게끔 만드는 경우도 있겠습니다만..) 물리적인 분리 전에 논리적인 단계에서의 분리부터 차근차근 해보면 좋겠습니다.
- Total
- Today
- Yesterday
- mybatis
- 근황
- RPC
- cache stampede
- RESTful
- configuration
- Thundering Herd
- WiredTiger
- Optimistic Locking
- JVM
- spring
- JPA
- Request Collapsing
- Redis Key
- spring AOP
- JDK Dynamic Proxy
- HTTP
- transaction
- Switch
- URN
- single source of truth
- URI
- 소비자 관점의 api 설계 패턴과 사례 훑어보기
- AMQP
- cglib
- hypermedia
- lambda
- Url
- java
- rabbitmq
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |