<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Database on Yarang's Tech Lair</title><link>https://blog.agentthread.dev/ko/tags/database/</link><description>Recent content in Database on Yarang's Tech Lair</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Sat, 06 Jun 2026 09:00:49 +0900</lastBuildDate><atom:link href="https://blog.agentthread.dev/ko/tags/database/index.xml" rel="self" type="application/rss+xml"/><item><title>pg_durable 분석: Rust로 구현하는 내구성 있는 함수 실행</title><link>https://blog.agentthread.dev/ko/post/pg_durable-%EB%B6%84%EC%84%9D-rust%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EB%82%B4%EA%B5%AC%EC%84%B1-%EC%9E%88%EB%8A%94-%ED%95%A8%EC%88%98-%EC%8B%A4%ED%96%89/</link><pubDate>Sat, 06 Jun 2026 09:00:49 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/pg_durable-%EB%B6%84%EC%84%9D-rust%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EB%82%B4%EA%B5%AC%EC%84%B1-%EC%9E%88%EB%8A%94-%ED%95%A8%EC%88%98-%EC%8B%A4%ED%96%89/</guid><description>&lt;p&gt;최근 Microsoft가 오픈 소스로 공개한 &lt;code&gt;pg_durable&lt;/code&gt;은 데이터베이스 내에서 직접 &amp;lsquo;내구성 있는(Durable)&amp;rsquo; 함수 실행을 가능하게 하는 실험적인 프로젝트입니다. 전통적인 마이크로서비스 아키텍처에서는 비즈니스 로직이 애플리케이션 계층에 위치하고, 데이터베이스는 단순히 상태를 저장하는 역할에 그치는 경우가 많습니다. 하지만 &lt;code&gt;pg_durable&lt;/code&gt;은 로직과 데이터를 물리적으로 가까운 곳에 통합하여, 외부 시스템의 장애나 네트워크 비용을 최소화하는 접근 방식을 제시합니다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 &lt;code&gt;pg_durable&lt;/code&gt;의 핵심 메커니즘을 분석하고, Rust와 PostgreSQL을 활용해 유사한 패턴을 구현하는 방법을 살펴보겠습니다.&lt;/p&gt;
&lt;h2 id="pg_durable의-핵심-메커니즘"&gt;pg_durable의 핵심 메커니즘
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;pg_durable&lt;/code&gt;의 가장 큰 특징은 함수의 상태(State)와 실행 흐름(Execution Flow) 자체를 데이터베이스 레코드로 저장한다는 점입니다. 일반적인 애플리케이션에서 HTTP 요청이 실패하면 메모리에 있던 컨텍스트가 사라지지만, &lt;code&gt;pg_durable&lt;/code&gt;은 실행할 함수의 입력(Input)과 현재 단계(Step)를 테이블에 기록합니다.&lt;/p&gt;
&lt;p&gt;이를 통해 다음과 같은 이점을 얻을 수 있습니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;자동 복구 (Auto-Recovery)&lt;/strong&gt;: 서버가 크래시되어도 데이터베이스에는 &amp;lsquo;실행해야 할 작업&amp;rsquo;이 남아있으므로, 서버 재시작 후 남은 작업을 이어서 수행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;정확히 한 번 실행 (Exactly-Once Semantics)&lt;/strong&gt;: 데이터베이스 트랜잭션을 활용해 상태 업데이트와 로직 실행을 원자적으로 처리할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="rust로-데이터베이스-트리거-기반-워커-구현하기"&gt;Rust로 데이터베이스 트리거 기반 워커 구현하기
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;pg_durable&lt;/code&gt;의 내부 구현은 복잡하지만, 이 패턴을 Rust와 PostgreSQL의 &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt; 기능을 사용해 간단하게 재현해 볼 수 있습니다. 이 접근 방식은 데이터베이스를 &amp;lsquo;메시지 브로커&amp;rsquo;처럼 활용하면서도 별도의 인프라 비용을 절약할 수 있습니다.&lt;/p&gt;
&lt;h3 id="1-데이터베이스-스키마-설계"&gt;1. 데이터베이스 스키마 설계
&lt;/h3&gt;&lt;p&gt;먼저 작업(Job)을 저장할 테이블을 생성합니다.&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-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; durable_jobs (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id SERIAL &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status TEXT &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;, &lt;span style="color:#75715e"&gt;-- &amp;#39;pending&amp;#39;, &amp;#39;running&amp;#39;, &amp;#39;completed&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload JSONB &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; created_at &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; NOW()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-rust-워커-구현-sqlx-사용"&gt;2. Rust 워커 구현 (sqlx 사용)
&lt;/h3&gt;&lt;p&gt;Rust의 비동기 런타임과 &lt;code&gt;sqlx&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-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Cargo.toml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;dependencies&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;tokio&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;full&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;sqlx&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.7&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;runtime-tokio&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;postgres&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;json&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;derive&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이제 Rust 코드로 워커를 구현합니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; sqlx::{PgPool, Listener};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::time::{sleep, Duration};
&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;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JobPayload&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user_id: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; action: String,
&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[tokio::main]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), sqlx::Error&lt;span style="color:#f92672"&gt;&amp;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:#66d9ef"&gt;let&lt;/span&gt; pool &lt;span style="color:#f92672"&gt;=&lt;/span&gt; PgPool::connect(&lt;span style="color:#e6db74"&gt;&amp;#34;postgresql://user:password@localhost/mydb&amp;#34;&lt;/span&gt;).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&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;// 데이터베이스 변경 사항 감지 리스너 (pg_notify 활용)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; listener &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pool.acquire().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&lt;/span&gt;.listen(&lt;span style="color:#e6db74"&gt;&amp;#34;job_channel&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:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Worker started, waiting for jobs...&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;loop&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:#66d9ef"&gt;let&lt;/span&gt; notification &lt;span style="color:#f92672"&gt;=&lt;/span&gt; listener.recv().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Received notification: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, notification.payload);
&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; process_jobs(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;pool).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&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&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;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process_jobs&lt;/span&gt;(pool: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;PgPool&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), sqlx::Error&lt;span style="color:#f92672"&gt;&amp;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;// 트랜잭션 내에서 &amp;#39;pending&amp;#39; 상태의 작업을 가져와 &amp;#39;running&amp;#39;으로 변경 (Locking)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; tx &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pool.begin().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&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;let&lt;/span&gt; job_opt &lt;span style="color:#f92672"&gt;=&lt;/span&gt; sqlx::query_as::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;_, (&lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;, JobPayload)&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;r&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;&lt;span style="color:#e6db74"&gt; UPDATE durable_jobs 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; SET status = &amp;#39;running&amp;#39; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; WHERE id = (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; SELECT id FROM durable_jobs 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; WHERE status = &amp;#39;pending&amp;#39; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; FOR UPDATE SKIP LOCKED 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; LIMIT 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; RETURNING id, payload
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&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; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .fetch_optional(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;tx)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&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; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Some((id, payload)) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; job_opt {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Processing job &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, id, payload);
&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;// 비즈니스 로직 실행 (예: 외부 API 호출 등)
&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:#66d9ef"&gt;let&lt;/span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; simulate_heavy_task(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;payload).&lt;span style="color:#66d9ef"&gt;await&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;match&lt;/span&gt; result {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(_) &lt;span style="color:#f92672"&gt;=&amp;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;// 성공 시 상태를 &amp;#39;completed&amp;#39;로 업데이트
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sqlx::query(&lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE durable_jobs SET status = &amp;#39;completed&amp;#39; WHERE id = $1&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .bind(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .execute(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;tx)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&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; Err(e) &lt;span style="color:#f92672"&gt;=&amp;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;// 실패 시 상태를 &amp;#39;failed&amp;#39;로 남겨두거나 재시도 로직 추가
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Job &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; failed: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, id, e);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sqlx::query(&lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE durable_jobs SET status = &amp;#39;failed&amp;#39; WHERE id = $1&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .bind(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .execute(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;tx)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&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&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; tx.commit().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(())
&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;simulate_heavy_task&lt;/span&gt;(payload: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;JobPayload&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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초 대기
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sleep(Duration::from_secs(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Task executed for user &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, payload.user_id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-작업-등록-트리거"&gt;3. 작업 등록 트리거
&lt;/h3&gt;&lt;p&gt;실제로는 애플리케이션 레벨에서 &lt;code&gt;INSERT&lt;/code&gt; 쿼리를 날린 후 &lt;code&gt;NOTIFY&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-sql" data-lang="sql"&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:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;OR&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;REPLACE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;FUNCTION&lt;/span&gt; notify_job() &lt;span style="color:#66d9ef"&gt;RETURNS&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TRIGGER&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&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;BEGIN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PERFORM pg_notify(&lt;span style="color:#e6db74"&gt;&amp;#39;job_channel&amp;#39;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;NEW&lt;/span&gt;.id::text);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;RETURN&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NEW&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;END&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;$$&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;LANGUAGE&lt;/span&gt; plpgsql;
&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;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TRIGGER&lt;/span&gt; job_notify_trigger
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;AFTER&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INSERT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; durable_jobs
&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; &lt;span style="color:#66d9ef"&gt;EACH&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ROW&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;EXECUTE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;FUNCTION&lt;/span&gt; notify_job();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="아키텍처-적용-시-고려사항"&gt;아키텍처 적용 시 고려사항
&lt;/h2&gt;&lt;p&gt;이 패턴(&lt;code&gt;pg_durable&lt;/code&gt; 스타일)을 ZeroClaw나 MCP 서버와 같은 고성능 시스템에 적용할 때는 다음 사항을 고려해야 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;데이터베이스 부하&lt;/strong&gt;: 데이터베이스가 단순한 저장소가 아니라 실행 엔진의 역할을 일부 담당하므로, 워커가 너무 잦은 폴링(Polling)을 하거나 무거운 로직을 수행하면 DB 성능에 저하가 올 수 있습니다. &lt;code&gt;SKIP LOCKED&lt;/code&gt; 구문을 사용해 여러 워커 인스턴스가 안전하게 분산 처리하도록 구성하는 것이 필수입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;확장성(Scale-out)&lt;/strong&gt;: 앞서 보여준 Rust 코드는 다중 프로세스로 실행하더라도 &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt; 덕분에 안전하게 작업을 분산 처리할 수 있습니다. Kubernetes 환경이라면 &lt;code&gt;HorizontalPodAutoscaler&lt;/code&gt;(HPA)를 통해 큐가 쌓일 때 워커 파드를 늘리는 식으로 유연한 대응이 가능합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="결론"&gt;결론
&lt;/h2&gt;&lt;p&gt;Microsoft의 &lt;code&gt;pg_durable&lt;/code&gt;은 &amp;lsquo;데이터 중심 아키텍처(Data-Centric Architecture)&amp;lsquo;의 진화된 형태를 보여줍니다. 복잡한 메시지 큐(Kafka, RabbitMQ)를 도입하기 전, 데이터베이스의 트랜잭션 기능을 적극 활용해 간단하고 강력한 내구성을 확보해 보는 것은 어떨까요? 위의 예제 코드는 바로 실무에서 테스트해 볼 수 있는 형태이므로, 여러분의 사이드 프로젝트나 백오피스 시스템에 적용해 보시길 권장합니다.&lt;/p&gt;</description></item><item><title>Redis Array의 진화: 대규모 데이터 처리를 위한 아키텍처 분석</title><link>https://blog.agentthread.dev/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.agentthread.dev/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>