인메모리 저장소 및 캐싱 전략

남예준·2025년 10월 21일

서론

저번에 프로젝트 때문에 Redis를 찍먹한 경험이 있다. 근데 인메모리 관련 강의를 지급해주니 좋은 거 같다.

💡

Spring Boot를 사용하면서 관계형 데이터베이스를 사용했습니다.
여기의 주된 목적은 영속성 데이터,
즉 파일시스템에 데이터를 저장함으로서 서비스가 종료되어도 유지되는 데이터를 위해서 입니다.

하지만 서비스를 만들다보면 때때로 일시적인 데이터를 저장해야 하는 상황이 발생합니다.
대표적으로 로그인 정보, 장바구니와 같은 기능은 사용자의 행동에 따라 빈번하게 데이터의 수정이 발생합니다.

이런 상황에서 파일시스템에 데이터를 저장하는 것은 그 특성상 속도가 느릴 수 밖에 없습니다.
그래서 특정 기능의 구현을 위해서 파일시스템을 사용하는 데이터베이스 대신,
메모리를 사용해 데이터를 일시적으로 보관하는 데이터베이스
를 찾아볼 수 있습니다.

Redis

Redis는 Remote DIctionary Server를 줄인말로, Java의 Map과 같은 방식으로 데이터를 저장하는 데이터베이스.

💡

2024년 3월 20일 Redis는 라이센스가 변경되어 완전히 오픈소스라고 하긴 어렵습니다만, 여전히 일반적인 사용 사례에 대해서는 무료로 사용

특징

  • 일반적인 관계형 데이터베이스와 다른 가장 큰 특징은 In-Memory 데이터베이스. RAM에서 동작한다고 생각하면 된다.
  • 일반적인 관계형 데이터베이스에 비해 더 빠르게 동작하는 대신, 언제든 사라질 수 있는 데이터를 다
  • 내부적으로 데이터를 저장하는 방식이 관계형 데이터베이스와 다르다는 특징 존재
💡

전역적인 자료구조를 쓰지 않고 Redis를 쓰는 이유는 서버가 여러 대인 경우 동시성을 제공할 수 있기 때문과 선택에 따라 서버가 꺼져도 데이터를 기억할 수 있기 때문이다.

NoSQL?

  • 관계형 데이터베이스는 일반적으로 테이블 형식으로 데이터를 저장하고, 그 데이터를 회수하기 위해 SQL을 사용
    SELECT * FROM users;
    반면 Redis는 단순 문자열(String)부터, 리스트, 집합, Hash 등 다양한 형태의 데이터를 저장하며, 이 데이터를 회수하기 위해 SQL을 사용하지 않는다.
    SET greeting "Hello, Redis!"
    GET greeting
  • Redis는 대표적인 NoSQL 데이터베이스. NoSQL은 데이터베이스를 만드는 접근법의 일종으로, (현재는) Not only SQL을 의미
  • 일반적인 관계형 데이터베이스가 약점을 가진 확장성, 유연성, 성능에 대한 문제를 해결하기 위해 사용되는 경우가 많으며, 컴퓨터 기술의 발전과 웹의 활성화로 인해 비정형 데이터를 더 높은 성능으로 사용하는데 초점

스키마와 SQL을 사용하는 관계형 데이터베이스와 달리, NoSQL 데이터베이스는 데이터를 관리하는 방법이 서로 다르며, 사용법도 판이

  • Key-Value: 가장 단순한 형태의 데이터베이스로, Key에 Value를 저장하는 형태. JSON, Python의 Dictionary, Java의 Map의 형태로 데이터를 관리 → 이게 Redis
  • Document: 객체를 표현하는 Document라는 단위로 데이터를 저장하는 형태. Key - Value에서 발전했다고 볼 수 있으며, JSON, XML 등 복잡한 데이터를 저장하고 관리 → MongoDB
  • Column-Family: 각 Row의 Column이 고정되어있지 않고, 필요한 데이터 Column을 이름, 데이터, Timestamp와 함께 저장하는 형태의 데이터베이스 → Cassandra

관계를 기준으로 데이터를 다루지 않기 때문에 스키마를 만들지 않고, 그렇기 때문에 비정형 대규모 데이터를 매우 빠르게 다룸

활용 사례

