이 글은 Velog에서 이전한 글입니다. Velog 원문 보기
https://docs.tosspayments.com/blog/hashing-and-encryption-difference
Redis Key 네임스페이스 설계 전략
서비스를 운영하면서 Redis를 도입해 응답 데이터를 캐싱해 활용하고 있습니다.
가장 중요했던 것은 사용자별 데이터 저장과 무효화 전략이었습니다.
단순히 set(key, value) 구조로 데이터를 저장한다면 원하는 바처럼 사용자별 데이터 저장과 무효화 전략을 적용하는 것이 어렵습니다.
또한, 사용자의 외부 서비스 로그인 정보도 캐시로 저장해서 활용하고 있기 때문에 목적이 섞이기 시작하면서 혼란이 생길 수 있습니다.
- 해당 키는 어떤 용도였지?
- 이 데이터를 지우면 다른 기능에 영향은 없을까?
- 특정 기능과 관련괸 키만 지우고 싶은데, 어떻게 한 번에 지울까?
이런 요구 사항을 한 번에 해결하기 위해서는 Redis 키를 기능별로 명확하게 '구분'할 수 있는 구조가 필요합니다.
이때 필요한 것이 Redis Key 네임스페이스 설계 전략입니다.
왜 Redis 키를 구분해서 사용해야 할까?
Redis는 내부적으로 모든 key를 평평한 공간(flat namespace)에 저장합니다.
즉, 기능이 완전히 다른 값들도 같은 공간에 들어가게 됩니다.
이때 접두사(prefix)를 붙여서 Key를 논리적으로 그룹화하면 아래와 같은 장점이 있습니다:
- 기능 별로 분리: 인증, 주문, 통계, 캐시 등 기능 구분
- 충돌 방지: 같은 ID라도 용도별로 겹치지 않게
- 유지보수 용이: 어떤 key가 어디서 쓰이는지 파악 쉬움
- 일괄 삭제/검색 가능
- GUI에서 보기 편함: RedisInsight 같은 도구에서 필터링 가능
Key Namespace 구조
구조 예시
[도메인 또는 기능]:[세부 기능]:[식별자]
예:
user:profile:1234 -> 사용자 1234의 프로필 정보
나의 실사용 예시
student:{studentId}:summary
왜 [도메인 또는 기능]:[식별자]:[세부 기능] 구조로 사용했을까?
서비스 특정상 '사용자 서비스 탈퇴', '사용자 데이터 갱신' 이벤트 발생 시 데이터가 더이상 필요 없거나,
캐싱된 데이터가 최신화 된 데이터와 불일치 할 가능성이 존재하기 때문에 특정 학생의 모든 캐시 데이터를 삭제해야 합니다.
[도메인 또는 기능]:[식별자]:[세부 기능] 구조를 사용한다면 와일드카드()를 활용하여 `student:{studentId}:`로 사용자 캐시 일괄 삭제가 가능합니다.
와일드카드 삭제의 성능 이슈
예를 들어, 제가 총 삭제하고자 하는 데이터의 key가 3개가 존재한다고 하겠습니다.
- student:{studentId}:a
- student:{studentId}:b
- student:{studentId}:c
이때, 각각 key를 명시적 키 삭제 방식과 와일드카드로 삭제 방식은 성능 차이가 존재합니다
key 명시적 키 삭제 방식
redisTemplate.delete(List.of(...))
지정된 Key를 기준으로 바로 DEL 명령어 실행합니다.
전체 성능: O(M) (M은 삭제할 key 수)
와일드카드 삭제 방식
redisTemplate.keys("student:{studentId}:*") → delete(...)
우선 와일드카드에 해당하는 Key를 Redis에서 조회해야 하기 때문에
조회 단계가 추가 됩니다.
- 조회 성능: 전체 Key 스캔 필요 (O(N))
이후 조회가 끝나면 조회된 Key를 기준으로 DEL 명령어를 실행합니다.
- 삭제 성능: O(M) (M은 삭제할 key 수)
전체 성능: O(N) (최악의 경우 전체 키 탐색)
또한, 와일드카드 삭제 방식은 성능 이슈가 생길 수 있어 운영 환경에서 비추천됩니다.
🔍 왜 성능 이슈가 생기나?
와일드카드를 이용한 키 조회는 내부적으로 SCAN이 아니라 KEYS 명령어로 동작하는 경우가 많습니다.
Spring Data Redis에서는 기본적으로 KEYS를 사용
🔍 Redis 키 조회 방식: KEYS vs SCAN
KEYS 명령어
- 문법:
KEYS pattern - 예시:
KEYS user:* - 전체 키 공간을 한 번에 탐색하여 패턴에 맞는 키들을 모두 반환
특징
- 시간복잡도: O(N) → 전체 키 수만큼 순회
- 결과를 즉시 반환 → 속도는 빠르지만...
단점
- 블로킹 명령어: 실행되는 동안 Redis가 다른 작업을 처리하지 못함
- 운영 환경에선 사용 비추천: 대규모 서비스에서 심각한 성능 저하 발생 가능
SCAN 명령어
- 문법:
SCAN cursor [MATCH pattern] [COUNT count] - 예시:
SCAN 0 MATCH user:* COUNT 100
특징
- 비블로킹(non-blocking): 실행 중에도 Redis는 다른 작업 가능
- 커서 기반 반복 조회: 한 번에 전체 조회가 아닌, 여러 번에 걸쳐 키를 순회
- 시간복잡도는 여전히 O(N) 이지만, 작업이 분산되어 안정적
⚠️ 주의
- 중복 키가 나올 수 있음 → 자체 deduplication 필요
- 모든 키를 한 번에 가져오지 않기 때문에 결과 신뢰도는 낮음
- 데이터 변경 중이라면 누락 가능성 있음
KEYS vs SCAN 요약 비교
| 구분 | KEYS | SCAN |
|---|---|---|
| 방식 | 전체 키 즉시 반환 | 커서 기반 점진적 순회 |
| 성능 | O(N), 블로킹 | O(N), 논블로킹 |
| 안정성 | ❌ 낮음 (운영 비추천) | ✅ 높음 (운영 사용 가능) |
| 결과 일관성 | ✅ 완전 | ⚠️ 불완전 (누락 가능) |
| 사용 권장 | 테스트/디버깅 | 운영 환경 조회용 |
운영 환경에서는 반드시 SCAN을 사용해야 합니다.KEYS는 일시적인 디버깅, 로컬 테스트 외에는 사용하면 안 됩니다.
Redis Hash란 무엇이며, 왜 사용할까?
Redis 여러 자료 구조 중 Hash는 자주 사용되는 구조 중 하나로,
도메인 객체의 속성들을 한 번에 저장하고 조회하는 데 유용합니다.
Redis Hash란?
Redis Hash는 한 개의 키(Key)에 여러 개의 필드(field)와 값(value)를 저장하는 자료구조입니다.
즉, 하나의 객체를 구성하는 여러 속성을 저장할 때 유용합니다.
예시
HSET user:123 name "sangmin" age "25" role "admin"
이렇게 하면 user:123이라는 Hash Key에 name, age, role이라는 필드가 저장됩니다.
왜 Redis Hash를 사용할까?
- 도메인 객체 캐싱에 적합
- 사용자, 학생, 상품 등 여러 속성을 가진 객체를 구조적으로 표현 가능하고, JSON처럼 다루기 좋습니다.
HGET user :123 name-> "Alice"HGETALL user:123-> 전체 필드/값 반환
- 사용자, 학생, 상품 등 여러 속성을 가진 객체를 구조적으로 표현 가능하고, JSON처럼 다루기 좋습니다.
- Keyspace 절약
- 같은 정보를 각각의 key로 저장하는 것보다 key 수를 줄일 수 있어 Redis key 개수가 적어집니다.
관리 편의성과 메모리 효율이 높아집니다.- 일반 키 방식:
user:123:name, user:123:age, user:123: role-> 키 3개 - Hash 방식:
user:123키 1개 + 다중 필드
- 일반 키 방식:
- 같은 정보를 각각의 key로 저장하는 것보다 key 수를 줄일 수 있어 Redis key 개수가 적어집니다.
- Partial Update 기능
- 특정 필드만 업데이트 가능하여 불필요한 전체 덮어쓰기를 방지할 수 있습니다.
HSET user:123 role "user"-> name, age는 그대로 유지
- 특정 필드만 업데이트 가능하여 불필요한 전체 덮어쓰기를 방지할 수 있습니다.
- 네임스페이스 분리와 함께 사용 시 캐시 설계 용이
user:{userId}:profile구조로 키를 설계하면, 와일드카드 기반 삭제나 필터링이 쉬워지고 RedisInsight 같은 GUI에서도 보기 편합니다.
❗ Redis Hash의 한계
- 필드 단위 TTL 설정 불가
Redis는 Hash 전체에 대해서만 TTL 설정이 가능하고, 개별 필드에는 만료 시간을 설정할 수 없습니다. - HGETALL의 비용 문제
모든 필드를 반환하기 때문에 필드 수가 많을 경우 네트워크 비용 증가
-> 운영 환경에서는 필요한 필드만 조회하는 HGET, HMGET 등을 사용하는 것이 좋습니다.
✅ 비교: HGETALL vs HGET
Redis 구조 예시
key = user:123:profile
{
"name": "Alice",
"email": "alice@example.com",
"age": "30",
"bio": "Redis 전문가입니다."
}
HGETALL user:123:profile
- 모든 필드를 조회하여 반환
- 네트워크 전송 데이터 양: 전체 필드 개수 × 평균 필드 크기
- 필드 수가 많을수록 네트워크 비용 및 파싱 비용 증가
HGET user:123:profile email
- 특정 필드 1개만 조회
- 네트워크 전송량: email 하나
- 다른 필드는 Redis → 클라이언트로 전송되지 않음
- 네트워크 부담 최소화, 빠른 응답
📌 운영 환경에서는
- 사용자가 1000명이고 각 사용자 Hash에 10개 필드가 있다면
- → HGETALL은 1000 × 10 = 10,000 필드 전송
- → HGET은 필요한 필드 1개만 1000번 조회 (총 1000개 전송)
→ 결과적으로 10배 이상의 네트워크 비용 절감 가능
✅ 대안은 없을까?
일반 키 방식
- 각 필드를 Redis의 독립적인 Key로 저장하는 방식
예:
SET user:123:name "Alice" EX 3600SET user:123:age "25" EX 3600
장점:
- 필드 단위 TTL 설정 가능
- 캐시 정책을 더 세밀하게 구성할 수 있음
단점:
- Redis에 저장되는 Key 개수가 많아져서 관리 복잡도와 메모리 부담이 증가
✅ 타협 전략: 혼합 전략
- 중요한 필드만 별도 key로, 나머지는 Hash로 묶어서 관리
예:
SET user:123:sessionToken "abc123" EX 600HSET user:123 name "Alice" age "25"
TTL 제어가 필요한 필드만 분리해서 저장하고,
나머지는 Hash로 묶으면 효율성과 TTL 제어를 동시에 충족할 수 있습니다.
✅ Redis Hash vs 해시 암호화 함수 vs 해시 자료구조 – 이름만 같고, 완전히 다른 개념입니다!
많은 개발자들이 헷갈리는 개념 중 하나는 Redis Hash 자료구조와 해시(Hash) 암호화 함수입니다.
이 둘은 이름은 같지만, 기능과 목적이 전혀 다릅니다.
✅ 해시 암호화 함수란?
- 입력값(예: 비밀번호)을 고정된 길이의 문자열로 변환하는 단방향 함수
- 복호화 불가능 -> 주로 비밀번호 저장, 무결성 검증에 사용
- 대표 함수: SHA-256, bcrypt, PBKDF2, scrypt 등
예:
SHA-256("mypassword")
→ "5f4dcc3b5aa765d61d8327deb882cf99"
✅ 알고리즘에서의 해시 자료구조
개념
- 키(key)를 해시 함수로 변환하여 내부 버킷(bucket)에 저장하는 자료구조
- 빠른 탐색/삽입/삭제를 위해 사용 (평균 시간복잡도: O(1))
대표 예시
- Java:
HashMap,HashSet - Python:
dict,set
사용 예
Map<String, Integer> map = new HashMap<>();
map.put("apple", 3);
map.get("apple"); // 3 반환
✅ 해시(Hash)의 개념별 차이점 정리
| 구분 | Redis Hash | 해시 암호화 함수 | 해시 자료구조 (알고리즘) |
|---|---|---|---|
| 개념 | 하나의 Key에 여러 필드-값 쌍을 저장하는 자료구조 | 입력값을 고정된 길이의 암호화된 문자열로 변환하는 함수 | Key → Value를 빠르게 매핑하기 위한 구조 |
| 목적 | 도메인 객체 캐싱 등 구조적 데이터 저장 | 단방향 암호화, 무결성 검증, 비밀번호 보안 | 빠른 데이터 탐색, 중복 제거, 맵/딕셔너리 구현 |
| 주요 기능 | 필드 단위 저장/조회/수정 | 입력 → 해시값 생성, 복호화 불가 | 해시 함수 기반 인덱싱, 충돌 처리 |
| 복호화 가능 여부 | ✅ 가능 (값 그대로 저장) | ❌ 불가능 (단방향) | ✅ 가능 (원본 데이터를 저장함) |
| 보안 기능 | ❌ 없음 | ✅ 있음 | ❌ 없음 (보안 목적 아님) |
| 사용 위치 | Redis 내부 자료구조 | 서버 애플리케이션 레이어 (ex. bcrypt, SHA-256) | 프로그래밍 언어 내부 자료구조 (Java Map, Python dict 등) |
| 사용 예시 | HSET user:1 name "Alice" |
bcrypt("password123") → $2a$... |
map.put("key", "value") |
면접 놓쳤던 질문 복습 정리
- ✅ Hash 자료구조란?
- Redis Hash는 하나의 Key에 여러 Field-Value 쌍을 저장하는 자료구조입니다.
- 예:
student:123키에summary,credits,status등의 필드 저장 가능
- ✅ HGETALL의 문제점
- 모든 필드를 한 번에 반환 → 필드 수가 많거나 데이터 크기가 크면 네트워크 I/O 비용 증가
- 운영 환경에서는 HGET / HMGET으로 필요한 필드만 조회 권장
- ✅ TTL 제약을 극복하기 위한 대안 설계
- Hash는 전체에만 TTL 적용 가능
- → TTL이 필요한 필드만 별도 Key로 분리 저장하는 하이브리드 설계 권장
- ✅ 와일드카드 기반 삭제의 성능 문제
KEYS또는SCAN MATCH를 통해 먼저 키 조회 (O(N)) → 이후DEL실행- → 조회 단계 추가로 인해 성능 저하 발생 가능
- → 운영 환경에서는 정확한 Key 명시 방식 추천
- ✅ Hash 자료구조에 TTL 적용 방식
- 전체 Hash Key에는 TTL 설정 가능 (
EXPIRE) - Hash 내부 필드에는 TTL 적용 불가
- 전체 Hash Key에는 TTL 설정 가능 (
- ✅ key vs hash-field TTL 차이
구분 TTL 적용 가능 여부 일반 Key ( student:123:summary)✅ 가능 Hash Key ( student:123)✅ 가능 (전체에 적용) Hash Field ( summaryinstudent:123)❌ 불가능
출처
kakao Tech, 'Redis의 SCAN은 어떻게 동작하는가'
Redis 공식 문서: hahses | Docs
'Language·CS > Backend Interview' 카테고리의 다른 글
| DB Replication 구조와 Read/Write 분리 전략의 장단점 (0) | 2026.08.02 |
|---|---|
| 중복 요청, 어떻게 ‘한 번만’ 처리할 것인가 – 멱등성과 서버 설계의 핵심 원리 (0) | 2026.08.02 |
| 무중단 배포 전략(Rolling, Blue-Green, Canary ): 트래픽 제어부터 롤백까지 (0) | 2026.08.02 |
| JPA - Cascade와 orphanRemoval, 진짜 이해하고 쓰고 있나요? (0) | 2026.08.02 |