Redis(Remote Dictionary Server), MemCached

김민우·2023년 12월 13일
0

잡동사니

목록 보기
17/22

DB에서 자주 조회해야 하는 데이터가 있다고 생각해보자. 매 요청마다 해당 데이터를 DB에서 가져가 준다면 엄청난 성능 저하가 발생할 것이다.

만약, 게임 사이트 메인 페이지에서 레벨이 높은 100명의 유저 정보를 보여준다고 생각해보자. 유저 정보는 변동성이 상대적으로 적을 것이다. 이렇다면 데이터를 창고(DB)에서 미리 꺼내놓고 요청마다 꺼내놓은 데이터를 그대로 준다면 응답 속도가 훨씬 좋아질 것이다.

이처럼 자주 조회하면서 변동성이 적은 데이터를 담아둘 수 있는 공간은 필수적이다. 캐시(Cache)를 활용한다면 이를 구현할 수 있다.

대표적으로 많이 사용하는 캐시 시스템으로 Redis와 MemCached가 있다. 이를 살펴보고 장/단점을 알아보자.

Redis


인메모리 구조(In-Memory)

인메모리 구조란 RAM에 데이터를 올려 사용하는 방법을 말한다. Redis는 인메모리 구조를 사용하여 상대적으로 속도가 빠르나 용량이 적다.

Key-Value 형태의 NoSQL

Dictionary 라는 말에서 알 수 있듯 Redis는 사전(Key-Value) 형태의 데이터 저장소이다.

다양한 형태의 데이터 구조 지원

다양한 형태의 데이터 구조를 지원하긴 하나 복잡한 데이터를 다루기엔 적합하지 않다.

캐시 데이터베이스 서버(Cache DB Server)에 유리

위 2가지 이유로 인해 캐시 데이터베이스 서버에 적합하다. 앞서 언급한 게임 사이트의 상위 100명 유저를 응답하는 트래픽 처리에 매우 유리하다.

메모리 관리

Redis는 메모리에 데이터를 저장하므로 물리적인 메모리(RAM) 용량보다 더 많은 용량을 사용하면 문제가 발생한다. 즉, 메모리 관리가 매우 중요하다

Memory Swap

RAM 용량이 부족한 경우 디스크에 SWAP 공간을 만들어 임시 저장하는 방식이다.

  • 매모리 부족을 해결하기 위한 좋은 방법
  • SWAP하는 동안 레이턴시(latency, 지연시간) 발생 (치명적인 단점)

보통 Redis가 느려진 상황이라면 RAM 공간이 부족하여 메모리 SWAP이 발생한 경우다. 만약 사용중인 Redis에서 SWAP이 발생했다면 계속해서 SWAP이 발생한다. 이 경우 프로세스를 재실행하여 해결이 가능하다.

메모리 단편화 (혹은 파편화)

RAM에서 사용 가능한 메모리가 충분히 존재하지만 메모리 공간이 작은 조각으로 나뉘어 할당이 불가능한 상태를 말한다.

Redis는 jmalloc 을 사용하여 메모리를 할당하는데 jmalloc 의 Memory-page-size 를 4096 byte로 잡으면 1 byte만 사용해도 4096 byte 만큼을 할당한다. 이는 결국 메모리 낭비로 이어지고 정확한 메모리 사용량을 파악하기 힘들다는 단점이 있다.

따라서, 다양한 크기의 데이터 사용을 자제하고 유사한 크키의 데이터를 사용하자. (데이터 크키의 통일성이 필요)

싱글 스레드(Single thread) 사용

Redis의 가장 큰 특징이다. 동시에 처리할 수 있는 명령어의 개수가 한 번에 하나다. (Redis가 C언어로 작성되있어 그런거 같음...) 덕분에, 데이터 손실없이 수평으로 스케일링할 수 있다.

참고
실제 명령어 동작이 패킷(packet)단위로 전달되고 이게 쌓여 command(명령)가 완성되면 명령을 실행한다.

이러한 특징 때문에 O(n) 관련 명령어를 가급적이면 지양하는게 좋다. 꼭 필요한 경우가 아니라면 사용하지 말자.

참고) Redis에서 대표적인 O(n) 명령어

  • keys
  • delete collection
  • get all collection
  • flushAll, flushDB

디스크 영속화(Disk Persistence)