Redis는 NoSQL 중에서도 Key-Value Store로 작동하는 인메모리 데이터베이스이며, 지연이 적은 읽기 / 쓰기 성능. 그렇기 때문에 일시적인 데이터, 변경이 잦은 데이터를 다뤄야 되는 상황에서 많이 활용

  • Session Clustering: 여러 애플리케이션 인스턴스에서 같은 세션 정보를 사용
  • Caching: 자주 사용되는 데이터를 저장해두어, 데이터베이스 조회를 줄이고 전반적인 응답속도를 개선
  • 지원하는 다양한 자료구조를 바탕으로 리더보드, 방문수 트래킹, 좌표 기반 검색 등의 기능을 쉽게 구현

설치

뭐 WSL을 사용해서 다운받거나 Docker를 사용해서 받거나 하라고 한다.

그런데 나는 이미 local에 그냥 받아놨었다. 그래서 그걸로 사용하겠다.

Redis Insight라는 IDE가 있다고 함. IDE라고 해야 하나 싶긴한데 뭐 여튼 관리 Tool인 것 같다. 이것도 같이 다운 받아주면 좋다

도커 사용 시 이미지를 redis/redis-stack 을 선택하면 redis insight까지 같이 설치되고 8001 포트로 접근이 된다고 한다.

타입

Redis는 앞서 설명한것 처럼 Key - Value 데이터베이스

  • 대부분의 명령이 Key를 바탕으로 동작하게 되며, Value로 사용하고자 하는 자료형에 따라 다른 명령어를 사용
  • 대부분의 명령이 존재하지 않는 Key를 이용해도 정상적으로 동작하며, 없는 Key에 데이터를 저장하면 Key를 생성
  • 단, 이미 만든 Key에 해당하는 데이터와 다른 자료형의 명령을 사용하는 경우 오류가 발생할 수 있음

String

가장 기본적인 자료형

  • Redis가 Java의 Map<String, String>처럼 동작한다고 생각하면서 접근
  • 저장할 수 있는 최대 크기는 512MB

명령어

  • GET, SET
    SET user:email alex@example.com
    GET user:email
    • SET <key> <value>: keyvalue 문자열 데이터를 저장, "으로 공백 구분
    • GET <key>: key에 저장된 문자열 반환
  • INCR, DECR (증감) 만약 저장된 데이터가 정수 데이터라면 데이터를 바로 증가, 감소가 가능
    SET user:count 1
    INCR user:count
    DECR user:count
    • INCR key: key에 저장된 데이터를 1 증가
    • DECR key: key에 저장된 데이터를 1 감소
  • MSET, MGET 여러 Key - Value를 한번에 다루고 싶다면 MSET, MGET을 활용
    MSET user:name alex user:email alex@example.com
    MGET user:name user:email
    • MSET key value [key value …]: key value의 형태로 주어진 인자들을 각 keyvalue를 저장
    • MGET key [key]: 주어진 모든 key에 해당하는 데이터를 반환.

단순 문자열이지만, 문자열은 결국 바이트 배열 ⇒ 이미지, 음성, 영상, 파일, 또는 이메일 본문 등도 보관이 가능

분산된 구조에서 비교적 큰 사이즈의 데이터를 주고받아야 하는 상황Key만 전달해서 데이터는 여기있다 전달하는 방식으로 활용

List

  • 여러 문자열 데이터를 Linked List의 형태로 보관하는 자료형 ⇒ Linked List의 add는 보통 양끝 추가임
  • Linked List이기 때문에, 중간의 데이터 보다는 양끝의 데이터, 즉 스택 또는 큐 처럼 사용

Redis는 Key - Value 형태로 저장 ⇒ 즉 Map<String, List<String>>의 형태로 사용

명령어

  • LPUSH, RPUSH, LPOP, RPOP PUSH 또는 POP을 L 또는 R과 조합하여, 왼쪽 또는 오른쪽에 데이터를 추가 / 제거가 가능
    LPUSH user:list alex  # [alex]
    LPUSH user:list brad  # [brad, alex]
    RPUSH user:list chad  # [brad, alex, chad]
    RPUSH user:list dave  # [brad, alex, chad, dave]
    
    LPOP user:list        # brad
    RPOP user:list        # chad
    • LPUSH key value: key에 저장된 리스트의 앞쪽value를 저장
    • RPUSH key value: key에 저장된 리스트의 뒤쪽value를 저장
    • LPOP key: key에 저장된 리스트의 앞쪽에서 값을 반환 및 제거
    • RPOP key: key에 저장된 리스트의 뒤쪽에서 값을 반환 및 제거
  • LLEN, LRANGE 리스트를 사용하면서 흔히 사용하는 길이 구하기, 범위 내 원소 반환하기 등의 기능도 제공
    LLEN user:list
    LRANGE user:list 0 3
    LRANGE user:list 0 -1
    • LLEN key: key에 저장된 리스트의 길이를 반환

    • LRANGE key start end: keystart부터 end까지 원소들을 반환

      LLEN은 없는 Key를 대상으로 하면 0이 되지만, 다른 자료형을 저장한 Key를 대상으로 하면 오류가 발생

      LRANGE의 경우 (언제나 그렇듯) 시작을 0으로 생각하고,

    • end가 실제 길이를 벗어나도 오류가 발생하진 않습니다.

    • start > end일 경우 빈 결과가 반환됩니다.

    • 음수의 경우 리스트의 뒤에서부터 데이터를 가져옵니다.

