Skip to main content
Summary

키 이름은 Redis의 스키마다

Redis에서 데이터를 저장하는 일 자체는 간단합니다. SET user:1001:profile ...처럼 키와 값을 넣으면 됩니다. 그러나 애플리케이션이 커지면 키는 수십 개가 아니라 수만, 수백만 개로 늘어납니다. 이때 키 이름이 흐트러지면 어떤 데이터가 어디에 저장되어 있는지 알기 어려워지고, 잘못 삭제하거나 만료 시간을 빠뜨리는 사고가 생깁니다.

관계형 데이터베이스에서는 테이블명, 컬럼명, 인덱스가 데이터 구조를 설명합니다. Redis에서는 그 역할의 상당 부분을 키 이름이 맡습니다. Redis 자체는 별도의 네임스페이스를 강제하지 않습니다. 키는 문자열이며, 어떤 이름을 붙일지는 개발자가 정해야 합니다. 그래서 Redis 키 설계는 단순한 취향 문제가 아니라 유지보수성과 운영 안정성을 결정하는 설계 작업입니다.1

좋은 키는 다음 질문에 답할 수 있어야 합니다.

  • 이 키가 어느 도메인의 데이터인지 알 수 있는가?

  • 어떤 식별자에 속한 데이터인지 알 수 있는가?

  • 임시 데이터인지, 오래 유지해야 하는 데이터인지 추측할 수 있는가?

  • 비슷한 키를 안전하게 검색하거나 삭제할 수 있는가?

예를 들어 profile이라는 키는 좋지 않습니다. 누구의 프로필인지 알 수 없고, 다른 기능에서도 같은 이름을 쓸 수 있습니다. 반면 user:1001:profile은 훨씬 낫습니다. user 도메인, 1001번 사용자, profile 데이터라는 의미가 순서대로 드러납니다.

키 설계와 데이터 생명주기AI 생성 이미지

콜론 기반 네이밍으로 범위를 표현하기

Redis에서는 콜론(:)으로 키의 계층을 나누는 방식이 널리 쓰입니다. Redis가 콜론을 특별한 문법으로 처리하는 것은 아닙니다. 사람이 읽고 도구로 필터링하기 쉬운 관례입니다.

기본 패턴은 다음처럼 잡을 수 있습니다.

객체종류:식별자:속성

예시는 다음과 같습니다.

user:1001:profile
user:1001:session
post:932:view-count
ranking:daily:2026-05-30
auth:email-code:user:1001

왼쪽에는 큰 범위, 오른쪽에는 구체적인 목적을 둡니다. 이렇게 하면 특정 사용자의 Redis 데이터를 찾거나, 일별 랭킹 키를 탐색할 때 패턴을 잡기 쉽습니다. 예를 들어 user:1001:*는 1001번 사용자와 관련된 키를, ranking:daily:*는 일별 랭킹 키를 찾을 때 사용할 수 있는 형태입니다. 다만 운영 환경에서 실제로 키를 찾는 방법은 뒤에서 살펴볼 SCAN을 사용해야 합니다.

공유 Redis를 여러 서비스가 함께 쓴다면 서비스 이름을 앞에 붙이는 것도 좋습니다.

shop:user:1001:profile
shop:cart:1001
admin:user:1001:permission

이 방식은 충돌을 줄입니다. user:1001:profile이라는 이름은 여러 서비스에서 쓸 수 있지만, shop:user:1001:profile은 쇼핑 서비스의 사용자 프로필이라는 의미가 더 분명합니다.

다만 키를 지나치게 길게 만들 필요는 없습니다. Redis 키는 문자열이므로 긴 키도 사용할 수 있지만, 키 자체도 메모리를 차지합니다. 너무 짧은 키는 읽기 어렵고, 너무 긴 키는 메모리를 낭비합니다. 실무에서는 "로그를 보거나 장애 상황에서 봐도 의미가 바로 드러나는 정도"를 기준으로 삼으면 좋습니다.

피해야 할 키 이름도 있습니다.

u:1001:p
profile
user profile 1001
user:1001:profile:temporary:new:backup

u:1001:p는 짧지만 의미가 약합니다. profile은 충돌 위험이 큽니다. 공백이 들어간 키도 Redis에서는 가능하지만 터미널에서 다루기 불편합니다. 마지막 예시는 의미가 너무 많아 오히려 관리하기 어렵습니다.

