<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Redis on Yarang's Tech Lair</title><link>https://blog.fcoinfup.com/ko/tags/redis/</link><description>Recent content in Redis on Yarang's Tech Lair</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Tue, 05 May 2026 09:00:52 +0900</lastBuildDate><atom:link href="https://blog.fcoinfup.com/ko/tags/redis/index.xml" rel="self" type="application/rss+xml"/><item><title>Redis Array의 진화: 대규모 데이터 처리를 위한 아키텍처 분석</title><link>https://blog.fcoinfup.com/ko/post/redis-array%EC%9D%98-%EC%A7%84%ED%99%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B6%84%EC%84%9D/</link><pubDate>Tue, 05 May 2026 09:00:52 +0900</pubDate><guid>https://blog.fcoinfup.com/ko/post/redis-array%EC%9D%98-%EC%A7%84%ED%99%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B6%84%EC%84%9D/</guid><description>&lt;h1 id="redis-array의-진화-대규모-데이터-처리를-위한-아키텍처-분석"&gt;Redis Array의 진화: 대규모 데이터 처리를 위한 아키텍처 분석
&lt;/h1&gt;&lt;p&gt;안녕하세요! 최근 Hacker News를 통해 흥미로운 기사 하나를 접하게 되었습니다. 바로 Redis의 핵심 개발자 중 한 명인 Oran Agra가 작성한 **&amp;ldquo;Redis array: short story of a long development process&amp;rdquo;**입니다. 단순히 기능 하나가 추가된 이야기가 아니었습니다. 이는 25년 된 레거시 코드를 건드리면서 성능을 유지하고, 안정성을 확보하며, 거대한 코드베이스를 밤새 포맷팅했던 개발자들의 집념의 기록이었습니다.&lt;/p&gt;
&lt;p&gt;오늘은 이 기사를 바탕으로, Redis 내부에서 Array(배열) 자료구조가 어떻게 진화해왔는지, 그리고 우리가 대규모 시스템을 설계할 때 배울 수 있는 교훈은 무엇인지 깊이 있게 분석해보겠습니다.&lt;/p&gt;
&lt;h2 id="1-문제-제기-25년-된-레거시-코드의-굴레"&gt;1. 문제 제기: 25년 된 레거시 코드의 굴레
&lt;/h2&gt;&lt;p&gt;Redis의 &lt;code&gt;LIST&lt;/code&gt; 자료구조는 내부적으로 &lt;code&gt;QuickList&lt;/code&gt;를 사용합니다. &lt;code&gt;QuickList&lt;/code&gt;는 양방향 연결 리스트인 &lt;code&gt;ziplist&lt;/code&gt;와 &lt;code&gt;linkedlist&lt;/code&gt;의 장점을 결합한 구조입니다. 하지만 수천만 개의 요소를 가진 거대한 리스트를 다룰 때, 메모리 파편화(memory fragmentation)와 캐시 미스(cache miss)가 심각한 성능 저하를 일으키는 문제가 있었습니다.&lt;/p&gt;
&lt;p&gt;특히, 배열(Array) 타입의 데이터를 처리할 때 기존의 구조는 다음과 같은 병목이 있었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;메모리 오버헤드:&lt;/strong&gt; 포인터 연결로 인한 추가 메모리 사용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;순차 접근 비용:&lt;/strong&gt; 캐시 라인을 효율적으로 사용하지 못해 발생하는 지연.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;개발팀은 이를 해결하기 위해 C 언어 수준에서 내부 구조를 뜯어고치기로 결심합니다. 여기서 가장 큰 난관은 바로 **&amp;ldquo;변경하지 않으면 안 되는 레거시 코드&amp;rdquo;**였습니다.&lt;/p&gt;
&lt;h2 id="2-해결-과정-formatting-a-25m-line-codebase"&gt;2. 해결 과정: Formatting a 25M-line Codebase
&lt;/h2&gt;&lt;p&gt;기사에서 가장 인상 깊었던 부분은 **&amp;ldquo;Formatting a 25M-line codebase overnight&amp;rdquo;**입니다. 2,500만 라인에 달하는 코드를 포맷팅하고 리팩토링하는 과정은 단순한 기술적 도전을 넘어 체스와 같은 전략이 필요했습니다.&lt;/p&gt;
&lt;h3 id="21-리팩토링을-위한-사전-준비"&gt;2.1. 리팩토링을 위한 사전 준비
&lt;/h3&gt;&lt;p&gt;대규모 리팩토링 시 가장 두려운 것은 **&amp;ldquo;회귀(Regression)&amp;rdquo;**입니다. 배열 구조를 변경하는 과정에서 수백 개의 Redis 명령어(&lt;code&gt;LPUSH&lt;/code&gt;, &lt;code&gt;RPUSH&lt;/code&gt;, &lt;code&gt;LINDEX&lt;/code&gt; 등)가 영향을 받을 수 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;이를 해결하기 위해 팀은 다음과 같은 접근 방식을 취했습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;테스트 커버리지 확대:&lt;/strong&gt; 기존 명령어에 대한 단위 테스트(Unit Test)를 통과하는지 확인.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD 파이프라인 강화:&lt;/strong&gt; 코드 변경 시 즉시 성능 저하가 발생하는지 감시하는 벤치마킹 스크립트 배치.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="22-redis-array의-새로운-구조"&gt;2.2. Redis Array의 새로운 구조
&lt;/h3&gt;&lt;p&gt;개선된 Array 구조는 단순히 메모리를 할당하는 방식에서 벗어나, 데이터 지역성(Locality)을 극대화하는 방향으로 변경되었습니다. 핵심은 **&amp;ldquo;연속된 메모리 블록을 최대한 활용하되, 필요시 분할하여 관리한다&amp;rdquo;**는 것입니다.&lt;/p&gt;
&lt;p&gt;이를 통해 다음과 같은 이점을 얻었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPU 캐시 히트율 향상:&lt;/strong&gt; 연속된 메모리 접근으로 인해 L1/L2 캐시 적중률이 크게 향상되었습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메모리 절약:&lt;/strong&gt; 불필요한 포인터 연결을 줄여 실제 데이터 저장 공간을 확보했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-실전-가이드-redis에서-효율적인-배열-사용하기"&gt;3. 실전 가이드: Redis에서 효율적인 배열 사용하기
&lt;/h2&gt;&lt;p&gt;이론적인 배경은 충분하니, 이제 실제로 어떻게 적용할 수 있는지 코드로 살펴보겠습니다.&lt;/p&gt;
&lt;h3 id="31-기존-리스트-사용의-문제점"&gt;3.1. 기존 리스트 사용의 문제점
&lt;/h3&gt;&lt;p&gt;먼저, 수천만 개의 아이템을 리스트에 넣는 기존 방식을 생각해봅시다. 이는 &lt;code&gt;QuickList&lt;/code&gt; 기반으로 동작하며, 아이템 수가 늘어날수록 점프 횟수가 늘어납니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 기존 방식 (QuickList based)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 10,000,000개의 아이템 추가 (메모리 및 속도 저하 발생 가능)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; i in &lt;span style="color:#f92672"&gt;{&lt;/span&gt;1..10000000&lt;span style="color:#f92672"&gt;}&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; redis-cli LPUSH my_huge_list &lt;span style="color:#e6db74"&gt;&amp;#34;item:&lt;/span&gt;$i&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="32-stream과-hash를-활용한-최적화"&gt;3.2. Stream과 Hash를 활용한 최적화
&lt;/h3&gt;&lt;p&gt;Redis Array의 내부 개선은 사용자에게 투명하게 적용되지만, 우리가 설계를 할 때는 **&amp;ldquo;데이터의 크기&amp;rdquo;**와 **&amp;ldquo;접근 패턴&amp;rdquo;**을 고려해야 합니다. 단순히 순서대로 저장만 하면 된다면 최신 버전의 Redis를 쓰는 것만으로도 이득을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 만약 배열 안의 데이터를 검색하거나 수정해야 한다면 &lt;code&gt;LIST&lt;/code&gt; 대신 &lt;code&gt;HASH&lt;/code&gt;를 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; redis
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; time
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;r &lt;span style="color:#f92672"&gt;=&lt;/span&gt; redis&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Redis(host&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;, port&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;6379&lt;/span&gt;, db&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 시나리오: 로그 데이터 저장 (대규모)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 1. List 사용 (순차 보관용)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;push_to_list&lt;/span&gt;(count):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; start &lt;span style="color:#f92672"&gt;=&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; i &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(count):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; r&lt;span style="color:#f92672"&gt;.&lt;/span&gt;lpush(&lt;span style="color:#e6db74"&gt;&amp;#34;logs:timeline&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;log_entry_&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;i&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;List pushed &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;count&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; items in &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time() &lt;span style="color:#f92672"&gt;-&lt;/span&gt; start&lt;span style="color:#e6db74"&gt;:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;.4f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;s&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2. Hash 사용 (검색 및 수정용)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;push_to_hash&lt;/span&gt;(count):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; start &lt;span style="color:#f92672"&gt;=&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pipe &lt;span style="color:#f92672"&gt;=&lt;/span&gt; r&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pipeline()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; i &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(count):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pipe&lt;span style="color:#f92672"&gt;.&lt;/span&gt;hset(&lt;span style="color:#e6db74"&gt;&amp;#34;logs:details&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;entry_&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;i&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;log_content_&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;i&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pipe&lt;span style="color:#f92672"&gt;.&lt;/span&gt;execute()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Hash pushed &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;count&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; items in &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time() &lt;span style="color:#f92672"&gt;-&lt;/span&gt; start&lt;span style="color:#e6db74"&gt;:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;.4f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;s&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; __name__ &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 10만 개 데이터 삽입 테스트&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; push_to_list(&lt;span style="color:#ae81ff"&gt;100000&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; push_to_hash(&lt;span style="color:#ae81ff"&gt;100000&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;실행 결과 분석:&lt;/strong&gt;
최신 Redis 버전(7.x 이상)에서는 내부적으로 Array 구조가 최적화되어 있어 &lt;code&gt;LPUSH&lt;/code&gt; 속도가 매우 빠릅니다. 하지만 특정 인덱스의 데이터를 자주 조회해야 한다면 &lt;code&gt;LINDEX&lt;/code&gt;는 O(N)의 복잡도를 가지므로, &lt;code&gt;HGET&lt;/code&gt;을 쓰는 O(1) 방식이 훨씬 유리합니다.&lt;/p&gt;
&lt;h2 id="4-결론-개발-문화와-기술의-조화"&gt;4. 결론: 개발 문화와 기술의 조화
&lt;/h2&gt;&lt;p&gt;Redis Array의 개발 과정은 우리에게 중요한 교훈을 줍니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;성능은 무료가 아니다:&lt;/strong&gt; 25년 된 코드를 개선하기 위해서는 그에 상응하는 리팩토링과 테스트 비용이 따른다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;도구의 투자:&lt;/strong&gt; 2,500만 라인의 코드를 포맷팅할 수 있는 자동화 도구와 CI/CD 환경이 있었기에 가능한 작업이었다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;우리가 시스템을 설계할 때, 단순히 &amp;ldquo;빠르다&amp;quot;는 것만 넘어서 &amp;ldquo;어떻게 유지보수 가능한 성능을 낼 것인가&amp;quot;를 고민해야 합니다. Redis 팀이 보여준 것처럼, 때로는 아키텍처의 근간을 흔드는 대규모 개선을 두려워하지 말아야 할 때입니다.&lt;/p&gt;
&lt;h2 id="5-참고-자료"&gt;5. 참고 자료
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://news.ycombinator.com/item?id=41284521" target="_blank" rel="noopener"
 &gt;Formatting a 25M-line codebase overnight (Hacker News)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://redis.io/docs/data-types/lists/" target="_blank" rel="noopener"
 &gt;Redis Internals: QuickList&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;감사합니다!&lt;/p&gt;</description></item></channel></rss>