레디스와 대한 개념과 여러가지 전략 , 사용법 그리고 최신 이슈 사항에대해 알아보겠습니다.
캐싱의 기본 개념
우리는 레디스를 알기전 캐싱에대해 먼저 이해하고 있어야합니다.
- Cache? 캐시란?
-데이터 혹은 어떤 요청에대한 결과를 미리 저장해두었다가 나중에 그 데이터가 필요한 요청이 들어오면 빠르게 서비스를 해주기 위한 저장소
일반적으로 캐시(cache)는 메모리(RAM)를 사용하기 때문에 데이터베이스 보다 훨씬 빠르게 데이터를 응답할 수 있어 이용자에게 빠르게 서비스를 제공할 수 있다.
일반적인 캐싱의 사용 사례와 이점
- 웹 콘텐츠 캐싱 정적 콘텐츠(이미지, CSS, JavaScript 파일 등)를 캐시하여 웹 사이트의 로딩 속도를 개선합니다.
- 데이터베이스 쿼리 결과 캐싱: 자주 실행되는 쿼리의 결과를 캐시하여 데이터베이스의 부하를 줄이고 응답 시간을 단축합니다.
- 세션 정보 캐싱: 사용자 세션 정보를 캐시하여 서버 간 세션 공유의 효율성을 높입니다.
잘못된 캐싱 전략
- 데이터 일관성 문제: 캐시된 데이터와 데이터 소스 간의 동기화 문제로 인해 사용자가 오래된 정보를 볼 수 있습니다.
- 메모리 부족: 캐시 용량 관리를 잘못하여 필요 이상의 데이터를 캐시하게 되면, 메모리 부족 문제가 발생할 수 있습니다.
- 성능 저하: 캐시 히트율이 낮거나 캐시 갱신 비용이 높은 경우, 오히려 성능 저하를 유발할 수 있습니다.
캐싱의 효과적 관리
캐싱의 효과를 극대화하기 위해서는 캐시의 크기와 수명 주기를 적절히 관리해야 합니다. 다음은 캐싱을 효과적으로 관리하는 몇 가지 방법입니다:
- 캐시 크기 제한: 캐시의 크기를 제한하여 메모리 사용을 최적화하고, 시스템의 전반적인 안정성을 유지할 수 있습니다. LRU(Least Recently Used) 알고리즘과 같은 정책을 사용하여 오래되거나 자주 사용되지 않는 데이터를 자동으로 제거할 수 있습니다.
- 데이터 만료 정책: 데이터의 중요성과 변동성에 따라 적절한 만료 시간(TTL, Time-To-Live)을 설정합니다. 이를 통해 항상 최신 데이터를 제공할 수 있으며, 불필요한 데이터로 인한 캐시 오버헤드를 줄일 수 있습니다.
- 적극적인 무효화: 데이터가 변경될 때 캐시도 즉시 업데이트하거나 무효화하여 데이터 일관성을 유지합니다. 이 방법은 데이터의 신선도가 중요한 경우에 특히 유용합니다.
Redis 소개
자 이제 레디스에 대해 소개하겠습니다.
레디스는 위에서 본 캐싱이란 큰 개념에 속해있는 오픈소스 입니다.
레디스도 이러한 캐싱 기능을 제공하는 여러 도구중 하나로
Key, Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형(NoSQL) 데이터 베이스 관리 시스템(DBMS) 입니다.
즉 레디스는 사용에 따라 데이터베이스 용도로 사용할수도있고 Cache Server로도 사용할수있습니다.
출처 https://www.alibabacloud.com/blog/the-mechanism-behind-measuring-cache-access-latency_599384
사진을 보시면 컴퓨터 아키텍처에서
L1 Cache는 CPU에 가장 가까워 엑세스시간이 가장 빠릅니다. 대신에 용량이 작습니다.
DISK DRIVE 는 용량은 크지만 CPU에서 가장 멀어 엑세스시간이가장 느리다는것을 알수있습니다.
아무리 빠른 SSD를 사용한다고 쳐도 인메모리보단 속도가 큰차이가 납니다!
구조#1 Look-aside Cache 작동 원리
- 데이터 요청: 애플리케이션은 필요한 데이터를 얻기 위해 먼저 캐시를 조회합니다.
- 캐시 확인:
- 캐시 히트: 요청된 데이터가 캐시에 존재하면, 캐시에서 데이터를 바로 가져와 사용합니다.
- 캐시 미스: 캐시에 데이터가 없으면, 데이터베이스나 다른 영구 저장소에서 데이터를 검색합니다.
- 데이터 저장 및 반환: 캐시 미스의 경우, 애플리케이션은 검색된 데이터를 캐시에 저장한 후 요청한 클라이언트에게 데이터를 반환합니다. 이 후속 요청에서는 데이터가 캐시에 있으므로, 데이터 소스에 다시 접근할 필요 없이 빠르게 데이터를 제공할 수 있습니다.
**Look-aside Cache의 장점과 단점**
장점:
- 성능 향상: 자주 사용되는 데이터를 빠르게 접근할 수 있어 애플리케이션의 성능이 향상됩니다.
- 부하 감소: 데이터 소스에 대한 부하가 감소하여 전체 시스템의 효율성이 증가합니다.
- 유연성: 애플리케이션 코드 내에서 캐싱 로직을 관리할 수 있어 캐싱 전략을 쉽게 조정할 수 있습니다.
단점:
- 개발 복잡성: 애플리케이션 코드 내에서 캐싱 로직을 직접 관리해야 하므로 개발과 유지보수의 복잡성이 증가할 수 있습니다.
- 데이터 일관성: 데이터 소스의 데이터가 변경될 때 캐시를 수동으로 무효화하거나 업데이트해야 합니다. 이 과정에서 데이터 일관성을 유지하는 것이 도전적일 수 있습니다.
**결론**
Look-aside cache는 애플리케이션의 성능을 향상시키고 데이터 소스의 부하를 줄이는 효과적인 방법입니다. 그러나, 캐싱 로직을 효율적으로 관리하고 데이터 일관성을 유지하기 위한 추가적인 노력이 필요합니다. 이 패턴은 캐싱 전략을 설계할 때 성능과 개발 복잡성 사이의 균형을 고려해야 하는 중요한 요소입니다.
구조#2 Write-back 작동 원리
Write-back (또는 Write-behind) 캐싱 전략에서는 데이터가 먼저 캐시에 쓰이고, 나중에 비동기적으로 영구 저장소에 저장됩니다. 이 방식은 쓰기 연산의 성능을 크게 향상시키지만, 데이터를 영구 저장소에 저장하기 전까지는 데이터 손실의 위험이 존재합니다.
**Write-back Cache의 장점과 단점**
장점:
- 성능 향상: 데이터 쓰기 연산이 캐시에 바로 이루어지기 때문에, 쓰기 지연이 거의 없습니다. 이는 특히 쓰기 연산이 빈번한 애플리케이션에서 성능을 크게 향상시킬 수 있습니다.
- 영구 저장소 부하 감소: 쓰기 연산이 비동기적으로 영구 저장소에 반영되므로, 영구 저장소에 대한 즉각적인 부하가 줄어듭니다. 이는 저장소의 처리 능력을 보다 효율적으로 사용할 수 있게 합니다.
- 배치 쓰기 최적화: 비동기적인 데이터 동기화는 배치 쓰기를 가능하게 하여, 네트워크 사용과 저장소 처리에 대한 효율성을 더욱 높일 수 있습니다.
단점:
- 데이터 손실 위험: 시스템 장애가 발생하여 캐시의 데이터가 영구 저장소에 저장되기 전에 손실될 경우, 데이터 복구가 불가능할 수 있습니다. 이는 데이터의 안전성을 위협하는 중대한 단점이 될 수 있습니다.
- 복잡한 데이터 일관성 관리: 데이터가 비동기적으로 영구 저장소에 반영되므로, 캐시와 저장소 간의 일관성을 관리하는 것이 더 복잡해집니다. 이는 데이터 일관성을 유지하기 위한 추가적인 메커니즘이 필요함을 의미합니다.
- 복잡한 구현과 관리: Write-back 캐싱은 구현이 복잡하며, 데이터 동기화를 관리하기 위한 추가적인 로직이 필요합니다. 또한, 캐시 데이터의 안전성을 보장하기 위한 메커니즘(예: 정기적인 데이터 동기화, 데이터 손실 방지를 위한 백업 솔루션 등)도 필요할 수 있습니다.
**결론**
이방번은 WEB SERVER에있는 데이터를 Cache에만 저장합니다.
그후 어떠한 시간에 맞춰 DB에 저장된 데이터를 삭제하고 Cache에있는 데이터를 DB에 업데이트를 합니다. DB의 부하가 줄어들고 성능이 더빠르다는 장점이있습니다.
하지만 캐시서버가 죽는다면 데이터의 유실로인한 단점이있습니다.
레디스의 데이터 구조
자 이제 본론으로들어와
캐시서버를 레디스로 구성하기위해 레디스의 기본 구조와 데이터 타입에 대해 알아보겠습니다.
- 인-메모리 데이터 스토어: Redis는 모든 데이터를 메모리에 저장하고 관리합니다. 이를 통해 빠른 읽기와 쓰기 속도를 제공합니다.
- 키-값 저장소: 데이터는 키-값 쌍으로 저장됩니다. 각 키는 고유하며, 이를 통해 관련 값에 빠르게 접근할 수 있습니다.
- 지속성 옵션: Redis는 데이터를 디스크에 저장하는 다양한 옵션을 제공하여, 메모리에만 데이터를 보관하는 것의 단점인 데이터 유실 위험을 줄입니다.
- 다양한 데이터 구조 지원: Redis는 문자열, 리스트, 세트, 해시, 정렬된 세트 등 여러 데이터 구조를 지원합니다.
- 다중 언어 지원: Redis는 C, Java, Python, PHP 등 다양한 프로그래밍 언어에서 사용할 수 있는 클라이언트 라이브러리를 제공합니다.
레디스는 기본적으로 자료구조를 제공합니다.
문자열(Strings)
- 가장 기본적인 데이터 타입으로, 텍스트나 이진 데이터를 저장할 수 있습니다.
- 사용 예시: 사용자의 이메일 주소나 단일 숫자 데이터(예: 사용자의 점수), 작은 이미지나 파일(직렬화 가능한 경우) 등을 저장할 수 있습니다.
java의 MAP Entry
와 유사한 개념
SET user:123:email "user123@example.com"
user:123:email이라는 키에 user123@example.com이라는 값을 저장
리스트(Lists)
- 순서가 있는 문자열의 집합을 저장하는 데 사용되며, 삽입 순서대로 요소를 보관합니다.
- 사용 예시: 최근 등록된 댓글, 작업 큐 등 순서가 중요한 시나리오에 활용될 수 있습니다.
java의 ArrayList
또는 LinkedList
와 유사한 개념
RPUSH system:logs "User logged in"
RPUSH system:logs "User viewed profile"
LRANGE system:logs 0 -1 //system:logs 리스트의 첫 번째 요소(인덱스 0)부터 마지막 요소까지 모든 요소를 반환
log:
1) "User logged in"
2) "User viewed profile"
Redis 리스트는 시간 순서대로 로그나 이벤트를 기록하는 용도로 사용될 수 있습니다. 예를 들어, 시스템의 이벤트 로그를 저장하고, 최신 이벤트부터 순차적으로 조회할 수 있습니다.
세트(Sets)
- 중복을 허용하지 않는 문자열의 집합입니다. 세트 내의 요소는 순서를 가지지 않습니다.
- 사용 예시: 태그, 친구 목록 등 중복된 값을 허용하지 않는 데이터를 관리할 때 유용합니다.
- 이는 Java의
HashSet
클래스와 비교할 수 있습니다.HashSet
역시 중복을 허용하지 않으며 요소의 순서를 보장하지 않는 컬렉션입니다. 두 구조 모두 데이터의 중복을 방지하고, 데이터의 존재 유무를 빠르게 검사하는 데 유용합니다.
SADD post:123:tags "redis"
SADD post:123:tags "database"
SADD post:123:tags "redis"
SMEMBERS post:123:tags //위의 명령을 순서대로 실행한 후에 SMEMBERS post:123:tags 명령을 사용하여 post:123:tags 세트의 모든 요소를 조회하면, 결과는 다음과 같이 나옵니다:
1) "redis"
2) "database"
해시(Hash
- 키-값 쌍의 집합을 저장합니다. 하나의 해시는 여러 필드를 가질 수 있으며, 각 필드는 고유한 키를 가집니다.
- 사용 예시: 객체를 저장할 때 유용합니다. 예를 들어, 사용자 ID를 키로 하고, 사용자의 이름, 이메일 등을 필드로 가지는 사용자 프로필 정보를 저장할 수 있습니다.
Java에서의 HashMap
이나 다른 프로그래밍 언어의 해시 테이블 구현과 유사합니다. Redis 해시는 주로 객체를 저장하거나, 복잡한 데이터 구조를 효율적으로 관리할 필요가 있을 때 사용됩니다.
프로필 정보 저장:
HSET user:123 name "John Doe"
HSET user:123 email "john.doe@example.com"
HGETALL user:123 //HGETALL 명령은 지정한 해시의 모든 필드와 값을 반환합니다.
1) "name"
2) "John Doe"
3) "email"
4) "john.doe@example.com"
정렬된 세트(Sorted Sets
- 세트와 유사하지만, 각 요소가 부가적인 점수를 가지며 이를 기반으로 정렬됩니다.
- 사용 예시: 리더보드, 우선 순위가 있는 작업 목록 등에서 활용될 수 있습니다. 각 사용자의 점수를 기록하여 높은 점수부터 낮은 점수 순으로 사용자를 정렬할 수 있습니다.
Java의 TreeSet
이나 PriorityQueue
와 비교될 수 있지만, 주요 차이점은 Redis의 정렬된 세트가 분산 데이터 시스템 내에서 네트워크를 통해 접근되며, 자동으로 정렬된 상태를 유지
점수 추가: ZADD
명령은 game:leaderboard
라는 정렬된 세트에 사용자와 그들의 점수를 추가합니다
ZADD game:leaderboard 5000 "user1"
ZADD game:leaderboard 7000 "user2"
ZREVRANGE game:leaderboard 0 -1 WITHSCORES
이 명령을 실행하면 가장 높은 점수를 가진 사용자부터 시작하여, 리더보드에 있는 모든 사용자의 점수를 높은 점수부터 낮은 점수 순으로 조회할 수 있습니다.
그럼 자바에도 동일한 자료구조를 가지고있는데 왜 레디스에 저장하는겨?
우선 서버가 여러대인 경우 일관성이 깨지게 됩니다.
레디스는 Single Threaded 이다.
즉 레디스가 동시에 처리하는 쓰기 할수있는 명령 개수는 1개밖에안된다.
그 작업이 단순한 get /set작업이면 초당 10만개까지는 처리 가능합니다.( cpu 성능에따라 상이)
하지만 그작업이 초당 1초가 걸리는 작업이라면?
타임아웃 설정시 서버는 바로 죽어버릴것이다.
그래서 모든 키를 조회한다던가. (몇 십만개이상되는)
데이터를 한번에 다 날리는 작업을한다는가
이러한 작업을하면 아마 서버가 바로 죽어버릴것이다.
**해결 방안**
1. 파이프라인(Pipelining) 사용
Redis의 파이프라인 기능을 사용하면, 여러 명령을 한 번에 서버로 전송하고, 결과를 일괄적으로 받아올 수 있습니다. 이는 네트워크 지연 시간을 최소화하고, 명령 처리량을 증가시킵니다. 단, 각 명령의 처리 시간이 중요한 영향을 미치지 않도록 주의해야 합니다.
2. 데이터 처리 작업 분산
대규모 데이터 처리 작업은 Redis의 다른 인스턴스나 데이터베이스로 분산시키는 것이 좋습니다. 예를 들어, 대량의 데이터 삭제 작업은 별도의 배치 처리 시스템에서 수행할 수 있으며, 필요한 경우 Redis와 동기화할 수 있습니다.
3. 스캔 명령 사용
KEYS
명령어 대신 SCAN
명령어를 사용하여 데이터 세트를 순회합니다. SCAN
명령은 데이터베이스를 점진적으로 순회하며, 서버의 부하를 크게 증가시키지 않습니다.
4. 타임아웃 및 성능 모니터링
클라이언트와 서버 모두에서 적절한 타임아웃 설정을 구성하여, 예상치 못한 긴 작업 시간으로 인한 시스템 중단을 방지합니다. 또한, Redis의 성능 모니터링 도구를 사용하여 시스템의 부하를 지속적으로 관찰하고, 필요한 경우 조치를 취할 수 있습니다.
5. Redis 클러스터 사용
Redis 클러스터를 사용하여 데이터와 부하를 여러 노드에 분산시키면, 단일 노드에 대한 부하를 줄이고 시스템의 가용성을 향상시킬 수 있습니다. 이는 특히 대규모 데이터 세트와 고부하 환경에서 유용합니다.
실제 프로젝트에 레디스를 적용해보고 성능 개선 눈으로 확인~
다음글을 확인해주세여 ✔😃
출처 : https://youtu.be/mPB2CZiAkKM?si=NxojZaARQM1pCRPu
우아한테크 세미나 영상 일부 발췌