<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Distributed-Lock on kastori</title><link>http://blog.kastori.dev/tags/distributed-lock/</link><description>Recent content in Distributed-Lock on kastori</description><generator>Hugo -- gohugo.io</generator><language>ko-kr</language><lastBuildDate>Mon, 20 Jul 2026 00:00:00 +0900</lastBuildDate><atom:link href="http://blog.kastori.dev/tags/distributed-lock/index.xml" rel="self" type="application/rss+xml"/><item><title>[Redis 실무 입문 #4] 실전 패턴 — 세션·Rate Limiting·분산 락·랭킹</title><link>http://blog.kastori.dev/tech/2026-07-20-redis-04-patterns/</link><pubDate>Mon, 20 Jul 2026 00:00:00 +0900</pubDate><guid>http://blog.kastori.dev/tech/2026-07-20-redis-04-patterns/</guid><description>&lt;h2 id="이런-것도-redis로-하네-싶은-것들"&gt;&lt;a href="#%ec%9d%b4%eb%9f%b0-%ea%b2%83%eb%8f%84-redis%eb%a1%9c-%ed%95%98%eb%84%a4-%ec%8b%b6%ec%9d%80-%ea%b2%83%eb%93%a4" class="header-anchor"&gt;&lt;/a&gt;&amp;ldquo;이런 것도 Redis로 하네&amp;rdquo; 싶은 것들
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="http://blog.kastori.dev/tech/2026-07-18-redis-03-caching/" &gt;지난 편&lt;/a&gt;까지가 캐시였다. 캐시가 Redis의 입구라면, 이번 편 패턴들은 Redis를 좀 써본 뒤에 &amp;ldquo;아 이거 Redis로 하면 되는구나&amp;rdquo; 하고 무릎을 치게 되는 것들이다. 공통점이 하나 있다. &lt;strong&gt;직접 코드로 짜면 골치 아픈 걸, Redis의 원자적 명령 몇 줄로 끝낸다는 것.&lt;/strong&gt; 자주 만나는 네 가지를 본다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-세션-저장소--서버가-두-대-이상이-되는-순간"&gt;&lt;a href="#1-%ec%84%b8%ec%85%98-%ec%a0%80%ec%9e%a5%ec%86%8c--%ec%84%9c%eb%b2%84%ea%b0%80-%eb%91%90-%eb%8c%80-%ec%9d%b4%ec%83%81%ec%9d%b4-%eb%90%98%eb%8a%94-%ec%88%9c%ea%b0%84" class="header-anchor"&gt;&lt;/a&gt;1. 세션 저장소 — 서버가 두 대 이상이 되는 순간
&lt;/h2&gt;&lt;p&gt;서버 한 대일 땐 로그인 세션을 서버 메모리에 둬도 아무 문제 없다. 문제는 트래픽이 늘어 서버를 여러 대로 늘리는 순간 터진다. 로그인은 A 서버에서 했는데 다음 요청이 B로 가면, B는 그 세션을 모른다. 로그인이 풀린다.&lt;/p&gt;
&lt;p&gt;이때 세션을 &lt;strong&gt;Redis에 몰아두면&lt;/strong&gt; 모든 서버가 같은 곳을 보니 어디로 요청이 가도 똑같이 처리된다. 스케일 아웃하는 순간 거의 반드시 마주치는 패턴이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HSET session:abc123 userId &lt;span class="m"&gt;1&lt;/span&gt; role &lt;span class="s2"&gt;&amp;#34;admin&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;EXPIRE session:abc123 &lt;span class="m"&gt;1800&lt;/span&gt; &lt;span class="c1"&gt;# 30분 후 자동 로그아웃&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;세션은 필드가 여럿이니 Hash로 담고, &lt;code&gt;EXPIRE&lt;/code&gt;로 자동 만료(=자동 로그아웃)까지 공짜로 얻는다. Spring이면 &lt;code&gt;spring-session-data-redis&lt;/code&gt;가 이 과정을 통째로 대신해줘서, 세팅만 하면 기존 세션 코드를 거의 안 건드리고 Redis로 옮겨간다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="2-rate-limiting--1분에-5번만"&gt;&lt;a href="#2-rate-limiting--1%eb%b6%84%ec%97%90-5%eb%b2%88%eb%a7%8c" class="header-anchor"&gt;&lt;/a&gt;2. Rate Limiting — &amp;ldquo;1분에 5번만&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;API 남용을 막거나 선착순을 만들 때 쓴다. 핵심은 &lt;a class="link" href="http://blog.kastori.dev/tech/2026-07-14-redis-01-getting-started/" &gt;#1&lt;/a&gt;에서 본 &lt;code&gt;INCR&lt;/code&gt;과 &lt;code&gt;EXPIRE&lt;/code&gt;의 조합이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;count = INCR rate:user:1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if count == 1: EXPIRE rate:user:1 60 # 창(window)이 처음 열릴 때만 TTL
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if count &amp;gt; 5: &amp;#34;너무 많은 요청&amp;#34; 에러
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;INCR&lt;/code&gt;이 원자적이라 동시에 요청이 쏟아져도 카운트가 정확하다. 첫 요청일 때(&lt;code&gt;count == 1&lt;/code&gt;)만 60초 TTL을 걸어두면, 60초 뒤 키가 사라지며 카운트가 알아서 0으로 리셋된다. &amp;ldquo;1분에 5번&amp;quot;이 이 몇 줄로 끝난다. 이걸 DB로 하려 들면 시간 컬럼 비교에 정리 배치까지 붙어 훨씬 지저분해진다.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;이건 가장 단순한 고정 창(fixed window) 방식이다. 창 경계에서 순간적으로 두 배가 허용되는 약점이 있는데(59초에 5번, 다음 1초에 또 5번), 처음엔 이걸로 충분하다. 더 빡세게 막아야 하면 슬라이딩 윈도우나 토큰 버킷을 찾아보면 된다.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="3-분산-락--동시에-하나만-실행-그리고-그-함정"&gt;&lt;a href="#3-%eb%b6%84%ec%82%b0-%eb%9d%bd--%eb%8f%99%ec%8b%9c%ec%97%90-%ed%95%98%eb%82%98%eb%a7%8c-%ec%8b%a4%ed%96%89-%ea%b7%b8%eb%a6%ac%ea%b3%a0-%ea%b7%b8-%ed%95%a8%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;3. 분산 락 — 동시에 하나만 실행 (그리고 그 함정)
&lt;/h2&gt;&lt;p&gt;여러 서버가 같은 작업을 동시에 하면 안 될 때가 있다. 주문 하나 정산을 두 서버가 동시에 잡으면 중복 정산이 나는 식이다. 이때 &lt;strong&gt;분산 락&lt;/strong&gt;으로 &amp;ldquo;이 작업은 지금 나만 한다&amp;quot;를 보장한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# NX = 키가 없을 때만 SET (획득 성공/실패를 원자적으로 판정)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SET lock:order:100 &lt;span class="s2"&gt;&amp;#34;server-A&amp;#34;&lt;/span&gt; NX EX &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 성공(OK) → 내가 락 소유, 작업 후 DEL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 실패(nil) → 이미 다른 서버가 잡고 있음&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;NX&lt;/code&gt;(없을 때만)와 &lt;code&gt;EX&lt;/code&gt;(만료)를 &lt;strong&gt;한 명령에 같이&lt;/strong&gt; 거는 게 핵심이다. &lt;code&gt;EX&lt;/code&gt;를 빼면, 락을 잡은 서버가 작업 도중 죽었을 때 락이 영영 안 풀려서 전체가 멈춘다.&lt;/p&gt;
&lt;p&gt;여기서 솔직하게 말하면, 분산 락은 겉보기보다 훨씬 까다롭다. &lt;strong&gt;작업이 10초를 넘겨 락이 먼저 만료돼 버리면&lt;/strong&gt;, 그 사이 다른 서버가 새 락을 잡는데 원래 서버가 뒤늦게 끝나면서 남의 락을 &lt;code&gt;DEL&lt;/code&gt;로 지워버릴 수 있다. 이런 경계 케이스가 한둘이 아니다. 그래서 나는 학습용으로 원리는 위처럼 이해하되, 실무에서는 직접 구현하지 말고 &lt;strong&gt;Redisson&lt;/strong&gt; 같은 검증된 라이브러리를 쓰라고 권한다. Redisson은 작업이 길어지면 락을 자동 연장(watchdog)하고, 자기 락만 안전하게 푸는 처리를 이미 다 해뒀다. 바퀴를 다시 발명하다 다칠 필요 없다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="4-실시간-랭킹--sorted-set-하나면-끝"&gt;&lt;a href="#4-%ec%8b%a4%ec%8b%9c%ea%b0%84-%eb%9e%ad%ed%82%b9--sorted-set-%ed%95%98%eb%82%98%eb%a9%b4-%eb%81%9d" class="header-anchor"&gt;&lt;/a&gt;4. 실시간 랭킹 — Sorted Set 하나면 끝
&lt;/h2&gt;&lt;p&gt;게임 점수판, 인기글, 실시간 검색어는 사실 전부 같은 문제다. &amp;ldquo;점수 순으로 정렬된 목록에서 상위 N개랑, 특정 대상의 순위를 빠르게.&amp;rdquo; &lt;a class="link" href="http://blog.kastori.dev/tech/2026-07-16-redis-02-data-structures/" &gt;#2&lt;/a&gt;에서 본 Sorted Set이 정확히 이걸 위해 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ZINCRBY ranking:today &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;post:100&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 조회될 때마다 점수 +1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ZREVRANGE ranking:today &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt; WITHSCORES &lt;span class="c1"&gt;# 오늘의 인기글 TOP 10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ZREVRANK ranking:today &lt;span class="s2"&gt;&amp;#34;post:100&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 이 글의 현재 순위&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;ldquo;오늘의 랭킹&amp;quot;처럼 기간이 있으면 키에 날짜를 박고(&lt;code&gt;ranking:2026-07-20&lt;/code&gt;) TTL을 걸어두면 지난 날짜 랭킹은 알아서 정리된다. 배치로 옛날 데이터 지울 필요가 없다. 이런 소소한 게 쌓여서 Redis가 편한 거다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="마치며"&gt;&lt;a href="#%eb%a7%88%ec%b9%98%eb%a9%b0" class="header-anchor"&gt;&lt;/a&gt;마치며
&lt;/h2&gt;&lt;p&gt;네 패턴을 관통하는 건 결국 하나다. &lt;strong&gt;원래 애플리케이션 코드로 복잡하게 짜야 할 것을 Redis가 원자적 명령으로 단순하게 만든다.&lt;/strong&gt; 세션 공유, 횟수 제한, 동시성 제어, 실시간 정렬 — 하나하나 직접 구현하면 다 골치 아픈 문제인데 명령 몇 줄로 접힌다.&lt;/p&gt;
&lt;p&gt;마지막 편에서는 이 모든 걸 Spring Boot에서 실제로 어떻게 붙이는지, 그리고 운영에 올리기 전 마지막으로 확인할 체크리스트를 정리하며 시리즈를 닫는다.&lt;/p&gt;</description></item></channel></rss>