Redis는 In-Memory 데이터 구조 저장소로, 다양한 자료형을 지원합니다. 그 중에서도 String은 Redis에서 가장 기본적이면서도 널리 사용되는 자료형입니다. 실제로 Redis 사용 사례의 90% 이상이 String 자료형을 활용한다고 할 수 있을 정도로 중요하고 유용한 자료형입니다.
Redis String의 내부 구조: SDS (Simply Dynamic String)
SDS란?
Redis String은 내부적으로 SDS(Simply Dynamic String) 구조를 사용합니다. 이는 C언어의 전통적인 null-terminated string이 아닌, Redis가 자체적으로 구현한 동적 문자열 구조입니다.
SDS의 주요 장점
1. 빠른 문자열 길이 조회
- 시간 복잡도: O(1)
- C의 strlen()은 O(n)이지만, SDS는 길이 정보를 미리 저장하여 즉시 반환
2. 버퍼 오버플로우 방지
- 문자열 추가 시 자동으로 메모리 재할당
- 안전한 메모리 관리로 시스템 크래시 방지
3. 바이너리 세이프 (Binary Safe)
- null 바이트(\0)를 포함한 모든 바이너리 데이터 저장 가능
- 이미지, 직렬화된 객체 등도 String으로 저장 가능
4. 효율적인 APPEND 연산
- 미리 할당된 여유 공간을 활용하여 문자열 추가 최적화
Redis String의 3가지 인코딩 방식
Redis는 메모리 효율성을 위해 값의 특성에 따라 다른 인코딩 방식을 사용합니다:
1. 정수값 저장 (int encoding)
SET counter 123
# 내부적으로 정수로 저장하여 메모리 절약
2. embstr 인코딩 (≤ 44바이트)
SET short_text "Hello Redis"
# 44바이트 이하의 문자열은 연속된 메모리에 할당
# RedisObject와 SDS가 하나의 메모리 블록에 저장됨
3. raw 인코딩 (> 44바이트)
SET long_text "This is a very long string that exceeds 44 bytes limit..."
# 44바이트 초과 시 RedisObject와 SDS를 별도 메모리에 분리 할당
핵심 String 명령어들
기본 저장 및 조회
SET 명령어
Redis에서 90% 이상 사용되는 가장 기본적인 데이터 저장 명령어입니다.
# 기본 사용법
SET users:1000:name "Redhorse"
SET users:1000:email "redhorse@example.com"
# 키 네이밍 컨벤션: 콜론(:)을 사용한 계층적 구조
SET project:redis:version "7.0"
GET 명령어
저장된 데이터를 검색하는 명령어입니다.
GET users:1000:name
# 결과: "Redhorse"
GET nonexistent_key
# 결과: (nil)
카운터 명령어들
Redis String의 강력한 기능 중 하나는 원자적(Atomic) 카운터 연산입니다.
INCR / DECR 명령어
# 방문 횟수 카운터
SET users:1000:visits 0
INCR users:1000:visits # 1 증가 → 1
INCR users:1000:visits # 1 증가 → 2
DECR users:1000:visits # 1 감소 → 1
Race Condition 방지: INCR/DECR은 원자적 연산이므로 동시성 문제가 발생하지 않습니다.
INCRBY / DECRBY 명령어
# 특정 값만큼 증가/감소
INCRBY users:1000:visits 5 # 5 증가
DECRBY users:1000:visits 2 # 2 감소
# 점수 시스템 예제
SET game:player:1000:score 150
INCRBY game:player:1000:score 50 # 50점 추가 → 200
다중 작업 명령어
MSET (Multiple SET)
# 여러 키-값을 한 번에 설정
MSET users:1000:name "Redhorse" users:1000:email "redhorse@example.com" users:1000:country "Korea"
MGET (Multiple GET)
# 여러 키의 값을 한 번에 조회
MGET users:1000:name users:1000:email users:1000:age
# 결과 예시:
# 1) "Redhorse"
# 2) "redhorse@example.com"
# 3) (nil) # users:1000:age가 존재하지 않는 경우
RTT(Round-Trip Time) 감소: MGET/MSET을 사용하면 네트워크 왕복 시간을 크게 줄일 수 있습니다.
# 비효율적: 3번의 네트워크 통신
GET users:1000:name
GET users:1000:email
GET users:1000:country
# 효율적: 1번의 네트워크 통신
MGET users:1000:name users:1000:email users:1000:country
SET 명령어의 고급 옵션들
만료 시간 설정
SETEX (SET with EXpire)
# 300초(5분) 후 자동 만료
SETEX session:abc123 300 "user_data"
# 캐시 구현 예제
SETEX cache:weather:seoul 3600 "22°C, Sunny"
PSETEX (밀리초 단위)
# 5000밀리초(5초) 후 만료
PSETEX temp:token:xyz789 5000 "temporary_token"
SET 명령어의 EX/PX 옵션
# EX: 초 단위 만료
SET cache:user:1000 "cached_data" EX 3600
# PX: 밀리초 단위 만료
SET cache:user:1000 "cached_data" PX 3600000
# NX: 키가 존재하지 않을 때만 설정
SET users:1000:name "Redhorse" NX
# XX: 키가 이미 존재할 때만 설정
SET users:1000:name "NewName" XX
조건부 설정
SETNX (SET if Not eXists)
# 분산 락(Distributed Lock) 구현
SETNX lock:resource:db1 "locked_by_server_A"
# 결과가 1이면 락 획득 성공, 0이면 실패
# 중복 처리 방지에 유용
실제 사용 사례들
1. 사용자 세션 관리
# 사용자 로그인 시
SET session:a1b2c3d4 "user:1000" EX 1800 # 30분 세션
# 세션 확인
GET session:a1b2c3d4
2. 캐시 구현
# API 응답 캐싱 (1시간)
SET cache:api:weather:seoul "22°C,Sunny,Humidity:65%" EX 3600
# 데이터베이스 쿼리 결과 캐싱
SET cache:db:user:1000:profile "{\"name\":\"Redhorse\",\"role\":\"developer\"}" EX 600
3. 카운터 시스템
# 일일 방문자 수
INCR stats:daily_visitors:2024-03-02
# API 호출 제한 (Rate Limiting)
SET rate_limit:api:user:1000 1 EX 60 # 1분간 1회
INCR rate_limit:api:user:1000
4. 분산 락
# 중복 처리 방지를 위한 락
SET lock:payment:order:12345 "processing" NX EX 30
# 처리 완료 후 락 해제
DEL lock:payment:order:12345
성능 최적화 팁
1. 키 네이밍 전략
# 좋은 예: 계층적이고 의미있는 구조
users:1000:profile
users:1000:settings:notification
orders:2024:03:02:summary
# 피해야 할 예: 너무 길거나 의미 불명확
this_is_a_very_long_key_name_that_wastes_memory
user_data_abcd1234
2. 메모리 효율성
# 작은 정수는 자동으로 정수 인코딩 사용
SET counter 42 # int 인코딩
# 44바이트 이하로 유지하면 embstr 인코딩
SET short_msg "Hello" # embstr 인코딩
# 불필요하게 긴 문자열 피하기
SET data "very long string..." # raw 인코딩 (메모리 사용량 증가)
3. 배치 연산 활용
# 여러 번의 GET 대신 MGET 사용
MGET users:1000:name users:1000:email users:1000:role
# 여러 번의 SET 대신 MSET 사용
MSET cache:A "dataA" cache:B "dataB" cache:C "dataC"
주의사항 및 베스트 프랙티스
1. 메모리 사용량 고려
- Redis는 In-Memory 데이터베이스이므로 메모리 사용량을 항상 모니터링해야 합니다
- 대용량 문자열 저장 시 메모리 부족 위험이 있습니다
2. 만료 시간 설정
# 캐시 데이터는 반드시 TTL 설정
SET cache:expensive_query "result" EX 3600
# 세션 데이터도 적절한 만료 시간 설정
SET session:token "user_data" EX 1800
3. 원자성 보장
# 카운터 연산 시 INCR/DECR 사용 (GET + SET 조합 피하기)
INCR page_views # ✅ 원자적 연산
# 다음과 같은 방식은 Race Condition 위험
GET page_views # ❌ 비원자적
SET page_views 101 # ❌ 중간에 다른 클라이언트가 수정할 수 있음
4. 키 존재 여부 확인
# 조건부 설정 활용
SET lock:resource "owner" NX # 키가 없을 때만 설정
SET config:updated "yes" XX # 키가 있을 때만 업데이트
Redis String은 단순해 보이지만 매우 강력하고 유연한 자료형입니다. SDS 구조의 장점과 다양한 인코딩 방식을 통해 메모리 효율성을 제공하며, 풍부한 명령어 세트를 통해 다양한 사용 사례를 지원합니다.
특히 캐싱, 세션 관리, 카운터, 분산 락 등의 시나리오에서 Redis String의 진가를 발휘할 수 있습니다. 원자적 연산과 RTT 최적화를 통해 높은 성능을 보장하므로, 현대적인 웹 애플리케이션에서 필수적인 도구라고 할 수 있습니다.
다음 포스트에서는 Redis의 다른 자료형들(List, Set, Hash, Sorted Set)에 대해서도 자세히 알아보겠습니다.
참고 자료: