Redis GET과 SET 마스터하기: 기본 데이터 연산

Redis 데이터 관리의 기초를 다지는 종합 가이드로, `GET`과 `SET` 명령어를 통해 기본적인 문자열 저장 및 검색 방법을 배우고, 원자적 설정(`NX`/`XX`) 및 통합 키 만료(`EX`/`PX`) 같은 고급 옵션을 탐구합니다. 이러한 기본 명령어가 고성능 캐싱 계층을 구축하는 데 얼마나 중요한지 알아보세요.

Redis GET과 SET 마스터하기: 기본 데이터 연산

Redis의 GETSET은 너무 단순해 보입니다. 값을 쓰고, 값을 읽습니다. 실제 애플리케이션에서는 이 두 명령어가 로그인 세션, 기능 플래그, 속도 제한, 캐시 항목, 단기 잠금, 그리고 "데이터베이스에 다시 쿼리하지 마세요" 단축키 뒤에 자리 잡고 있습니다.

세부 사항이 중요한 이유는 Redis가 요청한 대로 정확히 동작하기 때문입니다. 실수로 키를 덮어쓰면, 의도했는지 묻지 않습니다. 캐시 데이터에 만료 시간을 설정하는 것을 잊으면, 데이터 소스보다 더 오래 살아 있을 수 있습니다. 누락된 키를 빈 문자열과 동일하게 처리하면, 애플리케이션이 잘못된 결정을 내릴 수 있습니다.

Redis 키-값 모델

명령어를 살펴보기 전에, Redis가 간단한 키-값 저장소 모델에서 작동한다는 점을 기억하는 것이 중요합니다. 모든 데이터(값)는 고유 식별자(키)를 사용하여 액세스됩니다. 키는 문자열이며, 값은 다양한 데이터 유형(문자열, 리스트, 셋, 해시 등)이 될 수 있습니다. SETGET은 주로 Redis에서 가장 기본적이고 자주 사용되는 문자열 데이터 유형을 다룹니다.

1. 데이터 설정: SET 명령어

SET 명령어는 키에 값을 할당하는 데 사용됩니다. 키에 이미 데이터가 있으면 SET 명령어는 기존 값을 덮어씁니다. 기본 구문은 간단합니다.

기본 구문 및 사용법

가장 간단한 형태는 키와 값만 필요합니다:

SET key value

예시: 사용자 표시 이름 저장:

127.0.0.1:6379> SET user:100:name "Alice Johnson"
OK

127.0.0.1:6379> GET user:100:name
"Alice Johnson"

고급 SET 옵션: NX, XX 및 만료

SET의 강점은 선택적 인수에서 비롯되며, 이를 통해 원자적 조건부 설정과 TTL(Time-To-Live) 관리를 할 수 있습니다. 이러한 옵션은 잠금 및 캐시를 올바르게 구현하는 데 필수적입니다.

조건부 설정: NXXX

이 옵션들은 설정 작업이 언제 발생하는지 제어하여, 실수로 덮어쓰는 것을 방지하거나 키가 존재하는 경우에만 덮어쓰기가 발생하도록 합니다.

  • NX (존재하지 않음): 키가 이미 존재하지 않는 경우에만 설정합니다. "생성 전용" 작업 및 간단한 잠금 패턴에 유용합니다.

    SET my_lock_key some_unique_value NX
    
  • XX (존재함): 키가 이미 존재하는 경우에만 설정합니다. 실수로 새 키를 생성하지 않고 알려진 키를 새로 고칠 때 유용합니다.

    SET session:token:456 new_value XX
    

B. 만료 시간 설정 (TTL)

메모리를 관리하고 시간 기반 캐싱을 구현하려면 SET 명령어 내에서 직접 만료 시간을 설정할 수 있습니다. 이는 키를 설정한 다음 별도로 EXPIRE를 호출하는 것보다 훨씬 효율적입니다.

  • EX seconds: 만료 시간을 단위로 설정합니다.
  • PX milliseconds: 만료 시간을 밀리초 단위로 설정합니다.
  • EXAT timestamp: 특정 Unix 타임스탬프(초)로 만료를 설정합니다.
  • PXAT timestamp: 특정 Unix 타임스탬프(밀리초)로 만료를 설정합니다.

예시: 1시간 후에 만료되는 키 설정:

127.0.0.1:6379> SET cache:product:500 "Product Details" EX 3600
OK

