프로젝트
약 16분 분량 개인 프로젝트/위키엔진

Redis 샤딩: Consistent Hashing으로 워크로드 격리

RedisConsistent HashingShardingPerformancek6Spring BootGrafanaDocker
목차

이전 글

CDC (Change Data Capture): 이벤트 기반 동기화에서 PostService의 dual-write 구조를 이벤트 기반으로 전환하고, Debezium + Kafka CDC로 모든 DB 변경을 캡처했습니다. 이 글은 단일 Redis 인스턴스의 구조적 문제를 실측하고, Consistent Hashing으로 워크로드를 격리하는 과정입니다.


이전 글 요약

지표CDC 결과
PostService 의존성6개 → 이벤트 발행만 (OCP 준수)
검색 캐시 무효화미구현 → 이벤트 기반 L1 즉시 무효화
테스트117개 전체 통과
CDC 파이프라인MySQL binlog → Debezium → Kafka → Consumer
아키텍처 패턴@ApplicationModuleListener (fallback) + Kafka CDC (primary)

인프라와 아키텍처 모두 안정적이다. 이제 단일 Redis 인스턴스의 워크로드 간섭 문제와 KEYS 블로킹 안티패턴을 해결할 차례다.


1. 현재 아키텍처: 단일 Redis 인스턴스

Before: 단일 Redis 인스턴스 (4가지 워크로드 혼재)

하나의 Redis 인스턴스에 성격이 완전히 다른 4가지 워크로드가 혼재한다:

용도키 패턴추정 키 수특성
자동완성 KVprefix:v{version}:{prefix}수만~수십만배치 갱신, TTL 2시간
게시글/검색 캐시 (L2)post:{id}, search:{keyword}:{page}:{size}핫 쿼리 수천TTL 기반
조회수 카운터post:views:{id}조회된 게시글 수30초 flush 후 삭제
토큰 블랙리스트blacklist:{token}로그아웃 수TTL = JWT 잔여시간

2. 문제 인식: 3가지 실측 근거

문제 1: KEYS 블로킹 안티패턴, 30초마다 Redis 전체 멈춤

ViewCountService.flushToDB()에서 30초마다 실행되는 코드:

Set<String> keys = redisTemplate.keys(KEY_PREFIX + "*"); // KEYS post:views:*

Redis 공식 문서: “Don’t use KEYS in your regular application code. Consider it only for debugging purposes.”

KEYS전체 keyspace를 O(N)으로 블로킹 스캔한다. Redis는 싱글스레드이므로, KEYS 실행 동안 모든 GET/SET/INCR이 큐에서 대기한다.

실측 결과 (OCI ARM 2vCPU/12G, Redis 7.4-alpine, 키 2,041개):

명령시도 1시도 2시도 3Redis 내부 (SLOWLOG)
KEYS post:views:*88ms46ms66ms34.6ms
KEYS prefix:*48ms116ms46ms

셸 측정(46~116ms)에는 docker exec 오버헤드가 포함되어 있다. Redis 내부 실제 시간은 SLOWLOG 기준 34.6ms로, slowlog-log-slower-than 10000(10ms) 임계값을 3.4배 초과하여 SLOWLOG에 기록됐다. KEYS는 O(N)이므로 키가 10배(2만 개)면 ~350ms, 100배(20만 개)면 ~3.5초로 선형 악화한다.

프로덕션 사고 사례:

  • sidekiq-cron: KEYS cron_jobs:*가 40M 키 환경에서 5~6초 블로킹 → 서비스 다운
  • Medusa(e-commerce): 330K 키에서 KEYS mc:tag:*Redis 전체 블로킹, 모든 클라이언트 대기
  • Drupal Redis: 150만 키에서 캐시 flush 시 전체 사이트 프리징 → SCAN 패치로 전환

문제 2: 배치 쓰기 vs 실시간 읽기가 부르는 워크로드 간섭

buildPrefixTopK()가 매시간 수만 개 키를 동시에 SET하면서, 실시간 GET/INCR과 같은 싱글스레드에서 경합한다.

실측 근거 (SLOWLOG + commandstats):

SLOWLOG에 배치 빌드 중 개별 SET이 10.7ms로 기록됨:

SLOWLOG #2: SET prefix:v1774134000041:scien ["science"] EX 7200 → 10,722us (10.7ms)

