Computer Science

[TIL] 캐시와 Redis (Redis 특징과 장점, 사용 시 주의할 점)

쉬지마 이굥진 2024. 5. 28. 10:01

프로젝트에서 성능 개선을 위해 캐시를 이용해야 할 일이 생겼다. 프로젝트 적용에 앞서 캐시란 뭔지, 왜 써야하는지 등 캐시에 대한 것들과 Redis의 간결한 특징을 정리해보려 한다.

 

📌캐시(Cache)란?

캐시란, 데이터를 빠르게 읽고 처리하기 위해 (속도가 빠른 메모리를 활용하여) 미리 데이터를 저장해두는 임시 장소이다.

 

계산된 값을 임시로 저장해두고, 동일한 계산 / 요청 발생 시 다시 계산하지 않고 저장된 값을 바로 사용한다고 생각하면 된다. 이 때 사용하는 임시 저장소를 '캐시'라 하고, 이런 기술을 '캐싱'이라고 한다.

 

📌캐시의 활용

캐시는 컴퓨터 공학 전반의 매우 다양한 곳에서 쓰이는데, BE 개발자에게 익숙한 것 두 가지를 뽑아보면 DP와 JPA를 예로 들 수 있을 것 같다.

Dynamic Programming의 앞글자를 따서 DP라고 하는데 알고리즘 문제를 풀면서 많이 들어봤을 것이다. 이 DP에서는 이전의 값을 저장할 때 캐시의 역할을 사용한다.

JPA의 영속성 컨텍스트도 내부적으로 직접 데이터베이스에 접근하지 않고, 1차 캐시에 먼저 접근해 캐시에서 데이터를 가져와서 성능적인 이점을 가져간다. 

CDN에서도 캐시가 사용된다. 이미지나 동영상 같은 큰 파일들은 CDN이라는 곳에 캐싱되는데, 용량이 큰 파일을 요청했을 때, 사용자와 서버의 물리적인 거리가 멀다면 해당 파일은 네트워크를 따라 이동하게 되면서 파일 전송에 많은 시간이 소요되게 된다. 이때 원본 서버의 파일들을 미리 PoP 서버에 저장하여 사용자 요청 시 사용자와 가까운 PoP 서버에서 응답하도록 하면 네트워크 지연 시간을 줄일 수 있다.

 

우리가 Redis를 이용해서 백엔드 서비스 구현 시에 사용하게 될 캐싱은 '어플리케이션 캐싱'이라고 하는데, 어플리케이션에서 데이터나 계산 결과를 캐싱해서 반복적인 작업을 최적화하는 것을 의미한다.

 

📌캐시는 왜 써야할까?

파레토의 법칙이란 말을 들어본 적이 있을 것이다. (필자는 정보처리기사를 공부하면서 배웠던 기억이 남) 파레토 법칙을 예로 들면 캐시를 써야하는 이유에 대해서 이해하기가 쉬워진다.

파레토의 법칙

은 전체 결과의 80%가 전체 원인의 20%에서 비롯된다는 개념이다. 특정 소수의 요소가 대부분의 결과를 초래한다는 것을 설명하는데 사용되는데, 즉 이 법칙에 따르면 캐시에 자주 사용되는 약 20%의 데이터를 미리 캐싱해 둔다면 효과적인 성능 향상을 이끌어 낼 수 있다는 것을 의미한다.

 

 

📌캐시 히트와 캐시 미스

캐시에 대해 배우면서 꼭 알아두어야 할 개념이 캐시 히트(Cache Hit)와 캐시 미스(Cache Miss) 이다.

 

캐시 히트

캐시 히트는 캐시 서버에 특정 키를 가진 캐시를 요청했을 때, 정상적으로 응답이 오는 경우, 즉 캐시에 원하는 데이터가 존재하는 경우를 말한다. 이 경우 해당 데이터를 바로 반환할 수 있다.

 

캐시 미스

캐시 미스는 키가 잘못되었거나 해당 데이터가 이미 만료되어 데이터를 응답하지 못하는 경우, 즉 캐시에 원하는 데이터가 없는 경우를 말한다. 이 경우 DB로 직접 찾아가서 데이터를 찾아와 반환해야 한다.

 

📌캐시 전략 패턴

캐시 히트와 캐시 미스를 알았다면 이제 캐시 전략 패턴과 그 장단점에 대해서도 알아보자.

캐시 전략 패턴이란 '실제 캐시를 도입할 때 상황에 맞추어 적용될 수 있는 전략 패턴'이다. 크게는 읽기 전략과 쓰기 전략으로 나뉠 수 있는데, 읽기 전략은 Look Aside 패턴과 Read Through로 나누어지고, 쓰기 전략은 Write Around 패턴, Write Back 패턴, Write Through 패턴으로 나뉜다.

 

[ 읽기 전략 ]

Look Aside 패턴

 

Look Aside 패턴은 캐시의 데이터가 없을 때 DB를 조회해서 데이터를 읽어오는 전략이다.

  1. 애플리케이션 (클라이언트 + 서버)은 원하는 데이터를 캐시에서 가져올 수 있다. (데이터가 있다면, 즉 캐시 히트라면)
  2. 데이터가 없다면 (캐시 미스라면) DB를 본다. 즉 데이터베이스에서 데이터를 가져온다.
  3. 캐시에 올려놓고 사용한다.

이 패턴의 장점은 캐시에 문제가 생기는 경우 DB로 요청을 위임해서 데이터를 가져올 수 있다는 점이다.

반대로 DB와 캐시를 직접 잇는 연결점이 없기 때문에 (동기화가 되지 않았기 때문에) 데이터 정합성 유지가 어렵다는 것, 또한 첫 번째 조회를 할 때는 항상 데이터베이스로부터 데이터를 가져와서 캐시에 올려놓고 사용을 할 수 밖에 없기 때문에 DB에 부하가 오기 쉽다는 것이 단점이다.

 

 

Read Through 패턴

Read Through 패턴은 항상 캐시가 직접 DB에서 데이터를 가져오는 전략, 즉 항상 캐시를 통해서 읽는 전략이다.

  1. 애플리케이션이 캐시 스토어로부터 데이터를 읽어온다. (캐시 히트라면)
  2. 캐시 미스라면 데이터베이스로부터 캐시가 데이터를 직접 가져온 후 그 데이터를 애플리케이션이 읽게 된다.

Look Aside 패턴과 상반된 느낌인데, 이 전략의 장점은 캐시와 DB 간에 이런 연결점이 있기 때문에 항상 데이터의 정합성이 보장된다고 할 수 있다. 하지만 여기서 캐시가 죽어버리면 애플리케이션에서도 문제가 발생한다는 것이 단점이다. (3)

Read Through 패턴의 단점

 

[ 쓰기 전략 ]

Write Around 패턴

Write Around 전략은 캐시를 우회해서 직접 쓰는 전략을 의미한다.

  1. 애플리케이션이 직접 DB에 바로 쓴다.
  2. 캐시 미스가 발생한다면 데이터를 캐시 스토어에 쓴다.

이 전략의 장점 캐시를 거쳐서 쓰지 않고 데이터베이스에 바로 써서 성능이 좋다(빠르다)는 점과, 불필요한 데이터를 캐시에 올리지 않고 바로 쓰기 때문에 리소스를 아낄 수 있다는 점이다.

반면 캐시와 DB 사이 연결점이 없기 때문에 데이터 정합성 유지가 어렵다는 점이 단점이다.

 

 

Write Back 패턴

Write Back 전략은 캐시에 데이터를 미리 한꺼번에 써 놓고 나중에 DB에 쓰기 작업을 진행하는 전략을 의미한다.

  1. 먼저 캐시에 많은 양의 데이터를 쓴다.
  2. 그 후 DB에 쓰기 작업을 진행하는데, 이 때 스케쥴링 방식을 사용한다. 따라서 일정 시간이 지난 후에 한꺼번에 많은 양의 데이터를 한 번의 쓰기 요청으로 해결할 수 있다.

이렇게 원래 많은 양의 데이터를 쓰게 된다면 insert문이 여러개 나가서 성능적으로 문제가 생길 수 있는데, 하나의 insert문으로 묶어서 데이터를 처리하게 되니까 성능적인 이점을 가져갈 수 있다.

그러므로 장점은 쓰기 횟수 비용을 줄일 수 있다는 것이고, 반면 만약 캐시 스토어에만 데이터를 써 놓은 상태에서 캐시가 죽어버리게 된다면 데이터가 DB 까지 직접 쓰여지지 않을 것이다. 이러한 데이터 유실 문제가 생길 수 있다는 것이 단점이다.

 

 

Write Through 패턴

Write Through 전략은 항상 캐시를 통해서 쓰기를 진행하는 패턴을 의미한다.

  1. 캐시를 통해서 먼저 쓰고,
  2. 바로 데이터베이스에 쓰기 작업을 진행한다.

이런 과정을 거치면 항상 캐시를 거치고 데이터베이스로 가기 때문에, 데이터의 정합성은 보장될 수 밖에 없다는 것이 장점이다. 반면 필수적으로 무조건 두 번의 쓰기가 항상 진행되기 때문에 성능을 고려하면서 사용해야 한다는 것이 단점이다.

 

 

📌캐시 사용 시 주의사항 !! 

그렇다면 위 내용들을 종합해 봤을 때, 캐시를 사용할 때는 어떤 점을 주의하며 사용해야 할 지 정리해봤다.

  • 자주 사용되면서 변경이 되지 않는 데이터
  • 유실되어도 크게 문제가 없는 데이터
  • 데이터베이스와 함께 사용할 때 데이터 정합성 문제 고려

캐시에 대해서는 어느 정도 학습했으니 캐시를 구현하는 방법 중 한 가지인 Redis에 대해서도 알아보겠다.

✏️Redis 란?

  • Remote Dictionary Server의 약자
  • 다수의 서버를 사용하는 분산 환경의 서버가 공통으로 사용할 수 있는 해시 테이블로 생각하면 됨
  • Remote Server라는 의미는, redis가 각각의 서버 안에 로컬하게 존재하지 않고, 다수의 서버에서 공통적으로 사용할 수 있도록 원격에 존재한다는 의미
  • Dictionary라는 의미는, 해시맵과 같이 key-value 형태로 상수의 시간 복잡도로 사용이 가능하다는 의미

위 4문장을 다시 간단히 줄여보면!

Redis란 외부사전 형태로 저장하는 서버이며, 메모리에 저장하는 key-value 기반의 NoSQL DBMS 라고 요약할 수 있겠다.

 

또 Redis는 (앞서 말한 것처럼) 메모리에 저장되어 액세스가 빠르다는 점에서 캐싱 용도로 많이 사용되지만, 임시 작업 큐, 실시간 채팅, 메시지 브로커 등 다른 용도로도 사용됨을 알고 있어야 겠다 👍

 

 

✏️Redis 의 특징

  • 인메모리
    • 모든 데이터를 RAM에 저장한다! (백업/스냅샷 제외)
  • 싱글 스레드 (Single Threaded)
    • 단일 스레드에서 모든 task를 처리한다.
    • 이러한 특징으로 Race Condition이 거의 발생하지 않는다.
멀티 스레드로 데이터베이스 엔진을 구성했을 때의 장점이 분명 존재하지만, 멀티스레드는 프로그램의 복잡도를 증가시킬 수 있다!
👉 과감하게 단일스레드를 사용하는 단순한 디자인을 채택했다.
👉 이런 단순함이 레디스의 장점이 되어 레디스는 뛰어난 성능을 자랑한다.
👉 곧 개발자가 사용만 잘하면 사이드이펙트가 거의 없는 매우 안정적인 기술이라고도 할 수 있다.

* Race Condition: 두 개 이상의 프로세스가 동시적으로 하나의 리소스에 접근하려해서 서로 경쟁하는 상태를 의미
  • 클러스터 모드 지원
    • 다중 노드에 데이터를 분산 저장해서 안정성 & 고가용성을 제공한다.
  • 영속성 (Persistence)
    • 인메모리 데이터베이스라는 특성상 주로 휘발성 데이터를 저장하지만, RDB와 AOF라는 옵션을 통해 데이터를 안전하게 영속성으로 관리할 수도 있다.
    • 따라서 서버에 치명적인 문제가 발생하더라도 복구가 가능하다.

 

✏️영속성 옵션

상단의 레디스 특징에서 언급한 것 처럼, Redis는 메모리에 저장되어 데이터가 당연히 휘발 될 것이라고 생각할 수 있는데!

저장된 데이터를 디스크에 영속화 시킬 수 있는 RDB와 AOF 옵션이 존재한다. 

레디스는 주로 캐시로 사용되기 때문에 기본적으로 손실되어도 무방한 데이터를 기록해야 한다.
하지만 실제 서비스를 운영하다 보면, 캐시라 할지라도 데이터가 손실됐을 때 서비스에 지연이 발생하거나 장애 상황으로 이어지기도 한다.
👉 Redis는 안정적인 캐시 서버 운영을 위해 데이터 손실을 방지하기 위한 옵션을 제공하는 것이다.

RDB (Redis Database)

  • 특정한 간격으로 데이터의 스냅샷을 남기는 방식이다.
  • 장애가 발생했을 때 특정 시점에 스냅샷으로 빠르게 캐시를 되돌릴 때 (재난 복구), 동일한 데이터를 가진 캐시를 복제할 때 주로 사용한다.
  • 장점
    • 데이터를 모두 압축하여 저장하기 때문에 AOF보다 크기가 작다.
    • 로딩/복구 속도가 빠르다.
  • 단점
    • 백업 중 서버가 다운될 경우 최신 데이터가 유실될 가능성이 있다.
    • 또한 스냅샷 생성 중에 전체적인 레디스 서버의 성능 저하가 발생하여 클라이언트 요청 처리에 지연이 발생할 수 있다.

 