127.0.0.1:6379> TTL cache:product:500 
(integer) 3598 

캐시 항목에는 SET key value EX N 또는 PX N을 사용하세요. 쓰기와 만료를 하나의 명령어로 만들어, 애플리케이션이 캐시 키를 쓰고 EXPIRE를 호출하기 전에 충돌하는 일반적인 버그를 방지합니다.

옵션 결합

모든 옵션은 복잡한 원자적 작업을 위해 종종 결합될 수 있습니다:

# 키가 존재하지 않는 경우에만 설정하고, 60초 후에 만료되도록 함
SET my_config_setting "active" NX EX 60

2. 데이터 검색: GET 명령어

GET 명령어는 주어진 키와 연결된 문자열 값을 검색합니다. Redis가 수행하는 가장 빠른 작업 중 하나로, 종종 마이크로초 단위로 완료됩니다.

기본 구문 및 사용법

GET key

예시: 저장된 사용자 이름 검색

127.0.0.1:6379> GET user:100:name
"Alice Johnson"

존재하지 않는 키 처리

키가 존재하지 않으면 GET은 아무것도 찾을 수 없음을 나타내는 특별한 응답을 반환합니다:

127.0.0.1:6379> GET non_existent_key
(nil)

애플리케이션 코드에서 (nil)을 받는 것은 데이터가 누락되었음을 확인하는 표준 방법이며, 일반적으로 캐시 미스를 트리거하여 애플리케이션이 기본 소스(예: 데이터베이스)에서 데이터를 가져온 후 Redis에 다시 써야 합니다.

만료를 변경하면서 값 가져오기: GETEX

기본 GET 명령어는 값만 반환합니다. 남은 TTL을 반환하지 않습니다. TTL이 필요하면 별도의 명령어로 TTL key 또는 PTTL key를 사용하세요.

GETEX는 다릅니다: 값을 반환하고 동시에 키의 만료를 변경합니다. 이는 각 읽기가 세션 수명을 연장하는 슬라이딩 세션 동작에 유용합니다.

GETEX session:abc123 EX 1800

이것은 세션 값을 읽고 만료를 30분으로 재설정합니다. 일반 캐시 읽기에는 이 기능을 가볍게 사용하지 마세요. 모든 읽기가 키 메타데이터를 변경하는 쓰기와 유사한 작업이 되기 때문입니다.

3. 실제 응용: GETSET을 사용한 캐싱

GETSET의 기본 사용 사례는 간단한 캐시-사이드 패턴을 구현하는 것입니다.

애플리케이션 로직 단계:

  1. GET product:500을 시도합니다.
  2. Redis가 값을 반환하면 디코딩하여 반환합니다.
  3. Redis가 nil을 반환하면 기본 데이터베이스에서 제품을 가져옵니다.
  4. 직렬화된 결과를 SET product:500 <json> EX 300으로 저장합니다.
  5. 호출자에게 결과를 반환합니다.

이 패턴은 데이터베이스 부하를 줄일 수 있지만, 오래된 데이터 창을 생성합니다. 데이터베이스에서 제품이 변경되면 Redis는 TTL이 만료되거나 애플리케이션이 키를 무효화할 때까지 이전 값을 계속 제공할 수 있습니다. TTL은 저장하려는 트래픽 양뿐만 아니라 데이터가 얼마나 잘못되어도 괜찮은지에 따라 선택하세요.

혼란을 만들지 않고 키 이름 지정하기

Redis는 명명 규칙을 요구하지 않지만, 미래의 당신은 요구할 것입니다. user:100:name, product:500:summary, 또는 rate:user:100:login과 같은 읽기 쉬운 패턴은 redis-cli로 디버깅을 훨씬 쉽게 만듭니다.

키를 명확하고 합리적으로 짧게 유지하세요. 매우 큰 규모로 운영하고 키 오버헤드를 측정하지 않는 한, 키 이름을 u:100:n으로 지정하여 몇 바이트를 절약하는 것은 혼란을 초래할 가치가 거의 없습니다. 대부분의 팀에게 일관성이 극단적인 간결함보다 더 중요합니다.

키에 사용자 제공 값을 사용할 때는 주의하세요. 이메일 주소, URL 또는 테넌트 이름이 키의 일부가 되는 경우 먼저 정규화하세요. 그렇지 않으면 작은 형식 차이로 인해 중복 캐시 항목이 생성될 수 있습니다:

