레디스의 개념
레디스는 In-memory Database 로서, 디스크에 저장되는 일반적인 RDBMS(MySQL, Oracle)과 달리 디스크가 아닌 메모리(RAM)에 저장되는 데이터베이스이다. 관계형이 아닌 Key-Value 형태의 NoSQL DB이기 때문에 스키마가 존재하지 않고, 메모리상에서 데이터를 매우 빠르게 읽어들일 수 있기에 주로 캐시 저장소로서 많이 사용된다.
그럼 왜 레디스를?
기본적으로 클라이언트에서 서버로의 데이터 접근 요청이 있을 때, 일반적인 DB를 사용할 경우 클라이언트 -> 서버 메모리 -> 디스크 DB 의 3단계를 거치며 데이터를 접근하여 가져온다. 이 과정 중 특히 서버 <-> DB 통신의 I/O가 대부분의 시간을 차지하는데, 레디스는 서버 메모리상에서 동작하기 때문에 대부분의 시간을 잡아먹던 이 DB I/O를 없애버릴 수가 있는 것이다. 이런 특징에서 오는 차이는 조금 더 빠르다 정도가 아니고, 인메모리 저장소 사용 시 1만건 기준 데이터 조회 속도가 최소 1000배 이상 빠르다는 것이 정설이다.
그런데 프로그램 메모리에 대해 어느정도 이해가 있는 사람은 이 구조를 보고 이런 생각이 들 것이다.
"그럼 프로그램 내에 변수로 데이터를 저장하고 불러오는 거나 레디스를 통해 불러오는 거나 메모리에서 다루는 건 결국 똑같은 게 아닌가?"
그렇다. 메모리상에서 다룬다는 관점만 생각해보면 그냥 Java의 경우 HashMap에 데이터를 적재하는 것이나 레디스에 적재하는 것이나 거의 차이가 없다. 하지만, 서버가 1대 뿐인 작은 어플리케이션이라면 HashMap만으로도 해결이 가능하겠지만, 여러대의 서버를 사용하는 대규모 애플리케이션에서 In-Memory 저장소를 HashMap으로 대체할 수 있을까? 서버가 여러대로 늘어날 수록 서버간의 데이터 동기화에 대한 문제는 점점 더 복잡해지고 다루기가 어려워지기 마련이다. 수십대의 서버에서 HashMap을 사용하고, 그 HashMap에 대한 데이터를 동기화하고자 한다면 상당한 어려움이 따를 것이다. 이럴 때 Redis를 사용하면, 레디스를 동기화 클러스터링 서버로 두어 인메모리 저장소의 이점을 살리면서 데이터 동기화에 대한 문제도 손쉽게 해결이 가능해지는 것이다.
레디스의 특징
위에서 설명한 특징들 외에도, 레디스를 사용할 이유는 더욱 다양하다. 레디스의 대표적인 특징들은 아래와 같다.
- 1. 다양한 자료구조 지원(String, Lists, Sets, Sorted Sets, Hashes 등등..)
- 2. 읽기 성능 향상을 위한 서버 복제 지원(Master/Slave)
- 3. 쓰기 성능 향상을 위한 샤딩(Sharding) 지원
- 4. 영구적 데이터 저장을 위한 영속성 지원
- 5. 싱글스레드로 동작
먼저 레디스는 아래와 같은 다양한 자료구조를 지원하여 상황에 맞도록 유연하게 자료를 저장할 수 있다.
2. 레디스는 Master/Slave 구조를 제공하여, 한 서버가 장애시 다른 서버가 대응하는 장애 대응 구조를 가질 수 있고, 마찬가지로 데이터 동기화를 통해 백업을 수행할 수도 있으며, 읽기연산은 Slave에서 하고 쓰기연산은 Master에서 하는 등으로 처리 성능의 향상도 이끌어낼 수 있다.
3. 레디스는 수평적 파티셔닝인 샤딩을 지원하여 대량의 데이터를 다룰 시 효과적으로 처리가 가능하다.
4. 레디스는 메모리상에 존재하는 In-Memory 저장소지만, 메모리의 데이터를 스냅샷으로 저장하거나, 쓰기연산에 대한 로그를 디스크에 저장하여 실행시마다 해당 로그의 연산을 재실행하는 등으로 데이터의 영속성을 지원할 수 있다.
5. 레디스는 싱글 스레드로 동작하기 때문에 데이터의 원자성을 보장하며 Race Condition을 예방할 수 있다.
Redis vs Memcached
인메모리 캐시 저장소로 사용하기위한 또다른 선택지는 Memcached도 있다. 맴캐시드는 레디스와 마찬가지로 오픈소스 라이브러리인데다, 사용 방법도 간편하여 상황에 따라서는 레디스 대신 선택하는 것도 좋은 선택이 될 수 있다.
그럼 둘의 특징은 어떤 차이가 있고, 어떤 경우에 레디스 또는 맴캐시드를 선택하는 게 좋을지 한번 확인해보자.
Redis | Memcached | |
저장소 | In Memory Storage | In Memory Storage |
저장 방식 | Key-Value | Key-Value |
데이터 타입 | String, Set, Sorted Set, Hash, List 등등 | String |
데이터 저장 | Memory, Disk(영속성) | Only Memory(비영속성) |
메모리 재사용 | 메모리 재사용 하지 않음 (명시적으로만 데이터 삭제 가능) |
메모리 부족시 LRU 알고리즘을 이용하여 데이터 삭제 후 메모리 재사용 |
스레드 | Single Thread | Multi Thread |
캐싱 용량 | Key, Value 모두 512MB | Key name 250 byte, Value 1MB |
속도 | 읽기, 쓰기 속도가 Memcached보다 느림 하지만 큰 차이는 없음 |
디스크를 거치지 않기 때문에 읽기, 쓰기 속도가 Redis보다 빠름 |
Memcached를 선택하는 경우
- 상대적으로 작고 정적인 데이터를 캐싱하는 경우
- 여러 코어 또는 스레드가 있는 멀티 스레드의 경우
- 메모리 관리가 redis만큼 정교하지는 않지만, 메타 데이터에 대한 메모리 리소스를 비교적 적게 소비하여 간단한 사용에 적합하다.
- 쉽게 확장할 수 있지만 해싱 사용 여부에 따라 캐시된 데이터의 일부 또는 전부를 잃는다.
Redis를 선택하는 경우
- 문자열, 해시, 목록, 세트, 정렬된 세트 및 비트맵과 같은 복잡한 데이터 유형이 필요한 경우
- 인 메모리 데이터 세트를 정렬하거나 순위를 지정해야 하는 경우
- 키 저장소의 속성을 원할 경우
- 읽기 집약적 애플리케이션을 위해 기본 항목에서 하나 이상의 읽기 전용 복제본으로 데이터를 복제해야 하는 경우
- 기본 노드가 실패할 때 자동 장애 조치가 필요한 경우
- 서버에 대한 이벤트를 클라이언트에 알리기 위해 게시 및 구독(게시/구독) 기능이 필요한 경우
- 백업 및 복원 기능이 필요한 경우
- 여러 데이터베이스를 지원해야 하는 경우
레디스의 문제점
1. 시간복잡도
레디스는 싱글스레드로 동작하는 저장소이기에 원자성이 보장된다는 장점이 있지만, 그만큼 대량 처리시 처리속도가 늦어진다는 단점 또한 존재한다. 실시간으로 소수의 데이터를 처리할 때는 문제가 되지 않지만 O(N)의 복잡도를 가진 Keys(저장된 모든키를 보여주는 명령어)나 flushall(모든 데이터 삭제) 등의 명령어를 처리할 때는 상당히 오랜 시간이 소요되어 서비스 성능의 저하를 가져올 수 있다. 이런 연산은 맴캐시드가 레디스보다 훨씬 빠르므로, 해당 연산이 자주 수행될 서비스라면 레디스보다는 맴캐시드의 사용을 고려해보아야 한다.
2. 메모리 파편화
메모리를 할당 받고 해제 하는 과정에서 아래 그림과 같이 부분부분 빈 공간이 생기게 되는데, 4번같이 새로운 메모리를 할당할때 알맞는 공간(4칸)이 없기 때문에 마지막부분(우측) 부분에 프로세스가 위치해야 되고, 그러면 빈 공간 메모리가 남아 낭비가 발생하게 된다.
그리고 이 현상이 계속되면 실제 physical 메모리가 커져 프로세스가 죽는 현상이 발생 할 수도 있다.
또한 쓰기 연산이 copy on wirte 방식으로 동작하기 때문에 최대 메모리를 2배 이상까지 사용하기도 한다.
그래서 redis를 사용할때 메모리를 적당히 여유있게 사용하는 것이 좋다.
* Reference.
https://sihyung92.oopy.io/database/redis/1
'개발' 카테고리의 다른 글
RestTemplate / WebClient, 그리고 Reactive Programming (0) | 2023.03.27 |
---|---|
Blocking/Non-Blocking & Sync/Async (0) | 2023.03.15 |
Mock객체를 활용한 테스트와 유의사항 (0) | 2023.03.03 |
세션 불일치 문제 해결법 (0) | 2023.02.27 |
TDD (0) | 2023.01.12 |