데이터는 언제 사라져야 하는가

Redis에서 키는 기본적으로 영구적입니다. SET으로 저장한 키는 직접 삭제하지 않는 한 계속 남아 있습니다. 반대로 만료 시간을 가진 키는 일정 시간이 지나면 자동으로 사라집니다. Redis에서는 이런 키를 TTL이 있는 키라고 말합니다. TTL은 Time To Live의 약자로, 키가 앞으로 살아 있을 시간을 의미합니다.

만료 시간은 EXPIRE로 설정할 수 있습니다.

SET auth:email-code:user:1001 492810
EXPIRE auth:email-code:user:1001 300

위 키는 300초, 즉 5분 뒤 만료됩니다. 이메일 인증 코드, 비밀번호 재설정 토큰, 로그인 세션, 속도 제한 카운터처럼 시간이 지나면 의미가 없어지는 데이터에 적합합니다.

실무에서는 값을 저장하면서 만료 시간까지 한 번에 지정하는 방식도 자주 씁니다.

SET auth:email-code:user:1001 492810 EX 300

이렇게 하면 "값 저장"과 "만료 시간 설정"이 하나의 명령으로 처리됩니다. SET 다음에 EXPIRE를 따로 호출하는 방식도 가능하지만, 두 명령 사이에서 애플리케이션이 실패하면 만료 시간이 없는 키가 남을 수 있습니다. 처음 학습할 때는 EXPIRE의 의미를 이해하는 것이 중요하고, 실제 코드에서는 가능하면 한 흐름에서 TTL까지 함께 설정하는 습관을 들이는 것이 좋습니다.

남은 시간은 TTL로 확인합니다.

TTL auth:email-code:user:1001

TTL의 결과는 세 가지로 해석할 수 있습니다.2

  • 양수: 남은 수명입니다. 예를 들어 120이면 120초 뒤 만료됩니다.

  • -1: 키는 존재하지만 만료 시간이 없습니다.

  • -2: 키가 존재하지 않습니다.

이 차이는 디버깅에서 중요합니다. 애플리케이션에서 인증 코드가 계속 유효하다면 TTL을 확인해야 합니다. 결과가 -1이면 키는 있는데 만료 시간이 빠진 상태입니다. 반대로 -2이면 이미 삭제되었거나 처음부터 저장되지 않은 상태입니다.

만료 시간을 제거할 때는 PERSIST를 사용합니다.

PERSIST auth:email-code:user:1001
TTL auth:email-code:user:1001

이제 TTL은 -1을 반환합니다. 즉, 키는 더 이상 자동으로 만료되지 않습니다. PERSIST는 임시 키를 영구 키로 바꿔야 할 때 사용할 수 있지만, 운영 코드에서 자주 등장한다면 설계를 다시 점검해야 합니다. 원래 사라져야 할 데이터가 계속 남는 원인이 될 수 있기 때문입니다.

참고로 Redis의 만료는 사용자가 보기에는 "시간이 지나면 더 이상 조회되지 않는다"로 이해하면 충분합니다. 내부적으로 언제 메모리에서 정리되는지는 Redis가 관리합니다. 애플리케이션 코드는 만료된 키를 계속 사용할 수 있다고 가정하면 안 됩니다.

갱신이 TTL을 바꿀 수 있다

Redis에서 생명주기 버그는 보통 "값은 맞게 저장했는데 만료 정책이 틀어진 경우"에 생깁니다. 특히 SET은 기존 값을 새 값으로 덮어쓰면서 기존 TTL을 제거할 수 있습니다.3

다음 흐름을 보겠습니다.

SET user:1001:profile "cached-profile" EX 600
TTL user:1001:profile

이 키는 10분 뒤 사라질 캐시입니다. 그런데 다른 코드가 프로필을 갱신하면서 이렇게 실행했다고 가정해 봅시다.

SET user:1001:profile "updated-profile"
TTL user:1001:profile

이때 TTL이 -1이 될 수 있습니다. 캐시였던 키가 영구 키가 된 것입니다. 이후 원본 데이터베이스의 프로필이 다시 바뀌어도 Redis에는 이전 값이 계속 남을 수 있습니다. 이것이 오래된 데이터, 즉 stale data 문제입니다.

캐시를 다시 저장하는 코드라면 TTL도 함께 지정해야 합니다.

SET user:1001:profile "updated-profile" EX 600