[email protected]
[email protected]
 [email protected]

이들은 모두 애플리케이션에는 동일한 사용자를 나타낼 수 있지만 Redis에는 다른 키를 나타냅니다.

덮어쓰기, 빈 값 및 Nil

SET은 기본적으로 덮어씁니다:

SET config:mode "safe"
SET config:mode "fast"
GET config:mode

최종 값은 "fast"입니다. 덮어쓰기가 위험한 경우 NX 또는 XX를 사용하세요.

또한 누락된 키와 빈 값을 구분하세요. Redis nil은 키가 누락되었음을 의미합니다. 빈 문자열은 실제 저장된 값입니다:

SET user:100:nickname ""
GET user:100:nickname

클라이언트 라이브러리는 이를 다르게 표현할 수 있습니다: null, None, nil, 빈 바이트 문자열 또는 빈 문자열. 추측하지 말고 클라이언트 동작을 확인하세요.

안전한 잠금 패턴, 주의 사항 포함

다음 패턴을 자주 보게 될 것입니다:

SET lock:invoice:123 "worker-7:1700000000" NX EX 30

이는 "이 잠금이 존재하지 않는 경우에만 생성하고, 30초 후에 만료시킨다"는 의미입니다. 만료는 선택 사항이 아닙니다. 만료가 없으면 충돌한 작업자가 잠금을 영원히 남길 수 있습니다.

단순한 단일 인스턴스 Redis 설정의 경우, 이 패턴은 위험이 낮은 조정에 충분한 경우가 많습니다. 장애, 클록 드리프트 및 여러 Redis 노드에 걸친 중요한 분산 잠금의 경우 잘 검토된 라이브러리를 사용하고 그 절충점을 이해하세요. 잠금 버그는 데이터 손상 버그가 될 수 있습니다.

redis-cli로 디버깅하기

GET 또는 SET 경로가 이상하게 동작할 때, 키를 직접 확인하세요:

redis-cli GET product:500
redis-cli TTL product:500
redis-cli TYPE product:500

TYPE은 유용합니다. GET은 문자열 값에서만 작동하기 때문입니다. 키가 해시, 리스트, 셋 또는 정렬된 셋을 보유하고 있으면 Redis는 잘못된 유형 오류를 반환합니다. 이는 일반적으로 애플리케이션의 두 부분이 동일한 키 이름을 다른 목적으로 사용하고 있음을 의미합니다.

개발 중에 여러 관련 키를 검사해야 하는 경우, 바쁜 프로덕션 서버에서는 KEYS보다 SCAN이 더 안전합니다:

redis-cli SCAN 0 MATCH 'product:500:*' COUNT 100

KEYS *는 키 공간을 스캔하는 동안 Redis를 차단할 수 있습니다. 작은 로컬 인스턴스에서는 괜찮습니다. 프로덕션에서는 나쁜 습관입니다.

실제 시스템에서 TTL 선택하기

TTL 선택은 Redis 트릭이 아닌 제품 및 운영 결정입니다. 사용자 프로필 캐시는 5분의 오래된 데이터를 허용할 수 있습니다. 권한 확인은 훨씬 더 짧은 TTL 또는 명시적 무효화가 필요할 수 있습니다. 기능 플래그는 위험한 롤아웃을 제어하는 경우 거의 즉각적인 업데이트가 필요할 수 있습니다.

세 가지 일반적인 패턴은 다음과 같습니다:

SET cache:product:500 "<json>" EX 300
SET session:abc123 "<json>" EX 1800
SET rate:user:100:login "1" EX 60 NX

제품 캐시는 약간 오래될 수 있습니다. 세션은 명확한 수명을 가지고 있습니다. 속도 제한 키는 NX를 사용하여 첫 번째 시도가 창을 만들고 이후 시도가 설계에 따라 관련 키를 증가시키거나 확인할 수 있도록 합니다.

명확한 무효화 경로가 없으면 "영원한 캐시" 키를 피하세요. 영원한 캐시는 결국 두 번째 데이터베이스가 되며, 일반적으로 실제 데이터베이스에 적용하는 운영 규율이 없습니다.

직렬화 세부 사항

Redis 문자열은 바이너리 안전합니다. JSON, MessagePack, 압축 데이터, 카운터 또는 일반 텍스트를 보유할 수 있습니다. 명령어는 상관하지 않습니다. 애플리케이션이 상관합니다.

JSON은 검사하기 쉽습니다:

SET product:500 "{\"id\":500,\"name\":\"Desk Lamp\"}" EX 300

바이너리 형식은 일부 애플리케이션에서 공간이나 CPU를 절약할 수 있지만, 터미널 디버깅을 더 어렵게 만듭니다. 압축은 큰 반복 데이터에 도움이 될 수 있지만 CPU 비용을 추가하고 너무 큰 객체를 캐싱하고 있다는 사실을 숨길 수 있습니다.

카운터의 경우, 여러 클라이언트가 동일한 키를 업데이트할 수 있다면 GET으로 읽고, 애플리케이션에서 더하고, SET으로 쓰지 마세요. 대신 Redis 원자적 카운터 명령어를 사용하세요:

INCR page:view:500
EXPIRE page:view:500 86400

만료가 있는 첫 번째 쓰기 카운터의 경우, 엄격한 동작이 필요하면 트랜잭션이나 작은 Lua 스크립트를 사용하세요. 그렇지 않으면 수용하는 경쟁 조건에 대해 명확히 하세요.

키 충돌 방지하기

동일한 Redis 데이터베이스를 사용하는 두 팀이 실수로 user:100과 같은 키를 재사용할 수 있습니다. 한 팀은 SET으로 JSON을 저장하고, 다른 팀은 HSET으로 필드를 저장합니다. 다음 GET은 잘못된 유형 오류를 반환하고 두 팀 모두 시간을 낭비합니다.

네임스페이스가 도움이 됩니다:

shop:prod:user:100:profile
shop:prod:session:abc123
billing:prod:invoice:9001

지나치게 긴 키가 필요하지는 않지만, 환경, 서비스 및 데이터 유형 간의 충돌을 피하기 위해 충분한 컨텍스트를 포함하세요. Redis를 애플리케이션 간에 공유하는 경우 명명 규칙은 인터페이스의 일부입니다.

GETSET을 사용하지 말아야 할 때

문자열은 시작점일 뿐, 전체 Redis 모델이 아닙니다. 더 큰 JSON 블롭 내에서 하나의 필드를 자주 업데이트하는 경우 해시가 더 깔끔할 수 있습니다:

HSET user:100 name "Alice Johnson" email "[email protected]"
HGET user:100 email

정렬된 이벤트가 필요하면 스트림이나 리스트를 사용하세요. 멤버십 확인이 필요하면 셋을 사용하세요. 순위가 필요하면 정렬된 셋을 사용하세요. 모든 작은 변경에 대해 전체 직렬화된 문자열을 다시 쓰는 것은 처음에는 간단하지만, 객체가 커짐에 따라 비용이 많이 들고 어색해질 수 있습니다.

배포 전 작은 체크리스트

새로운 GETSET 경로를 배포하기 전에 몇 가지 간단한 질문을 해보세요.

  • 정확한 키 이름은 무엇인가요?
  • 두 서비스가 실수로 동일한 키를 사용할 수 있나요?
  • 키가 만료되어야 하나요?
  • Redis가 nil을 반환하면 어떻게 되나요?
  • 덮어쓰기가 허용되나요, 아니면 NX 또는 XX를 사용해야 하나요?
  • 값이 하나의 문자열로 읽고 쓰기에 충분히 작은가요?
  • 프로덕션 동작이 잘못된 것처럼 보일 때 redis-cli에서 값을 디버깅할 수 있나요?

이러한 질문은 대부분의 기본적인 Redis 문자열 실수를 사고로 이어지기 전에 잡아냅니다. 명령어 구문은 쉽습니다. 키 주변의 수명 주기가 버그가 일반적으로 숨어 있는 곳입니다.

GETSET은 작은 명령어이지만 많은 운영적 무게를 가지고 있습니다. 캐시 데이터에는 만료를 사용하고, 덮어쓰기가 중요할 때는 NX 또는 XX를 사용하며, nil을 별도의 상태로 처리하고, 키 이름을 터미널에서 디버깅할 수 있을 만큼 일관되게 유지하세요. 문자열이 비좁게 느껴지기 시작하면 관련 필드를 해시로 이동하고 액세스 패턴과 일치하는 데이터 구조를 사용하세요.