commandstats 기준 SET 평균은 10.04us(0.01ms)이므로, 이 10.7ms는 평균의 1,070배다. 일반 GET 명령도 SLOWLOG에 잡혔다:

SLOWLOG #1: GET prefix:v1774018800170:삼성 → 15,513us (15.5ms)

GET 평균 8.30us 대비 1,868배 느린 케이스로, 배치 쓰기와 실시간 읽기가 같은 싱글스레드에서 경합하는 증거다.

redis-benchmark baseline (배치 없을 때):

명령처리량P50
SET88,339 req/s0.231ms
GET104,602 req/s0.231ms

baseline에서 Redis 자체는 충분히 빠르다 (GET P50 0.231ms). 문제는 KEYS/배치 빌드 시점에 간헐적으로 수십 ms 스파이크가 발생하는 것이다.

현업 사례:

  • GitLab: eviction 중 Redis 메인 스레드 CPU 대부분 소모, 응답률 25,000 → 5,000 ops/sec (80% 하락)
  • Alibaba Cloud: 트래픽 증가로 메모리 5분 만에 100%, eviction 연쇄 → 모든 GET/SET 타임아웃

문제 3: 용도별 격리 부재와 blast radius

워크로드특성위험
자동완성 KV배치 대량 WRITE (수만 키)배치 중 다른 워크로드 지연
게시글/검색 캐시 (L2)TTL 기반, eviction 허용eviction이 다른 키에 영향
조회수 카운터고빈도 INCR, 30초 flushKEYS 스캔이 INCR 블로킹
토큰 블랙리스트보안 크리티컬, 유실 불가volatile-lru에서 TTL 있는 블랙리스트 키가 eviction 대상 가능

핵심 위험: maxmemory-policy volatile-lru 설정에서, 메모리가 128MB에 도달하면 TTL이 설정된 모든 키가 eviction 대상이 된다. 블랙리스트 키(blacklist:{token})도 TTL이 있으므로 eviction되면 로그아웃된 토큰이 다시 유효해진다. 곧 보안 사고다.

Redis 공식 문서의 직접 권고: “The volatile-lru, volatile-random policies are mainly useful when you want to use a single instance for both caching and persistent keys. However, it is usually a better idea to run two Redis instances to solve such a problem.”

eviction 시뮬레이션 결과 (maxmemory를 used_memory + 1MB로 임시 축소):

항목결과
evicted_keys2,077개
블랙리스트 키 (Before)1개
블랙리스트 키 (After)1개 (이번에는 생존)

이번 테스트에서 블랙리스트 키는 LRU 순서상 최근 접근이라 eviction 후순위였다. 하지만 이건 타이밍에 의존하는 결과다. 로그아웃 후 시간이 지나 LRU에서 밀리면 eviction 대상이 된다.

핵심: 2,077개 키가 eviction되는 동안 어떤 키가 제거될지 예측할 수 없다. 이번에 블랙리스트가 살아남은 건 운이지 보장이 아니다.

3가지 문제의 공통 원인: 성격이 다른 워크로드가 하나의 싱글스레드 Redis를 공유하고 있다. Redis 창시자(Salvatore Sanfilippo)는 단일 인스턴스에서 SELECT로 DB를 나누는 것을 “the worst design mistake”라고 직접 언급했다.


3. 근본 원인 분석

Redis는 모든 명령을 단일 스레드에서 순차 실행한다. 이것이 INCR의 원자성을 보장하는 장점이지만, 동시에 하나의 느린 명령이 모든 것을 블로킹하는 단점이다.

문제원인해결 방향
KEYS 블로킹O(N) 전체 keyspace 스캔, 중간에 양보(yield) 안 함KEYS → SCAN 전환
배치 간섭Pipeline 없이 개별 SET 수만 회 → 네트워크 왕복 수만 회용도별 Redis 인스턴스 분리
blast radiusvolatile-lru가 TTL 키를 용도 구분 없이 eviction블랙리스트 전용 인스턴스

4. 대안 검토

대안 비교: Redis 분산 전략

Redis Cluster (공식 분산)

자동 샤딩(16384 슬롯) + 자동 failover는 프로덕션 표준이지만 최소 6노드($144/월)가 필요하다. 현 규모(~60MB)에서는 과잉이다.

Redis Sentinel (현 규모 최적)

자동 failover + 단일 Primary. 데이터가 단일 노드 메모리에 들어가고 HA만 필요한 경우의 최적 선택. ~$36/월.

