리액티브 레디스 마스터 노트 시리즈 7편 (마지막). Redis Transaction(MULTI/EXEC)의 원자성 보장과 한계, Persistence 두 모드(RDB 스냅샷·AOF 로그)와 운영 트레이드오프, GeoSpatial(GEOADD·GEORADIUS)으로 위치 기반 서비스 구현, Redis 6+ ACL로 사용자별 권한 제어, requirepass·rename-command 보안 설정까지 — 시리즈 마무리.
이 글은 리액티브 레디스 마스터 노트 시리즈의 마지막 일곱 번째 편입니다. 1~6편이 핵심 기능이었다면, 이번엔 운영 환경의 고급 주제 — Transaction·Persistence·GeoSpatial·ACL.
Redis는 휘발성 인메모리 저장소지만, 운영 환경에선 영속화·보안·정밀 제어가 필요합니다. 이 4가지가 그 답.
처음 고급 주제가 어렵게 느껴지는 이유
처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, 각 주제가 독립적입니다. Transaction과 Persistence가 어떻게 연결? 둘째, GeoSpatial이 Redis에 있는 게 의외입니다 — 그게 왜 인메모리 DB의 기능?
해결법은 한 가지예요. 각 기능을 "어떤 운영 문제를 풀까"로 보는 것. Transaction = 원자성, Persistence = 영속화, GeoSpatial = 위치 기반 서비스, ACL = 보안. 각자 독립적인 운영 답이라 각자 정리.
Redis Transaction — MULTI/EXEC
문제 — 명령 사이 끼어들기
Client A: GET balance → 100
Client B: SET balance 50 ← 끼어듦
Client A: SET balance 100+10 → 110 (60이어야 했음!)
해결 — Transaction
> MULTI # 트랜잭션 시작
OK
> GET balance # 큐에 들어감 (실행 X)
QUEUED
> INCR balance
QUEUED
> EXEC # 한 번에 원자적으로 실행
1) "100"
2) (integer) 101
MULTI와 EXEC 사이 명령은 큐. EXEC 시 한 번에 원자적 실행. 다른 클라이언트가 끼어들 수 X.
한계
여기서 정말 중요한 시험 함정 — Redis Transaction은 RDB Transaction과 다름:
- 롤백 없음 — 한 명령 실패해도 나머지 계속
- 격리만 보장 (다른 클라이언트 안 끼어듦)
- 명령 검증 = EXEC 시점 (구문 오류는 MULTI 시 거부)
> MULTI
> SET key "value"
> INCR key # key는 문자열, INCR 실패할 것
> EXEC
1) OK # SET 성공
2) (error) ERR value is not an integer # INCR 실패
# SET은 그대로 (롤백 X)
진짜 원자성·롤백 = Lua Script.
Optimistic Locking — WATCH
> WATCH balance
> GET balance
> MULTI
> SET balance 110
> EXEC
WATCH 후 다른 클라이언트가 balance 변경 → EXEC 거부 (nil 반환). 재시도.
Reactive Transaction
redisTemplate.execute(connection -> {
connection.multi();
connection.stringCommands().get(...);
connection.stringCommands().incr(...);
return connection.exec();
});
여기서 시험 함정이 하나 있어요. Reactive Transaction은 제한적. 동기 흐름과 다름. 분산 락이나 Lua Script 사용이 더 깨끗.
Lua Script — 진짜 원자성
> EVAL "
local val = redis.call('GET', KEYS[1])
if tonumber(val) > 100 then
return redis.call('INCRBY', KEYS[1], -10)
end
return val
" 1 balance
서버에서 한 번에 실행. 진짜 원자성.
DefaultRedisScript<Long> script = new DefaultRedisScript<>(
"local val = redis.call('GET', KEYS[1])\n" +
"if tonumber(val) > 100 then\n" +
" return redis.call('INCRBY', KEYS[1], -10)\n" +
"end\n" +
"return tonumber(val)",
Long.class
);
redisTemplate.execute(script, List.of("balance")).subscribe();
Persistence — RDB vs AOF
휘발 메모리 → 디스크 백업.
1. RDB (Redis Database) — 스냅샷
periodic snapshot → dump.rdb 파일 (전체 메모리 직렬화)
설정 (redis.conf):
save 900 1 # 900초·1번 변경 시
save 300 10 # 300초·10번 변경 시
save 60 10000 # 60초·1만번 변경 시
장점:
- 컴팩트 (압축)
- 복구 빠름
- fork() 자식 프로세스로 비동기
단점:
- 마지막 스냅샷 이후 데이터 손실
- 큰 데이터셋은 fork() 비용
2. AOF (Append Only File) — 로그
모든 쓰기 명령을 appendonly.aof 파일에 추가
재시동 시 명령 재실행으로 복구
설정:
appendonly yes
appendfsync everysec # 1초마다 fsync (권장)
# always - 매 명령마다 (안전하지만 느림)
# no - OS 위임 (위험)
장점:
- 데이터 손실 최소 (everysec = 최대 1초)
- 사람이 읽을 수 있음
- 자동 rewrite로 압축
단점:
- RDB보다 큼
- 복구 느림
3. RDB + AOF 결합 (권장)
appendonly yes
save 900 1 # RDB도 활성
복구 시 AOF 우선 (최신). RDB는 빠른 백업·이전 시점 복구용.
여기서 정말 중요한 시험 함정 — 운영 표준 = RDB + AOF + everysec. 데이터 손실 ≤ 1초. 둘 다 비활성 시 재시동 = 모든 데이터 손실.
디스크 분리
WAL과 데이터를 같은 디스크 X — 분리 권장 (Kafka 6편 시리즈와 동일 원칙).
GeoSpatial — 위치 기반 서비스
Redis 3.2+. ZSet 기반 (점수 = 위치 해시).
명령
# 위치 추가
> GEOADD restaurants 127.0276 37.4979 "강남역"
> GEOADD restaurants 127.0382 37.5012 "역삼역"
> GEOADD restaurants 127.0095 37.4836 "양재역"
# 두 위치 거리
> GEODIST restaurants "강남역" "역삼역" km
"1.1"
# 반경 검색
> GEOSEARCH restaurants FROMLONLAT 127.0276 37.4979 BYRADIUS 2 km ASC
1) "강남역"
2) "역삼역"
# 좌표 조회
> GEOPOS restaurants "강남역"
1) 1) "127.0275999..."
2) "37.49789..."
Reactive 사용
ReactiveGeoOperations<String, String> ops = redisTemplate.opsForGeo();
// 위치 추가
ops.add("restaurants",
new Point(127.0276, 37.4979),
"강남역").subscribe();
// 반경 검색
GeoReference<String> reference = GeoReference.fromCoordinate(127.0276, 37.4979);
RedisGeoCommands.GeoSearchCommandArgs args = RedisGeoCommands.GeoSearchCommandArgs
.newGeoSearchArgs()
.includeCoordinates()
.includeDistance()
.sortAscending()
.limit(10);
ops.search("restaurants",
reference,
new Distance(2, RedisGeoCommands.DistanceUnit.KILOMETERS),
args)
.subscribe(r -> System.out.println(r.getName() + " " + r.getDistance()));
사용처 — 식당 찾기·배달·차량 호출
public Flux<Restaurant> findNearby(double lon, double lat, double radiusKm) {
return redisTemplate.opsForGeo()
.search("restaurants",
GeoReference.fromCoordinate(lon, lat),
new Distance(radiusKm, KILOMETERS),
GeoSearchCommandArgs.newGeoSearchArgs()
.includeDistance()
.sortAscending()
.limit(20))
.map(this::toRestaurant);
}
여기서 정말 중요한 시험 함정 — Redis GeoSpatial = 모든 위치 기반 서비스의 출발점. PostGIS·MongoDB Geo와 비교해 매우 빠름. 단 거리 정확도는 km 단위 (mm 정밀도 X).
Redis ACL — 사용자별 권한
Redis 6+. 사용자·비밀번호·명령 권한 분리.
기본 — requirepass (Redis 5 이전 방식)
requirepass "secret"
> AUTH secret
OK
단일 비밀번호, 모든 명령 가능. 한계 명확.
ACL (Redis 6+)
# 사용자 생성 — 읽기 전용
> ACL SETUSER reader on >readerpass ~* +@read
# 쓰기 가능
> ACL SETUSER writer on >writerpass ~app:* +@all -DEBUG -FLUSHALL
# 사용자 목록
> ACL LIST
1) "user default on nopass ~* &* +@all"
2) "user reader on #abc... ~* &* +@read"
3) "user writer on #def... ~app:* &* +@all -DEBUG -FLUSHALL"
# 인증
> AUTH writer writerpass
ACL 문법
on/off # 활성/비활성
>password # 비밀번호
~prefix:* # 키 패턴 (이 패턴만 접근)
+command # 허용 명령
-command # 거부 명령
+@category # 카테고리 허용 (read·write·dangerous 등)
&pattern # 채널 패턴 (Pub/Sub)
Spring Boot 연동
spring:
data:
redis:
username: writer
password: writerpass
여기서 시험 함정이 하나 있어요. 운영 환경 ACL 필수. 단일 requirepass는 모든 권한 = 키 노출 시 폭발. ACL로 서비스별 사용자 분리.
보안 체크리스트
✓ AOF + RDB 활성화
✓ ACL — 사용자별 권한 (Redis 6+)
✓ 위험 명령 rename-command (FLUSHALL·DEBUG·CONFIG)
✓ TLS 활성화 (운영 외부 노출 시)
✓ bind 127.0.0.1 (외부 접근 차단)
✓ 방화벽 (포트 6379 외부 X)
✓ 정기 백업 (RDB 파일 별도 저장소)
TLS 설정
Redis 6+:
tls-port 6379
port 0 # non-TLS 포트 비활성
tls-cert-file /path/cert.crt
tls-key-file /path/key.key
tls-ca-cert-file /path/ca.crt
시리즈 마무리 — 7편 종합
1편부터 7편까지의 흐름:
| 편 | 주제 | 한 줄 |
|---|---|---|
| 1 | 기초·Spring Boot | Reactive Redis 등장 배경, Lettuce 기본 |
| 2 | Template·Redisson·Serializer | 5 Operations, 클라이언트 선택, JSON 직렬화 |
| 3 | 자료구조 5종 | String/Hash/List/Set/ZSet 사용 패턴 |
| 4 | WebFlux 캐싱 | @Cacheable 함정, Cache-Aside 표준 |
| 5 | 성능 | 벤치마크, Pipeline, slowlog, 모니터링 |
| 6 | Pub/Sub·WebSocket | 실시간 통신, 트렌딩, Streams |
| 7 | 고급 | Transaction, RDB/AOF, GeoSpatial, ACL |
WebFlux + Reactive Redis 통합의 거의 모든 운영 패턴을 한 번에 통찰할 토대.
시험 직전 한 번 더 — 자주 헷갈리는 함정 모음
여기까지가 7편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.
- Transaction (MULTI/EXEC) = 격리 (다른 클라이언트 안 끼어듦)
- 롤백 없음 — 한 명령 실패해도 나머지 계속
- WATCH = Optimistic Locking
- 진짜 원자성 = Lua Script (EVAL)
- Reactive Transaction은 제한적
- Persistence 2 모드 — RDB (스냅샷) / AOF (로그)
- RDB — 컴팩트·빠른 복구·마지막 스냅샷 이후 손실
- AOF — 데이터 손실 ≤ 1초·복구 느림
- 운영 표준 = RDB + AOF + everysec
- 둘 다 비활성 = 재시동 시 데이터 전손실
- WAL과 데이터 디스크 분리
- GeoSpatial = ZSet 기반 위치 서비스
- GEOADD·GEODIST·GEOSEARCH·GEOPOS
- 식당·배달·차량 호출
- Spring
ReactiveGeoOperations - Redis ACL (6+) — 사용자·비밀번호·권한
- requirepass = 단일 비밀번호 (Redis 5 이전), 한계
- ACL —
~prefix:*키 패턴,+@read카테고리,+/-명령 - 운영 ACL 필수 — 서비스별 사용자
- 보안 체크 — AOF·RDB·ACL·rename-command·TLS·bind·방화벽·정기 백업
- TLS =
tls-port 6379+ cert·key
시리즈 다른 편 (시리즈 마지막)
- 1편 — Redis 기본·Spring Boot 연동
- 2편 — Template·Redisson·Serializer
- 3편 — 자료구조 5종
- 4편 — WebFlux 캐싱
- 5편 — 성능
- 6편 — Pub/Sub·WebSocket
- 7편 — 고급 (Transaction·Persistence·GeoSpatial·ACL) (현재 글, 시리즈 마지막)
공식 문서: Redis Persistence / Redis ACL / Redis GeoSpatial 에서 더 깊이.
리액티브 레디스 마스터 시리즈는 여기서 마무리. 1편부터 7편까지의 흐름이 머리에 남으면 WebFlux + Redis 통합의 거의 모든 운영 패턴을 손에 잡고 시작할 토대가 됩니다.