AOF (Append only file)

  • 모든 Write 작업(입력/수정/삭제)을 모두 LOG 파일에 기록하는 방식이다.
  • 장점
    • 저장 속도가 빠르다.
    • RDB와 달리 실시간 데이터 백업이 가능해서 데이터 손실이 거의 일어나지 않는다.
  • 단점
    • 장애 상황 복구 시 Write 작업을 다시 적용(모든 로그를 다시 적용)하기 때문에 스냅샷 방식(RDB)보다 복구 속도가 느리다.
    • 명령 실행 기록을 모두 기록하기 때문에 파일 크기가 크다.

 

✏️Redis의 장점

  • 높은 성능 ⭐⭐⭐
    • 모든 데이터를 메모리에 저장하기 때문에 매우 빠른 읽기/쓰기 속도 보장
  • 다양한 Data Type 지원

    • 레디스의 데이터 타입은 직관적인 인터페이스로 구현되어 있고 전반적인 성능이 매우 뛰어나다.
    • 이런 데이터 타입을 잘 활용해서 다양한 기능을 구현 가능하다.
    • Key는 기본적으로 String으로 제공되고, Value는 다양한 타입을 가질 수 있다.
  • 클라이언트 라이브러리
    • python, java, js 등 다양한 언어로 작성된 클라이언트 라이브러리를 지원한다.
    • 따라서 백엔드와 쉽게 연동 가능하다.
  • 다양한 사례 / 강한 커뮤니티
    • 레디스를 활용해서 비슷한 문제를 해결한 사례가 많고, 커뮤니티의 도움을 받기 쉽다.

 

✏️Redis 사용 시 주의할 점

  • 데이터 타입에 따른 적절한 자료 구조 사용
  • O(N) 명령어 주의
    • ex) KEYS, SMEMBERS, HGETALL, SORT 명령어 등
레디스의 대부분의 명령어는 O(1) 시간복잡도를 가져서 매우 빠르게 동작한다. 일부 명령어의 경우 O(N) 시간복잡도를 갖는데 레디스는 단일 스레드에서 동작하기 때문에 모든 클라이언트의 요청을 순차적으로 처리한다.

오래 걸리는 O(N) 명령어 수행 시, 다른 요청들이 대기하게 되고 👉 (하나의 레디스 클러스터를 공유하고 있는 경우) 전체적인 어플리케이션 성능 저하를 유발할 수 있다. 
  • 메모리 관리가 필수!
    • Redis의 경우 인메모리 데이터 스토어이므로 메모리 관리가 필수적이다.
    • 메모리 특성 상, 메모리 단편화 발생
    • 👉 RSS 모니터링이 필요하다.
** RSS 모니터링 **
Resident Set Size : 실제 물리 메모리 사용량
  • Redis의 도입 목적을 분명히 하자
    • 도입할 때 캐시용인지 저장소용인지 목적을 분명히 하는게 좋다.
    • 영속화 기능 (RDB, AOF)이 장애 발생 가능성이 높다고 알려져 있다. 레디스에 저장되었던 데이터가 없어져도 문제가 없는지, 일부 값이 유실되어도 치명적이지 않은지 등을 판단해서 캐시용으로만 사용한다면 👉  Persistence 기능 OFF를 권장한다.

 

🍀마치며

이렇게 Redis에 1차캐싱을 시키기 전 캐시와 Redis는 뭔지에 대해서 자세히 학습해보는 시간을 가졌다. 그 동안 Redis Redis 하면서 대체 레디스가 자세히 뭔데? 하는 막연한 생각만을 갖고 있었는데 이번 기회를 통해 자세히 배우고 또 직접 적용해 볼 기회가 생겨서 좋다. 

적용 전 어떤 데이터가 캐시의 대상이 될 수 있는지, 어떤 전략을 선택할지 ... 등등 고려해 볼 점이 많지만 잘 고려해서 프로젝트에 적용해봐야겠다. 그럼 캐싱하러 가볼까나


References

https://www.youtube.com/watch?v=tVZ15cCRAyE&list=PLgXGHBqgT2TvpJ_p9L_yZKPifgdBOzdVH&index=47

https://www.inflearn.com/course/%EC%8B%A4%EC%A0%84-redis-%ED%99%9C%EC%9A%A9

https://ko.wikipedia.org/wiki/%ED%8C%8C%EB%A0%88%ED%86%A0_%EB%B2%95%EC%B9%99