MemCached와 차이점이라 볼 수 있다. 특정 데이터를 디스크에 영구 저장할 수 있다. AOF, RDB Snapshot 2가지 방식이 존재한다.

메모리만 사용한다면 정합성을 맞추기가 어려운데 디스크 영속화를 통해 이를 보장해준다. 그러나, 성능은 메모리만 사용할 때가 훨씬 좋다.

복제(Replication)

Redis 서버는 영속성고가용성을 높이기 위해 클러스터링 방식으로 많이 사용한다. Master-Replica 형태로 구성되며, Master의 데이터를 Replica로 복제한다.

영속성

  • 데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터 특성

고가용성

  • 서버, 네트워크, 프로그램 등의 정보 시스템이 상당히 오랜 기간동안 지속적으로 정상 운영이 가능한 성질

클러스터링

  • 유사한 성격을 가진 개체를 묶어 그룹으로 구성하는 것

비동기 복제(Async Replication)으로 인해 복제 시 동시성 문제가 발생하여 렉이 발생할 수 있다.

추가로 Replicaof 명령어 사용시 fork가 발생하므로 메모리 여유 공간을 반드시 확인해야 한다. (만약, Master 메모리 공간이 5GB이면 순간적으로 10GB가 필요)

트랜잭션(Transaction)

MemCache와 달리 Redis는 트랜잭션을 지원한다. MULTI 커맨드를 통해서 트랜잭션을 시작하며 EXEC로 추가 명령어를 실행한다. WATCH를 통해서 트랜잭션을 종료할 수 있다.

MemCached


인메모리 구조(In-Memory)

MemCached 또한 인메모리 구조를 사용하여 상대적으로 속도가 빠르나 용량이 적다.

Key-Value 형태의 스토리지

key-value 형태의 스토리지다. 단, NoSQL은 아니다.

String 데이터 타입만 지원

오직 String(문자열) 데이터 타입만 지원한다. 오로지 읽기 전용으로 사용되며 더 이상 가공할 필요가 없어진다. 따라서, 데이터를 저장하는데 유리하다.

작고 변동성이 없는 데이터 캐싱에 유리

내부 메모리 관리가 Redis에 비해 상대적으로 복잡하지 않아 비교적 적은 메모리를 사용한. 따라서, HTML 코드 부분같이 작고 변하지 않는 데이터를 캐싱하기 유리하다. 따라서, DB/API 통신을 줄이기 위해 데이터를 캐싱할 때 유리하다.

내부적으로 메모리 할당이 잦지 않다

Redis에 비해 slab 할당자를 사용하여 메모리 할당을 수행한다. 이는 내부 메모리를 다시 할당하지 않고 사용할 수 있다. (Redis는 jmalloc이라 사용마다 메모리를 할당한다.)

따라서, 상대적으로 메모리 할당이 잦지 않으며 트래픽이 몰려도 응답속도가 안정적이다.

멀티 스레드(Multi Thread)

멀티 스레드로 동작하여 컴퓨팅 자원을 추가함으로 스케일 업을 할 수 있다. 이로인해 캐싱된 데이터가 유실될 가능성도 존재한다.

Redis 와 MemCached 비교


이 둘의 공통점 및 차이점을 비교해보고 상대적인 장/단점에 대해 알아보자.

공통점

  • 인메모리(In-Memory) 위에서 동작하는 key-value 형태 스토리지

차이점

페이지 교체 알고리즘

  • Redis : 6가지 방법이 존재
  • MemCached : 오래된 데이터를 지우는 방식(Least Recently Used, 이하 LRU) 사용

    페이지 교체 알고리즘

  • 메모리가 가득 차 특정 페이지를 스왑 영역으로 보내야 되는 경우 사용되는 알고리즘

사용할 수 있는 자료형(타입)

  • Redis : 문자열(String), 해시(hash), 리스트(list) 등 다양한 자료형 지원
  • MemCached : 문자열(String)만 지원

데이터 저장 방식

  • Redis : 데이터를 투명하게(캡슐화) 관리 (서버가 직접적으로 데이터를 조작하여 저장)
    • 100가지 이상의 명령을 통해 데잍러르 조작 및 처리를 다른 시스템에 위임하지 않고 직접 처리

지속성

  • Redis : 디스크를 활용하여 데이터를 영구 저장할 수 있다. 하지만, 이를 사용하면 성능이 급격히 저하된다.
  • MemCached : 메모리만 사용한다.

Redis, MemCahced 뭘 사용할까?


어떤 프레임워크를 사용하는 이유를 명확히 알고 사용하는 것과 모르고 사용하는 것은 천치차이다. 프로젝트에 캐시 시스템을 도입하고자 할 때, 둘 중 뭐를 사용하는게 더 효율적일지 생각해보자.

Redis를 사용하는 이유

다양한 데이터 구조 활용

MemCached에 비해 다양한 데이터 구조를 지원한다. 복잡한 데이터를 저장하기에 유리하다.

SnapShot

SnapShot

  • 특정 시간에 데이터 저장 장치의 상태를 별도의 파일이나 이미지로 저장하는 기술
  • 이를 통해 유실된 데이터 복원과 일정 시점의 상태로 데이터 복원 가능

Redis는 특정 시점의 데이터를 Disk에 저장할 수 있으며, 장애 상황시 데이터 복구가 필요한 경우 이를 사용할 수 있다.

<단점>
단, 이로 인해 메모리를 2배 사용하는 단점이 있다. Shapshot을 뜰 때 마다 자식 프로세스를 만들고 서로 변경된 메모리 페이지를 복사해서 사용한다. Copy and Write 방식을 사용하여 자식 프로세스는 실제 메모리 양 이상의 메모리를 복사한다. 따라서, 실제로 필요한 메모리 양보다 더 많은 메모리를 사용하기에 주의해야 한다.

Copy and Write
작성 시 이전의 내용을 복사하는 기법 (e.g. Java의 String)

String x = "Hello";
String y = x;	// 같은 레퍼런스
y += " World";	// 기존 x가 참조하는 공간(버퍼)는 그대로 두고
// 해당 공간의 값을 새로운 공간 y에 복사 후 쓴다 (copy and write)
// x는 영향을 받지 않는다.

기존 "Hello"를 저장하는 버퍼가 수정되는 것이 아니라 이를 복사한 새로운 공간이 할당된다. Redis에서도 이러한 방식으로 자식 프로세스를 만들기에 조심해야 한다.

cf) Java에서도 이로 인한 가비지(Gabage)가 생기는 것을 방지하기 위해 StringBuilder 등의 유틸 클래스를 지원한다.

복제(Replication)

Mater - Replica 형태로 구성이 되어 Master의 데이터를 Replica로 복제한다. 따라서, 영속성과 고가용성을 높이기 위해 클러스터링 방식으로 많이 사용한다.

<단점>
Replicaof 명령 시 fork가 발생하여 메모리 부족으로 이어질 수 있다. (e.g. Master 메모리 공간이 5GB이면 순간적으로 10GB 필요)

트랜잭션(Transaction)

트랜잭션을 지원하여 ACID을 보장한다.

ACID

  • Atomicity(원자성) : 트랜잭션의 연산은 데이터베이스에 모두 반영되든지 아니면 전혀 반영되지 않아야 한다.
  • Consistency(일관성) : 시스템이 가지고 있는 고정요소는 트랜잭션 수행 전과 완료 후의 상태가 같아야 한다.
  • Isolation(독립성) : 둘 이상의 트랜잭션이 동시에 병행 실행되는 경우 어느 하나의 트랜잭션 실행 중에 다른 트랜잭션의 연산이 끼어들 수 없다.
  • Durablility(영속성)
    성공적으로 완료된 트랜잭션의 결과는 시스템이 고장나더라도 영구적으로 반영되어야 한다.

다른 프레임워크와 연동성

MemCached를 사용하는 이유

DB/API 통신을 줄이기 위해 데이터를 캐싱할 때 유리

메모리 할당이 잦지 않다

내부적으로 slab 명령어를 사용하므로 Redis에 비해 메모리 할당이 빈번하지 않다.

트래픽이 물려도 응답 속도 안정

메모리 할당이 잦지 않으므로 대용량 트래픽을 처리할 때 유리하다.

정적인 데이터 처리 유리

정적인 데이터를 캐싱할 때 내부 메무리 관리가 Redis만큼 복잡하지 않아 비교적 적은 메모리를 사용한다.

멀티 스레드 사용

멀티 스레드를 사용하여 멀티 프로레스 코어를 사용할 수 있다. 이로 인해 스케일업을 통해 더 많은 작업을 처리할 수 있다.

0개의 댓글