핵심 trade-off: 현 규모에서 고가용성만 필요했다면 Redis Sentinel이나 단일 인스턴스 유지가 더 단순한 선택이었을 수 있습니다. 하지만 이 단계의 핵심 문제는 HA보다도, 서로 다른 워크로드를 같은 인스턴스에 섞어두었을 때 생기는 간섭과 보안상 영향 범위를 어떻게 줄일 것인가에 더 가까웠습니다.

앱 레벨 Consistent Hashing (선택: 워크로드 분리와 제어 가능성)

Redis Cluster는 범용 샤딩에는 적합하지만, 이 단계에서 중요했던 것은 단순한 데이터 분산보다 자동완성, 일반 캐시, 조회수, 블랙리스트처럼 성격이 다른 데이터를 어떻게 분리하고 어떤 노드에 둘지 더 세밀하게 제어하는 일이었습니다. 그래서 현재 제약 안에서는 애플리케이션 라우팅 계층을 두고, 키 재배치와 노드별 역할을 직접 통제하는 방식이 더 적합하다고 판단했습니다.

비용 환산 (AWS 기준)

구성월 비용 (AWS)비고
현재 (단일 Redis)ElastiCache t4g.micro ~$12/월128MB, 단일 노드
Redis Sentinel (권장)~$36/월1 Primary + 2 Replica
3노드 샤딩 (선택)~$36/월 + 앱 라우팅 관리동일 비용에 운영 부담 추가
Redis Cluster~$144/월3P + 3R, 과잉

5. Consistent Hashing 알고리즘

Consistent Hashing: 해시 링과 가상 노드

hash(key) % N의 문제

단순 모듈로 해시는 노드 수가 변경되면 거의 모든 키가 재배치된다:

시나리오hash(key) % NConsistent Hashing
3 → 4노드~75% 키 이동~25% 키 이동 (1/N)
4 → 3노드 (장애)~75% 키 이동~25% 키 이동 (1/N)

가상 노드 (Virtual Nodes)

물리 노드 3개만으로는 해시 링 위 분포가 불균등하다. 각 물리 노드를 여러 가상 노드로 매핑하여 균등 분산:

가상 노드 수키 분산 편차노드 추가 시 이동 키
1 (가상 노드 없음)~50% 편차최대 50%
50~10% 편차~1/N
150 (선택)~5% 편차~1/N
500~2% 편차~1/N

가상 노드 150개는 균등 분산과 메모리(TreeMap 엔트리 수) 사이의 적절한 균형점이다.


6. 구현

아키텍처: 하이브리드 (Consistent Hashing + 용도별 격리)

After: Consistent Hashing + 용도별 격리

데이터 라우팅 전략

데이터 라우팅 전략: 키 유형별 목적지

데이터라우팅 방식이유
자동완성 KV (prefix:*)Consistent Hashing키 수 최다, 분산 효과 극대화
게시글/검색 캐시 (post:{id}, search:*)Consistent Hashing키 기반 분산 자연스러움
조회수 (post:views:{id})Consistent Hashing키 기반 분산, flush 시 각 노드에서 SCAN
버전 포인터 (prefix:current_version)고정 노드 (Ring 첫 번째)전역 메타데이터, 1개만 존재
토큰 블랙리스트 (blacklist:*)전용 인스턴스 (격리)보안 크리티컬, 샤딩 불가

토큰 블랙리스트 샤딩 문제: JWT 토큰을 Consistent Hashing으로 분산하면, 노드 장애 시 해당 샤드의 블랙리스트를 조회할 수 없다. 보수적 정책(장애 시 모든 토큰 거부)과 결합하면 1/3 확률로 전체 인증 차단이 발생한다. 따라서 블랙리스트는 샤딩하지 않는 것이 안전하다.

ConsistentHashRouter 구현