만약 기존 TTL을 그대로 유지한 채 값만 바꾸고 싶다면 Redis의 SET 옵션 중 KEEPTTL을 사용할 수도 있습니다.

SET user:1001:profile "updated-profile" KEEPTTL

중요한 것은 "값을 갱신한다"와 "수명을 어떻게 할지 정한다"를 항상 함께 생각하는 것입니다. 값은 최신인데 만료 정책이 빠져 있으면, Redis 안에서는 오히려 더 오래된 문제가 만들어질 수 있습니다.

반대로 INCR, HSET, LPUSH처럼 값을 제자리에서 수정하는 명령은 일반적으로 기존 TTL을 유지합니다. 예를 들어 로그인 실패 횟수를 세는 키에 TTL을 걸어 두고 INCR을 반복하면, 카운터 값은 증가하지만 이미 설정된 만료 시점은 유지됩니다.

고정된 1분 구간 동안 로그인 실패 횟수를 세고 싶다면 보통 첫 실패 때만 만료 시간을 설정합니다.

INCR login-fail:user:1001

이 명령의 결과가 1이라면 새로 만들어진 카운터라고 볼 수 있으므로 그때 TTL을 설정합니다.

EXPIRE login-fail:user:1001 60

이후 같은 키에 대해 INCR을 반복해도 만료 시점은 처음 설정한 시간에서 유지됩니다. 즉, "처음 실패한 시점부터 1분"을 하나의 구간으로 삼는 방식입니다.

반대로 매번 실패할 때마다 제한 시간을 다시 60초로 늘리고 싶다면 INCR만으로는 부족합니다. 그때는 매번 EXPIRE를 함께 호출해야 합니다.

INCR login-fail:user:1001
EXPIRE login-fail:user:1001 60

이 방식은 마지막 실패 시점을 기준으로 만료 시간이 다시 잡힙니다. 어느 방식이 더 맞는지는 서비스 정책에 따라 다릅니다. 중요한 것은 데이터 갱신 정책과 TTL 갱신 정책을 별도로, 그리고 명확하게 설계해야 한다는 점입니다.

실무에서는 다음 원칙을 권장합니다.

  • 캐시 키를 SET할 때는 TTL도 같은 흐름에서 함께 설정합니다.

  • TTL이 필요한 키는 코드 리뷰에서 TTL 또는 만료 옵션이 빠졌는지 확인합니다.

  • 영구 키와 임시 키의 접두어를 구분합니다. 예: cache:user:1001:profile, session:token:...

  • 같은 데이터를 여러 키에 나누어 저장했다면 삭제와 만료 시점을 함께 설계합니다.

KEYS는 편하지만 위험하다

키 이름을 잘 설계하면 특정 패턴의 키를 찾고 싶어집니다.

KEYS user:1001:*

학습 환경에서는 이 명령이 편합니다. 조건에 맞는 모든 키를 한 번에 보여 주기 때문입니다. 문제는 운영 환경입니다. KEYS는 매칭되는 키를 찾기 위해 Redis가 키 공간을 훑는 동안 서버를 오래 붙잡을 수 있습니다. 키가 적을 때는 티가 나지 않지만, 수백만 개의 키가 있는 Redis에서 실행하면 다른 요청의 응답 시간까지 나빠질 수 있습니다. 공식 문서에서도 일반 애플리케이션 코드에서 KEYS를 사용하지 말 것을 권장합니다.4

운영 환경에서 키를 탐색해야 한다면 SCAN을 사용합니다.

SCAN 0 MATCH user:1001:* COUNT 100

SCAN은 한 번에 모든 키를 반환하지 않습니다. 커서를 이용해 조금씩 순회합니다. 처음에는 커서 0으로 시작하고, Redis가 반환한 다음 커서를 다음 호출에 사용합니다. 반환된 커서가 다시 0이 되면 순회가 끝난 것입니다.

예시는 다음과 같은 흐름입니다.

SCAN 0 MATCH cache:user:* COUNT 100
SCAN 384 MATCH cache:user:* COUNT 100
SCAN 912 MATCH cache:user:* COUNT 100

여기서 COUNT 100은 "정확히 100개를 달라"는 뜻이 아닙니다. Redis에게 한 번에 이 정도 작업량으로 순회해 달라고 알려 주는 힌트에 가깝습니다. 결과가 100개보다 적을 수도 있고, 어떤 호출에서는 빈 배열이 나올 수도 있습니다. 그래도 커서가 0으로 돌아오기 전까지는 계속 진행해야 합니다.