리스트의 경우 소셜 네트워크에서 많이 사용하는 자료형

대표적으로 X (구 트위터)가 List를 바탕으로 Timeline을 구성

Set

문자열의 집합입니다. 집합인 만큼 중복값을 제거하며, 순서가 존재하지 않습니다.

  • SADD, SREM, SMEMBERS, SISMEMBER, SCARD
    SADD user:java alex  # [alex]
    SADD user:java brad  # [alex, brad]
    SADD user:java chad  # [alex, brad, chad]
    SREM user:java alex  # [brad, chad]
    
    SMEMBERS user:java        # [alex, brad, chad]
    SISMEMBER user:java brad  # true
    SISMEMBER user:java dave  # false
    • SADD key value: key에 저장된 집합에 value를 추가
    • SREM key value: key에 저장된 집합의 value를 제거
    • SMEMBERS key: key에 저장된 집합의 모든 원소를 반환
    • SISMEMBER key value: key에 저장된 집합에 value가 존재하는지 반환
    • SCARD key: key에 저장된 집합의 크기를 반환
  • SINTER, SUNION, SINTERCARD 여기에 복수의 집합이 있다면, 교집합, 합집합 등의 기능을 제공 결과 집합 자체를 반환하기도, 결과 집합의 크기를 반환
    # 다른 Set에 추가한 뒤,
    SADD user:python alex
    SADD user:python dave
    
    SINTER user:java user:python        # [alex]
    SUNION user:java user:python        # [alex, brad, chad, dave]
    SINTERCARD 2 user:java user:python  # 1
    • SINTER key1 key2: key1key2에 저장된 집합들의 교집합의 원소들을 반환
    • SUNION key1 key2: key1key2에 저장된 집합들의 합집합의 원소들을 반환
    • SINTERCARD number key1 [key2 ...]: number개의 key에 저장된 집합들의 교집합의 크기를 반환

중복을 허용하지 않으며, 어떤 데이터의 존재 여부를 확인하는 SISMEMBER 같은 경우 O(1)의 시간복잡도

중복 없는 방문 수, 인증 토큰 블랙리스트 등을 구현할 때 활용

❗ 단, 매우 높은 방문수를 기록하는 서비스의 경우 실제 데이터가 필요하지 않다면, 근사값을 돌려주는 확률형 자료형을 사용하는게 권장

Hash

Field - Value 쌍으로 이뤄진 자료형

Hash 데이터를 가져오기 위해 Key를 사용하고, 이후 다시 Key에 저장된 Hash 데이터에 Field - Value 쌍을 넣어주는 형식으로 동작 ⇒ Redis 전체가 Map 이라면 Hash는 Map<String, Map<String, String>>의 형식

  • HSET, HGET, HMGET, HGETALL, HKEYS, HLEN
    HSET user:alex name alex age 25
    HSET user:alex major CSE
    
    HGET user:alex name
    HGET user:alex age
    
    HMGET user:alex age major
    HGETALL user:alex
    
    HKEYS user:alex
    HLEN user:alex
    • HSET key field value [field value]: key의 Hash에 fieldvalue를 넣는다. 한번에 여러 field - value 쌍을 넣어줄 수 있다.
    • HGET key field: key에 저장된 Hash의 field에 저장된 value를 반환. 없는 field의 경우 null.
    • HMGET key field [field]: key에 저장된 Hash에서 복수의 field에 저장된 value를 반환.
    • HGETALL key: key에 저장된 Hash에 저장된 field - value를 전부 반환.
    • HKEYS key: key에 저장된 Hash에 저장된 field를 전부 반환
    • HLEN key: key에 저장된 Hash에 저장된 field의 갯수를 반환

Hash는 본래 하나의 키에 복잡한 데이터 (객체의 데이터 라던지)를 하나의 키에 저장하는 용도로 주로 활용되고, 공식 문서에서도 여러 Key에 걸쳐 객체의 데이터를 표현하기 보단 Hash를 자주 활용할 것을 권장

장바구니 같은 기능은 사용자별로, 어떤 물품이 몇개나 담겨있는지와 같은 정보가 포함. 사용자 마다 Hash 데이터를 생성하고, 물품 - 갯수 형식으로 데이터를 저장하면 사용자별 장바구니를 쉽게 저장

Sorted Set

이름처럼 정렬된 집합

기본적으로 Set과 동일하게, 유일한 값들만 유지하지만 여기에 더해 각 값들에 score라고하는 실수를 함께 보관. 그리고 데이터를 가져올 때, score를 바탕으로 정렬하여 값들을 가져옴.

  • ZADD, ZINCRBY, ZRANK, ZRANGE, ZREVRANK, ZREVRANGE
    ZADD user:ranks 10 alex
    ZADD user:ranks 9 brad 11 chad
    ZADD user:ranks 8 dave
    
    ZINCRBY user:ranks 2 alex
    
    ZRANK user:ranks alex
    ZRANGE user:ranks 0 3
    
    ZREVRANK user:ranks alex
    ZREVRANGE user:ranks 0 3
    • ZADD key score member [score member ...]: key의 Sorted Set에 score를 점수로 가진 member를 추가, 이미 있는 member의 경우 새로운 score를 설정
    • ZRANK key member: key의 Sorted Set의 member의 순위를 오름차순 기준으로 0에서 부터 세서 반환
    • ZRANGE key start stop: key의 Sorted Set의 member들을 start 부터 stop 순위까지 오름차순 기준으로 반환
    • ZREVRANK key member: key의 Sorted Set의 member의 순위를 내림차순 기준으로 0에서 부터 세서 반환
    • ZREVRANGE key start stop: key의 Sorted Set의 member들을 start 부터 stop 순위까지 내림차순 기준으로 반환
    • ZINCRBY key increment member: key의 Sorted Set의 memberscoreincrement 만큼 증가 (음수를 전달하면 감소)

대표적으로 리더보드나 Rate Limiter, 즉 순위와 관련된 기능을 만드는데 사용

💡 Rate Limiter란, API 등을 제공할 때 짧은 시간에 지나치게 많은 요청을 막기 위한 기능을 의미합니다.

그 외 공용 명령

그 외 자료형과 상관없이 사용할 수 있는 명령

이 중 대표적으로 많이 사용하는 건, Key를 제거하기 위한 DEL, 만료시각 설정을 위한 EXPIRE

  • DEL, EXPIRE, EXPIRETIME
    SET somekey "to be deleted"
    DEL somekey
    
    SET expirekey "to be expired"
    EXPIRE expirekey 5
    EXPIRETIME expirekey
    • DEL key: key(와 저장된 데이터)를 제거
    • EXPIRE key seconds: key의 TTL(유효시각)을 seconds로 설정, seconds초가 지나면 key 제거
    • EXPIRETIME key: key가 만료되는 시각을 Unix Timestamp로 반환

만약 저장된 모든 Key를 확인하고 싶다면,

KEYS * 

을 사용할 수 있습니다. 이는 glob 패턴 형식을 전달하여, 패턴에 일치하는 키를 반환하는 메서드 입니다.

💡 여기서 `*`은 **Linux 파일시스템 등에서 흔히 사용하는 Glob 패턴**이며, `*`은 임의갯수의 글자를 대체하는 와일드카드입니다. 지금은 `*`만 있으므로, 모든 Key와 일치하는 패턴이기 때문에 모든 Key가 반환됩니다. 그 외 예시들이 공식문서에 있으며, 더 궁금하다면 검색을 해봅시다!
  • keys 패턴 예시 (공식 문서)
    h?llo - hello, hallo, hxllo
    h*llo - hllo, heeeello
    h[ae]llo - hello, hallo (not hillo)
    h[^e]llo - hallo, hbllo, ... (not hello)
    h[a-b]llo - hallo, hbllo

마지막으로, 모든 Key를 제거하고 싶다면 FLUSHDB를 사용할 수 있습니다.

FLUSHDB

0개의 댓글