리액티브 레디스 — Transaction·Persistence·GeoSpatial·ACL

2026-05-03확률과 통계 마스터 노트

리액티브 레디스 마스터 노트 시리즈 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

MULTIEXEC 사이 명령은 . 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

시리즈 다른 편 (시리즈 마지막)

공식 문서: Redis Persistence / Redis ACL / Redis GeoSpatial 에서 더 깊이.

리액티브 레디스 마스터 시리즈는 여기서 마무리. 1편부터 7편까지의 흐름이 머리에 남으면 WebFlux + Redis 통합의 거의 모든 운영 패턴을 손에 잡고 시작할 토대가 됩니다.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!