@Component
public class ConsistentHashRouter {
private final ConcurrentSkipListMap<Long, StringRedisTemplate> ring = new ConcurrentSkipListMap<>();
private final List<StringRedisTemplate> nodes;
private static final int VIRTUAL_NODES = 150;
public ConsistentHashRouter(List<StringRedisTemplate> shardRedisTemplates) {
this.nodes = shardRedisTemplates;
for (int i = 0; i < nodes.size(); i++) {
for (int v = 0; v < VIRTUAL_NODES; v++) {
long hash = hash("node-" + i + "-vnode-" + v);
ring.put(hash, nodes.get(i));
}
}
}
/** 키에 해당하는 Redis 노드 결정 (ConcurrentSkipListMap은 thread-safe) */
public StringRedisTemplate getNode(String key) {
long hash = hash(key);
Map.Entry<Long, StringRedisTemplate> entry = ring.ceilingEntry(hash);
if (entry == null) {
entry = ring.firstEntry(); // 링 순환
}
return entry.getValue();
}
/** 모든 노드 반환 (SCAN 등 전체 조회 시) */
public List<StringRedisTemplate> getAllNodes() {
return Collections.unmodifiableList(nodes);
}
private long hash(String key) {
return Hashing.murmur3_128()
.hashString(key, StandardCharsets.UTF_8)
.asLong() & 0x7FFFFFFFFFFFFFFFL;
}
}

RedisShardConfig: 다중 LettuceConnectionFactory 구성

@Configuration
public class RedisShardConfig {
@Bean
@Primary
StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
// 기존 단일 인스턴스 유지 — 토큰 블랙리스트 등 비샤딩 용도
return new StringRedisTemplate(connectionFactory);
}
@Bean
List<StringRedisTemplate> shardRedisTemplates(
@Value("${redis.shards[0].host}") String host1,
@Value("${redis.shards[0].port}") int port1,
@Value("${redis.shards[1].host}") String host2,
@Value("${redis.shards[1].port}") int port2,
@Value("${redis.shards[2].host}") String host3,
@Value("${redis.shards[2].port}") int port3,
@Value("${redis.password:}") String password) {
return List.of(
createTemplate(host1, port1, password),
createTemplate(host2, port2, password),
createTemplate(host3, port3, password)
);
}
private StringRedisTemplate createTemplate(String host, int port, String password) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
if (!password.isBlank()) {
config.setPassword(password);
}
LettuceConnectionFactory factory = new LettuceConnectionFactory(config);
factory.afterPropertiesSet();
return new StringRedisTemplate(factory);
}
}

기존 코드 변경 영향

파일BeforeAfter
RedisAutocompleteServiceStringRedisTemplate redis (단일)ConsistentHashRouter router
TieredCacheServiceStringRedisTemplate redis (단일)ConsistentHashRouter router
ViewCountServiceStringRedisTemplate redisTemplate (단일)ConsistentHashRouter router
RedisTokenBlacklistStringRedisTemplate redisTemplate (단일)변경 없음 (기존 단일 Redis 유지)

변경 패턴은 모두 동일하다:

// Before (단일 Redis)
redis.opsForValue().get(key);
// After (Consistent Hashing)
router.getNode(key).opsForValue().get(key);

7. KEYS → SCAN 전환

// Before
Set<String> keys = redisTemplate.keys(KEY_PREFIX + "*"); // KEYS — O(N) 블로킹
// After
ScanOptions options = ScanOptions.scanOptions().match(KEY_PREFIX + "*").count(1000).build();
try (Cursor<String> cursor = redisTemplate.scan(options)) { // SCAN — 커서 기반, 비블로킹
while (cursor.hasNext()) { ... }
}
Before (KEYS)After (SCAN)
SLOWLOG (10ms 임계값)KEYS post:views:* 34.6ms 기록기록 없음 (10ms 미만)
전체 테스트117개 통과117개 통과

SLOWLOG RESET 후 90초 대기(flushToDB 3회 실행) → SLOWLOG GET 10 결과 비어 있음. KEYS 블로킹 안티패턴이 완전히 제거됨.


8. 3노드 분리 + ConsistentHashRouter 배포

배포 결과: 3개 Redis 샤드 컨테이너 healthy + App 정상 기동 확인

wiki-app-prod Up 3 minutes
wiki-redis-shard3-prod Up 14 minutes (healthy)
wiki-redis-shard2-prod Up 14 minutes (healthy)
wiki-redis-shard1-prod Up 14 minutes (healthy)

분산 균등성 테스트

시점shard-1shard-2shard-3기존 redis
배포 직후 (배치 빌드 전)0001,050
배치 빌드 후 (1회차)3693043471,022
배치 빌드 후 (2회차)7516076821,022

