2023. 3. 18. 22:40ㆍRedis
Redis
- 목차
- Redis란?
- 특징
- 데이터 타입
- 캐시 전략
- 영속성
- 트랜잭션
- 장단점
- 주의사항
- vs Memcached
1. Redis란?
Redis는 인메모리 형식의 NoSQL 데이터 저장소이다. Redis는 메모리 기반이기 때문에 속도가 일반 DB에 비해 매우 빠르다. 그래서 세션 저장소, 데이터 캐싱, 메시지 브로커 등으로 많은 개발자들이 사용한다.
2. 특징
- 영속성을 지원하는 인메모리 데이터 저장소이다.
- 자료구조가 Atomic하기 때문에 경쟁 상태를 피할 수 있다.
- 읽기 성능 증대를 위한 서버 복제(Replication)를 지원한다.
- 쓰기 성능 증대를 위한 클라이언트 측 샤딩(Sharding)을 지원한다.
- Key/Value 방식으로 데이터를 저장한다.
3. 데이터 형식
- String
- Redis의 가장 기본적인 타입이며 binary safe하다.
- ‘데이터 타입이 binary safe하다’라는 의미는 문자열 뿐만 아니라 정수, JPEG와 같은 이진 데이터도 다룰 수 있다는 뜻이다.
- 최대 512MB까지 저장할 수 있다.
- 활용
- 카운터와 같은 처리를 하고 싶을 때
INCR
,DECR
,INCRBY
와 같은 명령으로 할 수 있다. - 문자열 뒤에 추가를 하고 싶다면
APPEND
명령을 사용한다.
- 카운터와 같은 처리를 하고 싶을 때
- List
- 간단히 문자열의 리스트이며 삽입 정렬에 의해 정렬된다.
- 양방향 연결 리스트 구조로서 head와 tail에도 데이터 삽입이 가능하다.
LPUSH
- head에 데이터 삽입RPUSH
- tail에 데이터 삽입
LPUSH
또는RPUSH
중 하나가 빈 키에 수행되면 새로운 리스트가 생성된다.- 마찬가지로 리스트 연산이 리스트를 비울 경우 키 공간에서 키가 제거된다.
- 시간 복잡도 관점에서 Redis 리스트의 주요 기능은 수 백만 개의 요소가 삽입된 경우에도 head와 tail 근처에 있는 요소의 삽입과 삭제에 대한 일관적인 시간 소요를 지원하는 것이다.
- 요소 접근은 리스트의 양 끝단에서는 매우 빠르지만 O(N) 작업이므로 매우 큰 리스트의 중간 요소에 접근하려고 하면 속도가 느리다.
- 최대 2^32 -1(4,294,967,295) 크기의 요소를 가질 수 있다.
- 활용
- SNS의 새로운 타임라인을 만들려고 한다면
LPUSH
를 사용하여 추가하고LRANGE
를 사용하여 리스트 목록을 조회할 수 있다. - 요소의 개수가 고정된 리스트를 만들고자 한다면
LPUSH
와LTRIM
을 함께 사용할 수 있다.
- SNS의 새로운 타임라인을 만들려고 한다면
- Set
- 정렬되지 않은 문자열 컬렉션이다.
- 요소에 대한 삽입, 삭제, 존재 확인에 대해 O(1)이 가능하다.
- 중복을 허용하지 않는다.
- Set을 계산하는 여러 명령을 지원하여 매우 짧은 시간에 합집합, 교집합, 차집합을 수행할 수 있다.
- 최대 2^32 -1(4,294,967,295) 크기의 요소를 가질 수 있다.
- 활용
- 서비스에 접속한 PC들의 고유 IP를 알고 싶다면
SADD
명령을 사용하여 Set에 추가할 수 있다. - Set에서 임의의 요소를 추출하고 싶다면
SPOP
또는SRANDMEMBER
명령을 사용할 수 있다.
- 서비스에 접속한 PC들의 고유 IP를 알고 싶다면
- Hash
- 문자열 필드와 문자열 값의 맵으로, 객체를 표현하기 위한 완전한 데이터 타입이다.
- 일부 필드를 가진 Hash는 매우 작은 공간을 가지는 방법으로 저장된다
- 따라서 작은 Redis 인스턴스에 수 백만 단위의 객체를 저장할 수 있다.
- Hash는 주로 객체를 표현하기 위해 사용되지만 많은 요소를 저장할 수도 있다.
- 최대 2^32 -1(4,294,967,295) 크기의 필드-값 쌍을 가질 수 있다.
- Sorted Set
- Set과 유사하지만 순서를 유지한다는 차이가 있다.
- 시간 복잡도가 O(log N)으로 요소를 매우 빠르게 삽입, 삭제, 변경이 가능하다.
- 요소는 순서대로 저장되기 때문에 매우 빠른 방법으로 조회할 수 있다.
- 중간 요소에 접근하는 것도 매우 빠르기 때문에 요소에 대한 빠른 접근이 필요하다면 리스트 대신 사용할 수도 있다.
- 활용
ZADD
를 통해 요소를 삽입하고ZRANGE
로 조회한다.
- Bitmap and HyperLogLog
- String 기반 타입을 사용하는 데이터 타입이지만 각각의 의미를 가지고 있다.
- Stream
- 로그를 추가하기만 하는 자료구조이다.
- 이벤트 발생 시 이를 저장할 때 유용하다.
- Geospatial index
- 지리적 정보 처리를 위해 제공하는 데이터 타입이다.
4. 캐시 전략
Look aside Cache
캐시 저장소에 자료가 없으면 데이터 저장소에 접근하여 캐시 저장소에 갱신한다. 일반적으로 사용되는 전략이다.
- 클라이언트가 웹 서버에 데이터를 요청한다.
- 웹 서버는 데이터가 존재하는지 캐시 저장소를 확인한다.
- 캐시 미스인 경우 데이터 저장소에 접근하여 데이터를 가져온다.
- 웹 서버가 데이터 저장소로부터 가져온 데이터를 캐시 저장소에 데이터를 갱신한다.
특징
- 읽기에 적합하다.
- 캐시 장애를 대비한 구성이다.
- 정합성 문제가 발생한다.
- 반복적인 호출에 적합하다.
- 초기 조회 시 무조건 DB를 호출하므로 단건 호출 빈도가 높은 서비스의 경우 적합하지 않다.
Write-back
모든 데이터를 캐시 저장소에 먼저 저장한다. 그리고 일정 시간 후에 데이터 저장소에 저장한다. 따라서 데이터 저장소의 부하를 줄일 수 있다.
- 클라이언트가 웹 서버에 데이터를 보낸다.
- 웹 서버는 캐시 저장소에 데이터를 저장한다.
- 일정 시간 후에 데이터 저장소에 데이터를 저장한다.
특징
- 쓰기에 적합하다.
- 캐시 장애 시 데이터가 유실될 수 있다.
- 정합성을 만족한다.
- 불필요한 리소스를 저장한다.
- 따라서 TTL(expire time)을 사용하여 사용되지 않는 리소스는 삭제해야 한다.
Write-Around
데이터 저장소에만 데이터를 저장하는 방식이다. 캐시 미스가 발생한 경우에만 캐시 저장소를 갱신한다.
- 클라이언트가 웹 서버에 데이터를 요청한다.
- 웹 서버는 캐시 저장소에서 데이터를 조회한다.
- 캐시 미스인 경우 데이터 저장소에서 데이터를 가져온다.
- 데이터 저장소에서 직접 캐시 저장소를 갱신한다.
특징
- 초기 데이터는 항상 캐시 미스가 발생한다.
- 애플리케이션 단에서 캐시 대상을 판단하여 데이터 저장 시 캐시에도 저장하는 방안이 있다.
Write-through
데이터 저장소와 캐시 저장소에 데이터를 모두 저장하는 방식이다.
- 클라이언트가 웹 서버에 데이터를 보낸다.
- 웹 서버는 캐시 저장소에 데이터를 모두 저장한다.
- 캐시 저장소의 데이터를 모두 DB에 저장한다.
- 캐시 저장소를 통해 데이터를 조회한다.
특징
- 캐시 저장소에 항상 최신 정보를 가지고 있다.
- 재사용되지 않는 데이터도 저장될 수 있으므로 리소스 낭비가 발생한다.
- 이 또한 TTL을 적용해야 한다.
5. 영속성
Redis에서 영속성을 관리하는 방법은 다음과 같다.
- RDB(Redis Database)
- 데이터셋에 대해 특정 시점의 스냅샷을 파일로 만드는 방식이다.
- AOF(Append Only File)
- 서버에 의해 요청받은 모든 쓰기 연산을 로깅한 후 나중에 서버가 재시작되면 저장된 연산을 다시 수행하는 방식이다.
- 명령들은 Redis 프로토콜 형식을 사용하여 저장된다.
- 로그가 너무 크다면 백그라운드로 수행할 수도 있다.
- No persistence
- 데이터 영속화가 필요하지 않으면 서버 메모리에만 저장할 수도 있다.
- RDB + AOF
- 같은 인스턴스에서 RDB와 AOF를 결합하는 것도 가능하다.
- 이 경우 AOF 파일이 완전함이 보장되므로 원본 데이터셋을 재구성하는 데 사용된다.
6. 트랜잭션
- Redis 트랜잭션은 한 번에 여러 명령을 실행을 할 수 있게 보장한다.
- 핵심 명령은
MULTI
,EXEC
,DISCARD
,WATCH
이다. - Redis 트랜잭션은 다음 두 가지를 보장한다.
- 트랜잭션의 모든 명령은 직렬화되고 순서대로 실행된다. 다른 클라이언트가 보낸 요청은 Redis 트랜잭션의 실행 중간에 절대 수행되지 않는다. 이는 격리된 단일 연산으로써 명령이 수행됨을 보장한다.
EXEC
명령은 트랜잭션의 모든 명령의 실행을 트리거하므로 만약 클라이언트가EXEC
명령을 실행하기 전에 트랜잭션의 컨텍스트에서 서버와의 연결이 끊어지면 아무 연산도 수행되지 않는다.EXEC
명령을 호출했다면 모든 연산이 수행된다.
- 연산은 큐로 저장된다.
사용법
MULTI
- 트랜잭션을 시작한다. 정상적으로 실행되면OK
메시지를 출력한다.EXEC
- 트랜잭션을 수행한다. 정상적으로 실행되면QUEUED
메시지를 출력한다.DISCARD
- 트랜잭션 큐에 저장된 연산들을 취소하고 비운다. 트랜잭션 또한 종료된다. 정상적으로 실행되면OK
메시지를 출력한다.
MULTI
+OK
SET a abc
+QUEUED
SET b def
+QUEUED
EXEC
*2
+OK
+OK
트랜잭션 오류
트랜잭션 도중 발생할 수 있는 오류는 다음 두 가지이다.
- 명령을 큐에 넣지 못할 수 있기 때문에
EXEC
이 호출되기 전에 오류가 있을 수 있다.- 구문 오류, 메모리 부족 등
EXEC
을 호출한 후에도 오류가 발생할 수 있다.- 잘못된 값을 가진 키에 대해 작업 수행
Redis 2.6.5 이상 버전에서는 서버가 명령을 누적하는 중에 에러를 탐지한다. 그 다음 EXEC
에서 오류를 반환하는 트랜잭션을 실행하는 것을 거부하고 트랜잭션을 버린다. 다만 EXEC
이후에 발생하는 오류는 특별한 방식으로 처리되지 않는다. 트랜잭션 중 일부 명령이 실패하더라도 다른 모든 명령은 실행된다. 이는 프로토콜 레벨에서 더 명확하다. 다음 예제 코드를 보면서 확인하자.
MULTI
+OK
SET a abc
+QUEUED
LPOP a
+QUEUED
SET b def
EXEC
*3
+OK
-ERR Operation against a key holding the wrong kind of value
+OK
트랜잭션 중 명령의 문법 오류가 발생하면 바로 오류가 발생하며 큐에 누적되지 않는다.
MULTI
+OK
INCR a b c
-ERR wrong number of arguments for 'incr' command
롤백
Redis는 트랜잭션 롤백을 지원하지 않는다. 롤백을 지원하는 것은 Redis의 간단함과 성능에 상당한 영향을 주기 때문이다.
명령 취소하기(Discarding)
DISCARD
는 트랜잭션을 취소하기 위해 사용된다.
> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"
check-and-set을 사용한 낙관적 락
- 차후에 정리
7. 장단점
장점
- 리스트, 배열과 같은 데이터 처리에 유용하다.
- 리스트형 데이터 추가, 삭제가 MySQL에 비해 10배 정도 빠르다.
- 메모리를 활용하면서 영속성을 지원한다.
- 싱글 쓰레드로 수행되어 Master-Slave 구조로 서버 하나에 여러 Redis 서버를 실행할 수 있다.
- Master-Slave 간 복제는 논블로킹이다.
단점
- 인메모리 방식이기 때문에 장애 발생 시 데이터 유실이 있을 수 있다.
- 메모리 단편화가 발생하기 쉽다.
- 대규모 데이터에 대한 응답 속도가 불안정하다.
- 싱글 쓰레드이기 때문에 $O(N)$ 명령을 사용하면 지연이 발생할 수 있다.
KEYS
,FLUSHALL
,FLUSHDB
,DELETE COLLECTIONS
,GET ALL COLLECTIONS
8. 주의사항
MaxMemory 값 설정
Persistence/복제 사용 시 MaxMemory 설정 주의
- RDB 저장 또는 AOF rewrite 시 프로세스를 fork()한다.
- 이때 Copy-on-Write 방식을 사용하기 때문에 메모리를 2배로 사용하는 상황이 발생한다.
- 따라서 Persistence/복제 사용 시 MaxMemory는 실제 메모리의 절반으로 설정하는 편이 좋다.
메모리 관리
물리적으로 사용되고 있는 메모리 모니터링
- used_memory: 논리적으로 Redis가 사용하는 메모리
- used_memory_rss: OS가 Redis에 할당하기 위해 사용한 물리적인 메모리
- 삭제되는 키가 많으면 단편화 증가
- 특정 시점에 피크를 찍고 다시 삭제되는 경우
- TTL로 인한 eviction이 많이 발생하는 경우
캐시
캐시 저장
- 자주 변경되지 않는 데이터를 캐시 저장소에서 활용하는 경우 높은 성능 향상을 이끌어 낼 수 있다.
- → 캐시를 저장하는 시점은 자주 사용되며 자주 변경되지 않는 데이터를 기준으로 하는 것이 좋다.
캐시 제거
- 데이터 저장소와의 동기화가 제대로 이루어지지 않으면 캐시에 저장된 오래된 데이터를 사용할 수도 있다.
- → 캐시 구성 시 적절한 만료 정책을 설정해야 한다(일반적으로 LRU 알고리즘을 사용한다).
메모리 기반
- 캐시는 메모리 기반이다. 캐시에 저장되는 모든 데이터를 디스크에도 저장할 경우 성능 저하를 일으킬 수 있어 일정 시간 데이터 수집 및 저장을 하게 된다.→ 캐시에는 중요한 정보, 민감한 정보 등은 저장하지 않는 것이 좋다.
- → 캐시 솔루션 장애 발생 시 적절한 대응 방안을 검토해보는 것이 좋다.
- → 데이터 유실 및 정합성 문제가 발생할 수 있음을 항상 고려해야 한다.
9. vs Memcached
분류 | Redis | Memcached |
---|---|---|
처리속도 | 데이터가 디스크와 메모리에 저장되지만 Memcached와 큰 차이가 없으며 빠름 | 데이터가 메모리에만 저장되어 빠름 |
데이터 저장 방식 | 데이터가 디스크에도 저장되기 때문에 복구 가능 | 데이터가 메모리에만 저장되기 때문에 프로세스가 kill되거나 장애 발생 시 데이터 유실 |
만료일 지정 방식 | 만료일을 지정하면 만료된 데이터는 사라짐 | Redis와 동일 |
메모리 재사용 | 메모리를 재사용하지 않음 명시적으로 데이터 제거 가능 |
저장소 메모리를 사용 만료 전에 더 이상 데이터를 넣을 메모리가 없다면 LRU 알고리즘 기반으로 데이터 삭제 |
데이터 타입 | 다양한 데이터 타입 지원 | String 만 지원 |