SCAN도 완벽한 스냅샷을 제공하지는 않습니다. 순회 도중 키가 추가되거나 삭제될 수 있고, 같은 키가 두 번 나올 수도 있습니다. 따라서 SCAN 결과로 삭제나 갱신을 수행할 때는 같은 작업이 두 번 실행되어도 문제가 없도록 만들어야 합니다. 예를 들어 캐시 키를 지우는 작업은 같은 키를 두 번 삭제해도 큰 문제가 없습니다. 반면 결제 처리처럼 중복 실행되면 안 되는 로직에는 SCAN 결과를 그대로 연결하면 위험합니다.

정리하면 다음과 같습니다.

명령특징사용 위치
KEYS pattern조건에 맞는 키를 한 번에 검색로컬 실습, 작은 데이터의 디버깅
SCAN cursor MATCH pattern COUNT n커서 기반으로 조금씩 검색운영 환경의 점진적 탐색

삭제, 갱신, 만료가 섞일 때 생기는 문제

다음 상황을 생각해 봅시다. 쇼핑몰에서 상품 상세 정보를 Redis에 캐시합니다.

cache:product:42:detail

처음 조회할 때 데이터베이스에서 상품 정보를 읽고 Redis에 10분 동안 저장합니다.

SET cache:product:42:detail "product-json" EX 600

이후 관리자가 상품 가격을 바꿉니다. 이때 선택지는 보통 세 가지입니다.

첫째, Redis 키를 삭제합니다.

DEL cache:product:42:detail

다음 조회는 데이터베이스에서 최신 값을 읽어 다시 캐시합니다. 단순하고 안전한 방식입니다. 다만 삭제 직후 요청이 몰리면 여러 요청이 동시에 데이터베이스를 조회할 수 있습니다.

둘째, Redis 값을 새 값으로 갱신합니다.

SET cache:product:42:detail "new-product-json" EX 600

이 방식은 즉시 최신 캐시를 만들 수 있습니다. 하지만 SET만 하고 만료 옵션을 빠뜨리면 캐시가 영구 키가 됩니다. 이 실수가 오래된 데이터를 오래 남기는 대표적인 원인입니다.

셋째, TTL에 맡깁니다. "어차피 10분 뒤 사라지니 그냥 둔다"는 방식입니다. 구현은 쉽지만, 가격이나 재고처럼 사용자에게 즉시 반영되어야 하는 데이터에는 위험합니다. 10분 동안 사용자가 이전 가격이나 이전 재고를 볼 수 있기 때문입니다.

따라서 캐시 설계에서는 데이터마다 허용 가능한 오래됨의 시간을 정해야 합니다. 인기 게시글 순위는 1분 늦어도 괜찮을 수 있습니다. 인증 코드나 결제 상태는 그렇지 않습니다. TTL은 단순한 기술 설정이 아니라 서비스 정책입니다.

키 설계 체크리스트

Redis에 새 데이터를 저장하기 전에는 다음 항목을 확인해 봅시다.

  • 키 이름만 보고 도메인, 식별자, 목적을 알 수 있는가?

  • 콜론 기반 규칙이 기존 키와 일관되는가?

  • 영구 키인지 임시 키인지 결정했는가?

  • 임시 키라면 TTL을 어디에서 설정하는가?

  • SET으로 갱신할 때 TTL이 사라지지 않는가?

  • 운영 코드에서 KEYS를 사용하고 있지 않은가?

  • 대량 탐색이 필요하다면 SCAN의 중복 결과와 빈 결과를 처리할 수 있는가?

  • 삭제, 갱신, 만료 중 어떤 방식으로 오래된 데이터를 제거할지 정했는가?

Redis는 빠릅니다. 하지만 빠르다는 이유로 아무 키나 저장하면 문제도 빠르게 커집니다. 키 이름은 데이터를 찾는 지도이고, TTL은 데이터가 사라지는 규칙입니다. 이 둘을 먼저 설계하면 이후에 배울 String, Hash, List, Set, Sorted Set도 훨씬 안정적으로 사용할 수 있습니다.

참고

1Redis Keys and values 문서

2Redis TTL 명령 문서

3Redis EXPIRE 명령 문서, Redis SET 명령 문서

4Redis Keys and values 문서

책 목록

Redis 입문과 실전 운영

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기