샤드 합계: 2,040개 (751 + 607 + 682). 편차: 최소 607 / 최대 751 = 19.2%로, 3노드 × 150 가상 노드에서 Consistent Hashing이 정상 동작함을 확인했다.

자동완성 정상 동작:

curl /posts/autocomplete?prefix=science → ["science"]
curl /posts/autocomplete?prefix=삼성 → ["삼성전자"]

After redis-benchmark

명령Before (단일)After (shard-1)차이
GET104,602 req/s, P50 0.231ms104,166 req/s, P50 0.231ms동등
SET88,339 req/s, P50 0.231ms71,428 req/s, P50 0.239ms-19% (컨테이너 리소스 분산)

샤딩으로 인한 성능 저하 없음. GET P50 동일(0.231ms). SET 처리량 감소는 서버에 Redis 컨테이너 4개가 동시 실행되면서 CPU/메모리를 분산하기 때문이며, 개별 샤드의 실사용 SET 부하는 1/3이므로 문제없음.


9. 노드 장애 시 fallback

docker stop wiki-redis-shard2-prod
→ curl /posts/autocomplete?prefix=science
→ 정상 응답: Lucene PrefixQuery fallback으로 10건 반환
→ shard-2에 있던 키는 캐시 미스 → Lucene이 대신 응답
→ docker start wiki-redis-shard2-prod → 복구 확인

10. 핫스팟 문제와 Shard Manager

핫스팟 문제

샤딩은 데이터 분산에는 효과적이지만 부하 분산에는 부족하다:

  • 1글자 prefix(“위”, “대”, “한”)는 3글자 prefix보다 수십 배 많이 조회
  • 이벤트/시즌에 따라 특정 prefix가 폭발적으로 인기 상승
  • → 해당 키가 있는 샤드가 핫스팟이 되어 성능 병목

동적 복제 (Dynamic Replication)

Meta 사의 Shard Manager에서 영감을 받은 해결책:

#책임설명
1데이터 분산Consistent Hashing으로 샤드 간 데이터 분산
2동적 복제 관리각 샤드의 부하를 관찰하여 읽기 전용 복제본 동적 추가/제거
3최소 노드 보장고가용성을 위해 모든 샤드에 최소한의 건강한 노드 유지

핵심 포인트: Consistent Hashing으로 데이터 분산 문제를 해결하고, 동적 복제로 부하 분산 문제를 해결한다. 두 문제는 별개이며, 샤딩만으로는 핫스팟을 막을 수 없다.


11. 부하 테스트: k6 100 VU, 20분

k6 smoke 테스트 (5 VU, 2분)

시나리오평균P95
전체105ms490ms
자동완성35ms61ms
최신 게시글37ms62ms
상세 조회25ms44ms
에러율0.00%

k6 LOAD 테스트: 서버 분산 배치 (100 VU, 20분, 최종)

shard-2, shard-3을 서버 1 → 서버 2로 이동 + Kafka/Debezium 메모리 축소 후 재측정.

시나리오평균P95
전체42.81ms190.44ms
검색 (전체)29.18ms100.85ms
검색 (희귀 10%)22.79ms89.72ms
검색 (중빈도 60%)19.14ms88.82ms
검색 (고빈도 30%)51.28ms200.45ms
자동완성11.68ms68.44ms
최신 게시글18.61ms80.39ms
상세 조회25.80ms92.38ms
쓰기 (생성+좋아요)25.94ms94.41ms
에러율0.00%
총 요청 수41,912

비교: 단일 Redis → 서버 집중 → 서버 분산

지표CDC 이후 (단일 Redis)샤딩 (서버 1 집중)샤딩 (서버 분산)
평균35.6ms47.6ms (+34%)42.8ms (+20%)
P95138ms197ms190ms
P99294ms473ms398ms
자동완성10.4ms14.8ms11.7ms
검색25.7ms35.3ms29.2ms
에러율0%0%0%
처리량 피크~58 req/s~57 req/s~58 req/s

서버 분산 효과: shard-2, shard-3을 서버 2로 옮기면서 CPU/메모리 경합 완화. 자동완성 14.8ms → 11.7ms로 CDC 이후 수준(10.4ms)에 근접 회복. 잔여 +7ms(35.6→42.8)는 분산 시스템의 본질적 비용, 곧 해시 라우팅, 다중 커넥션 풀, 서버 간 네트워크 왕복 때문이다.

