ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Redis — 메모리 안의 작은 사전, 그리고 우리가 그것을 쓰는 자리들
    IT 2026. 5. 28. 21:00
    Redis — 메모리 안의 작은 사전, 그리고 우리가 그것을 쓰는 자리들

    거의 모든 현대 백엔드 시스템에 같은 도구 하나가 거의 빠짐없이 들어가 있다. Redis. 처음 듣는 사람에겐 낯선 이름이지만, 트위터·인스타그램·Stack Overflow·GitHub의 백엔드 어딘가에는 거의 확실히 자리잡고 있는 도구다.

    그런데 막상 "Redis가 뭐냐"고 물어보면 답이 흔히 모호하다. "캐시 같은 거", "빠른 DB", "key-value 저장소". 다 맞는 말이지만 그 단어들만으로는 Redis가 왜 만들어졌고 어떤 문제를 풀고 있는지 잡히지 않는다.

    이 글은 Redis를 처음부터 정리하는 글이다 — 어떤 목적으로 나왔고, 어떤 문제를 어떻게 풀고, 그리고 본인의 코드 위키 시스템(deep-wiki)에서 어떤 자리에 박혀 어떤 효과를 만드는지를 단계별로 풀어 본다.

    1. Redis가 뭔가 — 메모리에 사는 사전

    가장 짧은 정의는 이렇다. Redis는 메모리에 사는 key-value 저장소다. 다섯 단어 안에 핵심 세 가지가 다 있다.

    • 메모리 — 디스크가 아니다. 데이터는 RAM에 들고 있다. 그래서 매우 빠르다.
    • key-value — Python의 dict, 자바스크립트의 object와 같은 모양. 키 하나에 값 하나가 대응한다.
    • 저장소 — 단순한 임시 변수가 아니라, 여러 프로세스·여러 서버가 공유할 수 있는 별도 프로세스.

    이름의 어원도 정직하다 — Remote Dictionary Server. "원격에서 접근할 수 있는 사전 서버". 이탈리아 개발자 Salvatore Sanfilippo(닉네임 antirez)가 2009년 자신의 웹사이트 분석 서비스가 너무 느려서 직접 만든 게 시작이다.

    가장 단순한 사용 예 — Python에서 Redis 클라이언트로 키를 저장하고 읽는 코드는 이렇게 생겼다.

    import redis
    r = redis.Redis(host="127.0.0.1", port=6379)
    
    r.set("user:42:name", "MyName")     # 키에 값 저장
    name = r.get("user:42:name")      # 키로 값 읽기 — 약 0.5ms 안에 돌아옴
    

    Python의 dict와 거의 같은 인터페이스다. 차이는 단 하나 — 이 사전이 내 프로그램 안이 아니라 별도 프로세스(또는 별도 서버)에 산다는 것이다. 그래서 여러 프로세스가 같은 사전을 공유할 수 있고, 내 프로그램이 종료돼도 사전은 살아 남는다.

    2. 풀고자 하는 문제 — RDBMS 한 쿼리의 10~100배 latency

    Redis가 왜 만들어졌는지 이해하려면 응용 입장에서의 응답 시간 차이를 먼저 봐야 한다. 흥미로운 점은 SSD 자체는 의외로 빠르다는 사실이다 — Redis가 빠른 진짜 이유는 디스크가 느리기 때문이 아니라, RDBMS가 한 쿼리에 하는 일이 너무 많기 때문이다.

    diagram

    다이어그램 설명. 흔히 "Redis는 메모리니까 디스크보다 빠르다"고 단정하기 쉽지만, raw access 시간만 비교하면 NVMe SSD(~100μs)와 Redis 로컬 GET(~0.1~0.5ms)는 비슷한 수준이다. Redis가 빠른 진짜 이유는 단순함 — RDBMS 한 쿼리는 SQL parsing, query planning, B-tree traversal, multiple page fetch, result formatting 같은 부대 작업으로 1~10ms를 쓰는데, Redis GET은 그 모든 단계가 없다. 그래서 응용 입장에서 Redis가 RDBMS 대비 10~100배 빠른 것이지, 메모리 자체가 SSD보다 1000배 빠르기 때문이 아니다.

    그래서 "자주 묻는 동일한 답을 매번 RDBMS에 가져오기엔 비싸다"는 문제가 모든 시스템에 공통으로 생긴다. Redis는 그 자리에 끼어들어 "이 답을 미리 풀어 키-값 한 쌍으로 들고 있다가, 다시 묻거든 SQL 오버헤드 없이 즉시 돌려준다"는 단순한 발상으로 문제를 푼다.

    3. 어떻게 풀고 있는가 — 단순함이 만든 성능

    Redis가 빠른 이유는 단순한 설계 결정 세 가지에서 온다.

    3.1. 데이터를 메모리에만 둔다

    대부분의 DB는 데이터를 주로 디스크에 두고 메모리는 캐시로 쓴다. Redis는 정반대 — 데이터는 항상 메모리에 있고, 디스크는 "메모리가 날아갈 때를 대비한 백업"으로만 쓴다.

    이 단순한 역전이 모든 응답 latency를 1ms 아래로 떨어뜨린다. 단점은 분명하다 — 데이터 크기가 메모리 용량을 못 넘어간다. 그래서 Redis는 "전체 데이터 베이스"가 아니라 "자주 묻는 hot data 전용"이라는 자리에 박힌다.

    3.2. 단일 스레드로 명령을 처리한다

    의외의 사실 하나가 있다. Redis는 한 번에 하나의 명령만 처리한다. 멀티스레드가 아니다. "단일 스레드가 빠를 리 있나?" 싶지만, 메모리만 만지는 작업에선 스레드 간 락(lock) 관리 비용이 더 크다. 락 없이 빠른 명령들을 차례로 빠르게 처리하는 게 더 효율적이라는 판단이다.

    이게 또 한 가지 강력한 부수 효과를 만든다 — 각 명령이 atomic하다. 두 클라이언트가 동시에 같은 카운터를 INCR하면, Redis 내부적으로 한 번에 하나만 처리되므로 race condition이 없다. 응용 단에서 락을 짤 일이 거의 없다.

    3.3. 다양한 자료구조를 직접 제공

    Redis는 단순한 string key-value만 다루는 게 아니다. 다섯 가지 핵심 자료구조를 직접 제공해서, 응용이 흔한 패턴을 매번 짤 필요가 없게 한다.

    diagram

    다이어그램 설명. 다섯 자료구조는 우리가 흔히 짜는 백엔드 패턴 대부분을 커버한다. String으로 단순 캐시를, List로 작업 큐를, Hash로 객체 캐시를, Set으로 집합 연산을, Sorted Set으로 정렬·랭킹을 만든다. 응용이 같은 패턴을 매번 직접 짤 필요가 없다는 점이 Redis가 흔히 채택되는 진짜 이유다 — "빠르다"만큼 "흔한 패턴이 이미 들어 있다"가 중요하다.

    4. 우리 맥락 — 코드 위키 시스템에서 Redis가 박힌 자리

    본인의 deep-wiki(개인용 코드 위키 시스템)에는 여러 자리에 Redis가 들어가 있다. 어떤 자리에 어떤 패턴으로 쓰는지 정리하면 Redis의 실전 사용법이 더 분명해진다.

    diagram

    다이어그램 설명. 한 시스템 안에서 Redis는 네 가지 다른 자료구조 패턴으로 동시에 활용된다. 검증 점수는 String 캐시, outbox는 List 큐, 비용은 String 카운터(INCRBYFLOAT로 atomic 증가), sync 차단은 String 캐시지만 의미는 다르다. 같은 인프라(Redis 인스턴스 한 개) 위에 네 가지 패턴이 공존한다는 게 Redis의 진짜 가치 — 별도 큐 시스템·별도 카운터 서비스를 따로 띄울 필요가 없다.

    네 자리를 자세히 — 각각의 의미·목적·효과

    다이어그램만으로는 각 자리가 왜 거기에 박혔는지가 한눈에 안 잡힐 수 있다. 네 가지를 하나씩 풀어 본다.

    1) 페이지 검증 점수 캐시 (String)

    • 의미 — 한 번 LLM(Large Language Model, 거대 언어 모델)에 채점을 시킨 페이지의 결과(JSON 점수)를 메모리에 보관해 두는 자리다. 키에는 page_sha(페이지 본문의 해시), model_sha(채점에 쓴 모델 식별자), rubric_version(채점 기준 버전)을 모두 묶어 넣는다. 셋 중 어느 하나라도 바뀌면 새 키가 되니까 캐시 무효화가 자동으로 일어난다.
    • 목적 — 같은 페이지·같은 모델·같은 기준이면 채점 결과가 결정적(deterministic, 같은 입력이면 같은 출력)이다. 그런데 LLM 호출은 비싸다 — 한 번에 1초 이상 걸리고 $0.01 단위로 청구된다. 매번 다시 호출하지 않고 한 번 받은 답을 들고 있다가 재사용한다.
    • 효과 — 캐시 hit rate가 70%로 안착하면서 월 LLM 비용이 $90 예상에서 $10 아래로 떨어졌다. 응답 속도도 1초 이상 → 1ms 아래로 단축됐다. 같은 페이지를 매일 채점하던 cron 작업이 사실상 무료가 됐다.

    2) 작업 큐 — outbox 워커 (List)

    • 의미 — outbox 패턴(producer가 직접 다 처리하지 않고 큐에 일감만 던지고 별도 worker가 꺼내 가서 처리)을 Redis List 자료구조로 구현한 자리다. RPUSH로 끝에 추가하고, BLPOP으로 앞에서 꺼낸다. BLPOP은 blocking pop — 큐가 비어 있으면 워커가 새 일이 들어올 때까지 잠시 대기한다.
    • 목적 — 페이지 한 장이 생기면 NetworkX(메모리 그래프 라이브러리)·Qdrant(벡터 DB)·wiki-output(파일 시스템) 세 곳에 같은 정보가 같은 순서로 들어가야 한다. 셋 다 동시에 producer가 처리하려 들면 코드도 복잡하고 한 곳이 실패할 때 보상 트랜잭션이 어려워진다. 일감을 큐에 던지고 워커에게 맡기면 producer는 즉시 다음 일로 넘어갈 수 있다.
    • 효과 — producer 측 코드가 한 줄(rpush)로 끝난다. 실패 시 retry·exponential backoff·DLQ(Dead Letter Queue, 반복 실패하는 일감을 따로 모아 두는 큐) 같은 보상 패턴이 워커 쪽으로 모인다. 세 저장소 사이의 일관성(3-way consistency)이 깨질 위험을 한자릿수 ms 안에 잡아낸다.

    3) 비용 누적 카운터 (String + INCRBYFLOAT)

    • 의미INCRBYFLOAT는 키에 저장된 숫자를 atomic(원자적 — 한 번의 동작으로 분할 불가)하게 실수만큼 증가시키는 명령이다. 매 LLM 호출이 끝나면 그 호출의 비용을 USD 단위로 누적 키와 날짜별 키 두 군데에 더한다.
    • 목적 — 비동기로 동시에 여러 LLM 호출이 일어나는데, 각각의 비용을 한 변수에 합산해 가야 한다. 응용 단에서 "읽고 → 더하고 → 쓰기"를 짜면 동시 호출 시 race condition(경쟁 상태 — 두 작업이 같은 변수를 동시에 만져 결과가 어긋남)이 생긴다. Redis는 단일 스레드라 INCRBYFLOAT 한 번이 atomic을 자동 보장한다.
    • 효과 — 비용 누적 코드를 락 없이 한 줄로 짠다. 누적 총액이 $50/$80/$100 임계를 넘으면 텔레그램 알림이 발사되어 비용 폭주(예: 무한 루프에 빠진 cron이 LLM을 폭주 호출하는 사고)를 사전에 차단한다. 일별 키로 따로 누적하니까 "오늘 얼마 썼지"를 1ms 안에 답할 수 있다.

    4) 양방향 sync 무한 루프 차단 (String)

    • 의미docs/(개발자가 직접 쓰는 문서)와 wiki-output/(자동 생성된 위키)이 양방향으로 동기화되는 자리에 박힌 cache다. 한쪽에서 파일이 바뀌면 hook이 실행되어 다른 쪽으로 반영되는데, 그 반영이 다시 hook을 트리거해서 무한 루프에 빠지는 게 양방향 sync의 고전적 함정이다. 변환된 결과의 content SHA(파일 본문의 해시값)를 Redis에 한 줄 적어 두고, 다음에 같은 SHA가 다시 들어오면 "이미 처리한 변경"으로 즉시 종료한다.
    • 목적 — 양방향 hook 체인이 무한 루프에 빠지면 디스크 I/O와 CPU가 폭주하고, 최악의 경우 git history가 동일 변경으로 도배된다. 외부 락 시스템이나 복잡한 sync 알고리즘 대신 "같은 내용이면 한 번만 처리한다"는 가장 단순한 규칙으로 푼다.
    • 효과 — Redis GET 한 번(0.5ms)으로 무한 루프를 차단한다. 양쪽 hook 모두에 같은 cache lookup을 박아 두면 어느 쪽이 먼저 실행돼도 두 번째는 즉시 빠져나온다. 운영 부담이 추가 인프라 없이 cache key 하나로 끝났다.

    이렇게 보면 네 자리가 단순히 "같은 Redis 인스턴스 위에 같이 산다"는 것 이상의 공통점이 있다 — 모두 한두 줄의 Redis 호출로 응용 단의 복잡도를 크게 줄이는 자리다. 캐시·큐·카운터·SHA 차단이라는 서로 다른 패턴이지만 공통적으로 "이 자리를 직접 짜면 락·동시성·영속성을 다 새로 고민해야 하는" 것들이고, Redis가 그 고민을 단일 인프라 호출로 흡수한다.

    실제 코드 한 조각

    import redis
    
    # Redis 클라이언트 — 한 번 만들어 두고 여러 패턴에 재사용
    r = redis.Redis(host="127.0.0.1", port=6390, decode_responses=True)
    
    # 패턴 1: 캐시 — 같은 페이지를 두 번 채점하지 않기
    def get_or_compute_score(page_sha, model_sha, rubric_version):
        key = f"deep-wiki:score:{page_sha}:{model_sha}:{rubric_version}"
        cached = r.get(key)
        if cached:
            return json.loads(cached)        # 1ms 안에 반환
        score = call_llm_to_score(...)       # 1초 이상 + $0.01
        r.set(key, json.dumps(score))        # 다음 호출은 캐시 hit
        return score
    
    # 패턴 2: 큐 — outbox 워커에 작업 위임
    def enqueue_outbox_job(payload):
        r.rpush("deep-wiki:outbox", json.dumps(payload))   # 한 줄로 끝
    
    # 패턴 3: 카운터 — 비용 누적 (atomic)
    def add_cost(usd):
        today = datetime.now().strftime("%Y-%m-%d")
        r.incrbyfloat("deep-wiki:cost:total", usd)
        r.incrbyfloat(f"deep-wiki:cost:{today}", usd)
    

    코드 설명. 세 가지 다른 패턴이 같은 r 클라이언트 하나로 끝난다. 패턴 1의 캐시는 비용 절감에 쓰이고, 패턴 2의 큐는 비동기 처리에 쓰이며, 패턴 3의 카운터는 모니터링에 쓰인다. 각각 응용 단에서 직접 짜면 락 관리·동시성 처리·영속성 모두 새로 고민해야 하는 일을 Redis 한 줄 호출로 해결한다. "새 도구를 도입할 때마다 인프라가 늘어나는가"라는 흔한 부담이 Redis에는 없다는 게 점유율이 높은 진짜 이유다.

    5. 효과 — 우리 시스템에서 체감한 것

    Redis가 deep-wiki에 들어가면서 만든 효과를 정리하면 세 가지다.

    • 비용 절감 — LLM 채점 캐시 hit rate 70%가 안착해서 월 $90 예상이 $10 아래로 떨어졌다. Redis가 없었다면 매일 같은 페이지를 다시 채점하느라 비용이 폭주했을 것이다.
    • 응답 즉시성 — 대시보드가 시스템 상태(누적 비용·캐시 hit rate·hold queue 길이)를 보여줄 때, 모든 응답이 1ms 안에 돌아온다. 디스크 DB로 같은 일을 하면 페이지 로드마다 수십~수백 ms씩 늘어났을 것이다.
    • 인프라 단순화 — 별도 큐 시스템(Kafka·RabbitMQ), 별도 카운터 서비스, 별도 캐시 미들웨어를 띄울 필요가 없다. Redis 인스턴스 하나로 네 가지 패턴을 다 흡수. 운영 부담이 한 인스턴스만큼으로 한정된다.

    6. 한계 — Redis가 만능은 아니다

    Redis도 자기 자리가 있는 도구다. 다음 자리에선 Redis가 정답이 아니거나 부적합하다.

    • 데이터 크기 > 메모리 용량 — Redis는 메모리에만 사니까 사용자 수백만의 전체 프로필 같은 데이터는 못 담는다. 그 자리는 디스크 DB(Postgres·MySQL 등).
    • 복잡한 관계형 쿼리 — JOIN, GROUP BY, 트랜잭션 격리 같은 SQL 친화 작업엔 약하다. Redis도 트랜잭션(MULTI/EXEC)을 지원하지만 SQL DB 수준의 정교한 격리는 아니다.
    • 강한 영속성 보장 — Redis는 메모리가 사라지면 그때까지의 변경 일부가 손실될 수 있다(RDB snapshot이나 AOF로 완화하지만 100% 보장은 아님). 금융 거래처럼 손실이 절대 안 되는 자리는 RDBMS.
    • 큰 데이터 단위 한 번 처리 — Redis 명령은 단일 스레드라 한 번에 큰 데이터를 처리하면 다른 명령이 모두 대기. 100MB짜리 값 GET 같은 건 피해야 한다.

    그래서 실제 시스템 구성은 보통 이렇게 간다 — "디스크 DB(Postgres 등) + Redis(캐시·큐·카운터)"의 조합으로 짠다. 영속성 강한 자료는 RDB에 두고, 자주 묻는 hot data와 빠른 응답이 핵심인 자리는 Redis에 둔다. 두 도구가 서로 보완한다.

    마무리 — 왜 거의 모든 시스템에 Redis가 들어 있는가

    Redis가 흔한 이유는 단순히 빠르기 때문만이 아니다. "흔한 백엔드 패턴 다섯 가지를 같은 인프라에서 해결한다"는 게 진짜 이유다. 캐시·큐·카운터·랭킹·세션 같은 자리들은 거의 모든 시스템에 공통으로 등장하는데, 각각을 따로 짜면 코드도 인프라도 늘어난다. Redis 하나가 그 다섯을 다 흡수한다.

    본인의 코드 위키 시스템에서도 같은 패턴이 반복된다 — 검증 점수 캐시, 작업 큐, 비용 카운터, sync 차단 캐시까지 네 가지 자리가 같은 Redis 인스턴스(포트 6390 하나)에 공존한다. 시스템에 새 기능이 들어갈 때마다 "이건 Redis 어떤 자료구조로 풀까"가 자연스러운 첫 질문이 된다.

    "빠른 사전" 한 줄 정의로 시작했지만, 실제로 Redis가 시스템에 들어가면 그 영향은 단순 캐시를 한참 넘어선다. 인프라를 단순하게 유지하면서도 거의 모든 흔한 백엔드 패턴을 빠르게 해결하는 도구 — 그게 Redis의 진짜 가치다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.