k6 LOAD: 서버 분산 배치 후 터미널
k6 LOAD: Overview (42.8ms / P95 190ms / 에러율 0%)

“샤딩하면 빨라져야 하는 거 아닌가?”

아니다. 샤딩/멀티 인스턴스의 목적은 용량 확장, 워크로드 격리, 장애 격리이지 레이턴시 감소가 아니다. 같은 서버에서 인스턴스를 분리하면 커넥션 풀 관리, CPU 경합으로 오히려 오버헤드가 추가된다.

Redis Cluster 공식 스펙에서는 “N개 마스터 노드가 있으면 단일 인스턴스 대비 N배 성능을 기대할 수 있고, 레이턴시도 단일 노드와 동일”이라고 명시한다. 하지만 이는 별도 서버에 분산 배치한 경우다.

현업 사례:

  • GitLab: Redis를 워크로드별로 분리하면서 커넥션 오버헤드 증가를 수용. 목적은 “Sidekiq 폴링이 캐시 읽기를 간섭하는 noisy-neighbor 제거”
  • Shopify: 단일 Redis 공유로 전체 다운(“Redismageddon”) → Pod별 완전 격리 전환

+7ms의 trade-off 평가:

  • 42.8ms는 Jakob Nielsen의 “즉각적” 임계값(100ms) 미만이라 사용자가 인지할 수 없다
  • P95 190ms는 업계 표준(웹 API P95 < 200ms) 이내
  • 에러율 0%, 처리량 동등(58 req/s)
  • 얻은 것: KEYS 블로킹 제거, volatile-lru 보안 격리, 워크로드 분리, 노드 장애 시 1/3 부분 영향

12. Grafana 대시보드

Redis Shards (서버 분산 배치)

  • shard-1 (서버 1): 키 815개
  • shard-2 (서버 2): 키 37개 → 다음 배치 빌드 시 정상 분배
  • shard-3 (서버 2): 키 36개 → 다음 배치 빌드 시 정상 분배

Redis Shards: 서버 분산 배치

기존 Redis (블랙리스트 전용)

  • 메모리: 0.597%, L2 히트율: 66.6%, Eviction: 0

기존 Redis

Spring Boot

  • HTTP 평균: 양쪽 인스턴스 ~50ms 이하
  • App CPU: 서버 1 피크 ~80%, 서버 2 피크 ~40% (분산 효과)

Spring Boot

Tiered Cache + Lettuce

  • L2 히트율: 74%, Origin: 24%, L1: 1%
  • Lettuce P95: 안정 구간 ~5ms 이하

Tiered Cache + Lettuce

Debezium CDC

  • Connected: CONNECTED, Erroneous Events: 0
  • CDC Lag: ~80ms 이하로, Kafka/Debezium 메모리 축소 후에도 정상

Debezium CDC

MySQL

  • Primary QPS: 피크 ~400 ops/s
  • InnoDB 히트율: Primary 100%, Replica 99.9%

MySQL

Host

  • 서버 1 메모리: 55.0% (shard 2개 제거 효과)
  • 서버 2 메모리: 39.2% (shard 2개 추가 + Kafka/Debezium 축소)

Host
Containers
Nginx
HikariCP + System


13. 성능 종합 비교

지표Before (단일 Redis)After (SCAN + 3노드 분리)
flush 중 KEYS 블로킹34.6ms (SLOWLOG)0ms (SCAN 비블로킹)
배치 중 GET 최악15.5ms (SLOWLOG)워크로드 분리로 해소
블랙리스트 eviction 위험있음 (volatile-lru 대상)없음 (전용 인스턴스)
Redis 가용 메모리128MB384MB (3 × 128MB)
배치 쓰기 영향 범위전체 워크로드자동완성 노드만
k6 100 VU 에러율0%0%
k6 100 VU 평균35.6ms42.8ms (+7ms, 분산 비용)
k6 100 VU P95138ms190ms

핵심 개선: 레이턴시 감소보다 워크로드 격리(배치↔실시간 분리), 안티패턴 제거(KEYS→SCAN), 보안 격리(블랙리스트 전용 인스턴스)에 있습니다. Consistent Hashing은 분리된 노드의 라우팅 계층으로서, 현재 구조에서 키 이동을 최소화하면서 용도별 분리를 유지할 수 있게 만든 선택이었습니다.


프로필 사진
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글

댓글 수정/삭제는 GitHub Discussions에서 가능합니다.