<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>changelog</title>
    <link>https://changelog.encer.me/</link>
    <description>개발, 제품, AI에 관해 생각하고 만드는 과정에서 남긴 노트.</description>
    <language>ko</language>
    <atom:link href="https://changelog.encer.me/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>마지막 재고 1개는 누구의 주문이 되는가</title>
      <link>https://changelog.encer.me/posts/last-item-inventory-reservation</link>
      <guid isPermaLink="true">https://changelog.encer.me/posts/last-item-inventory-reservation</guid>
      <pubDate>Tue, 27 Oct 2026 00:00:00 GMT</pubDate>
      <description>마지막 재고를 두 번 팔지 않으려면 결제보다 먼저 예약 상태를 설계해야 한다. 동시 주문, 결제 실패, 이탈, 결과 미상을 나누는 재고 선점 설계.</description>
      <category>commerce</category>
      <category>inventory</category>
      <category>payment</category>
      <category>concurrency</category>
      <category>reliability</category>
      <content:encoded><![CDATA[<h1 id="마지막-재고-1개는-누구의-주문이-되는가">마지막 재고 1개는 누구의 주문이 되는가<a class="heading-anchor" href="#마지막-재고-1개는-누구의-주문이-되는가" aria-label="이 섹션으로 가는 링크">#</a></h1>
<p>재고가 1개 남은 상품에 두 사람이 거의 동시에 주문을 넣는다.</p>
<p>둘 다 결제 화면까지 가고, 둘 다 “주문이 처리되었습니다”를 본 뒤에야 누군가에게 취소를 안내한다면, 문제는 재고 수량 하나가 틀린 데서 끝나지 않는다. 결제 취소, 환불, 고객 문의, 판매자 대응, 신뢰 하락이 한 번에 따라온다.</p>
<p>이 상황에서 내가 지키고 싶은 원칙은 단순하다.</p>
<blockquote><p>마지막 재고는 한 주문만 가져가야 한다. 다른 고객에게는 결제 취소보다 품절이 낫다.</p></blockquote>
<p>그래서 재고가 마지막 1개일 때는 결제 승인부터 받지 않는다. 먼저 한 주문에만 재고를 선점하고, 다른 고객에게는 품절을 보여 준다. 그다음 결제를 승인한다. 결제가 명확히 실패하면 즉시 선점을 풀고, 결제 화면을 이탈하면 정해진 시간이 지난 뒤에만 푼다.</p>
<p>중요한 것은 “재고를 먼저 차감한다”는 한 줄이 아니다. <strong>재고를 예약 상태로 다루고, 어떤 결과에서 누가 그 예약을 확정하거나 되돌릴 수 있는지 정하는 것</strong>이다.</p>
<h2 id="재고-차감과-결제-승인-사이에는-예약-상태가-있다">재고 차감과 결제 승인 사이에는 예약 상태가 있다<a class="heading-anchor" href="#재고-차감과-결제-승인-사이에는-예약-상태가-있다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>재고를 먼저 줄이고 결제를 나중에 받으면 이런 반론이 바로 나온다.</p>
<blockquote><p>결제가 실패하면 이미 빠진 재고는 어떻게 할 건데?</p></blockquote>
<p>답은 영구 차감이 아니라 예약이다. 재고 1개를 마지막 주문에 배정하는 순간의 상태는 <code>판매 완료</code>가 아니라 <code>결제 예약됨</code>이어야 한다.</p>
<ul><li><code>판매 가능</code>: 새 주문이 선점할 수 있다.</li><li><code>결제 예약됨</code>: 특정 주문만 결제를 끝낼 수 있다. 다른 고객에게는 품절로 보인다.</li><li><code>판매 완료</code>: 결제가 확정돼 더는 판매할 수 없다.</li><li><code>예약 해제</code>: 실패·취소·만료 뒤 다시 판매 가능 상태로 돌아간다.</li></ul>
<p>제한 재고를 다루는 Stripe Checkout도 결제를 끝내지 않은 고객이 재고를 오래 점유하지 않도록 세션 만료를 두고, 만료 이벤트를 받아 예약 재고를 되돌리도록 안내한다.[1] 다만 Stripe의 세션 만료 범위가 모든 결제수단과 자체 결제 화면의 정답은 아니다. 예약 시간은 고객이 결제를 끝내는 데 필요한 시간, 상품 희소성, 결제사 응답 특성을 함께 보고 정해야 한다.</p>
<p>여기서 UX의 기준도 분명해진다. 마지막 상품이 이미 결제 예약 상태라면 다른 고객에게 “거의 다 됐어요”라는 기대를 주지 않는다. 바로 품절로 보인다. 예약이 풀렸을 때만 다시 판매 가능으로 바뀐다.</p>
<h2 id="한-주문만-선점하는-일은-읽기와-쓰기를-나누면-뚫린다">한 주문만 선점하는 일은 읽기와 쓰기를 나누면 뚫린다<a class="heading-anchor" href="#한-주문만-선점하는-일은-읽기와-쓰기를-나누면-뚫린다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>가장 흔한 실수는 재고를 먼저 읽고, 애플리케이션 코드에서 <code>재고가 있네</code>라고 판단한 다음 나중에 차감하는 방식이다.</p>
<pre><code class="language-text">1. 현재 재고를 읽는다: 1개
2. 재고가 있으면 주문을 만든다
3. 재고를 1개 뺀다</code></pre>
<p>두 요청이 1번을 거의 동시에 실행하면 둘 다 1개를 읽고, 둘 다 주문을 만들 수 있다. 문제는 결제사가 아니라 재고 판단과 차감이 서로 다른 순간에 일어난 데 있다.</p>
<p>마지막 재고의 선점은 조건과 변경을 한 번의 DB 쓰기에 넣어야 한다. 예를 들면 다음과 같은 형태다.</p>
<pre><code class="language-sql"><span class="hl-keyword">UPDATE</span> inventory
<span class="hl-keyword">SET</span> available_quantity = available_quantity - <span class="hl-number">1</span>,
    reserved_quantity = reserved_quantity + <span class="hl-number">1</span>
<span class="hl-keyword">WHERE</span> product_id = :product_id
  <span class="hl-keyword">AND</span> available_quantity &gt; <span class="hl-number">0</span>
RETURNING available_quantity;</code></pre>
<p><code>UPDATE</code>는 <code>WHERE</code> 조건을 만족한 행만 바꾸고, <code>RETURNING</code>은 실제 변경된 행의 값을 돌려준다.[3] 반환 행이 없으면 그 요청은 마지막 재고를 선점하지 못한 것이다. PostgreSQL의 기본 <code>Read Committed</code>에서도 같은 행을 동시에 갱신하려는 요청은 먼저 갱신한 트랜잭션의 결과를 기다린 뒤, 변경된 행에 대해 <code>WHERE</code> 조건을 다시 평가한다.[7]</p>
<p>이것은 <code>SELECT</code>를 먼저 한 뒤 “재고가 남았으니 차감한다”보다 안전하다. 다만 여러 SKU를 함께 선점하거나 쿠폰·판매 제한·장바구니 분할까지 엮이면 단일 SQL만으로 충분하다고 일반화하면 안 된다. 필요한 무결성 범위에 따라 트랜잭션, 일관된 잠금 순서, 낙관적 충돌 처리 중 무엇을 쓸지 정해야 한다. 행 잠금은 같은 행을 수정하려는 다른 writer를 막고 트랜잭션 종료 시 풀린다.[8]</p>
<h2 id="결제-실패-이탈-타임아웃은-같은-실패가-아니다">결제 실패·이탈·타임아웃은 같은 실패가 아니다<a class="heading-anchor" href="#결제-실패-이탈-타임아웃은-같은-실패가-아니다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>예약을 풀어야 한다고 해서 모든 실패를 같은 방식으로 다루면 다시 문제가 생긴다.</p>
<h3 id="1-결제-실패-응답을-받았다면-즉시-복구한다">1. 결제 실패 응답을 받았다면 즉시 복구한다<a class="heading-anchor" href="#1-결제-실패-응답을-받았다면-즉시-복구한다" aria-label="이 섹션으로 가는 링크">#</a></h3>
<p>카드 거절처럼 결제 실패가 명확하면, 그 주문이 잡아 둔 예약을 바로 해제한다. 이때 해제는 “재고를 1개 더한다”가 아니라, <strong>그 주문의 예약이 아직 살아 있을 때만</strong> 수행해야 한다.</p>
<p>예를 들어 <code>reservation_id</code>와 상태를 두고 <code>RESERVED → RELEASED</code> 전이가 한 번만 일어나게 만든다. 같은 실패 웹훅이 두 번 오거나 운영자가 재처리해도 재고가 두 번 늘지 않아야 한다.</p>
<h3 id="2-결제-화면-이탈은-예약-만료로-복구한다">2. 결제 화면 이탈은 예약 만료로 복구한다<a class="heading-anchor" href="#2-결제-화면-이탈은-예약-만료로-복구한다" aria-label="이 섹션으로 가는 링크">#</a></h3>
<p>브라우저를 닫았다고 결제 실패 이벤트가 항상 오는 것은 아니다. 그래서 예약에는 서버가 판단하는 만료 시각이 필요하다. 만료 시간이 지나면 예약을 찾아 해제하고, 품절 화면도 다시 판매 가능 상태로 바꾼다.</p>
<p>여기서 클라이언트의 카운트다운은 안내일 뿐이다. 실제 복구 권한은 서버의 예약 상태와 만료 시각에 있어야 한다. Stripe도 세션 만료 후 <code>checkout.session.expired</code> 이벤트로 예약 재고를 되돌리도록 설명한다.[1]</p>
<h3 id="3-타임아웃은-즉시-실패가-아니라-결과-미상이다">3. 타임아웃은 즉시 실패가 아니라 결과 미상이다<a class="heading-anchor" href="#3-타임아웃은-즉시-실패가-아니라-결과-미상이다" aria-label="이 섹션으로 가는 링크">#</a></h3>
<p>가장 위험한 경우는 결제 요청을 보냈는데 응답을 못 받는 경우다. 네트워크 타임아웃은 결제사가 요청을 못 받았다는 뜻이 아니다. 이미 승인했지만 응답만 늦었을 수 있다. 외부 호출 타임아웃은 실패가 아니라 결과 미상으로 다룬다가 말하듯, 상대 시스템이 멱등 키나 조회 API를 제공하지 않으면 중복과 유실 중 하나를 감수해야 한다.</p>
<p>그래서 타임아웃 직후 재고를 풀면 안 된다. 이 글에서 말하는 “타임아웃 가능 시간을 넘긴 뒤 복구”는 다음 조건을 포함한다.</p>
<ol><li>결제사별 최대 응답·웹훅 지연 시간을 운영 정책으로 정한다.</li><li>그 시간 전에는 예약을 <code>결제 확인 중</code>으로 유지한다.</li><li>기한 뒤 결제사 조회와 내부 결제 기록에서 성공이 없을 때만 예약을 해제한다.</li><li>늦은 성공 웹훅과 예약 해제가 겹쳐도, 한 상태 전이만 이기도록 조건을 둔다.</li></ol>
<p>이 보수적인 처리는 잠깐의 거짓 품절을 만들 수 있다. 그러나 성공한 결제를 놓치거나 같은 마지막 재고를 두 번 약속하는 것보다 피해가 작다. 결제사가 조회 API나 멱등성 계약을 제공하지 않는다면, 이 판단은 기술 문제가 아니라 사업상 위험 선택이 된다.</p>
<h2 id="재시도는-주문을-다시-만드는-일이-아니다">재시도는 주문을 다시 만드는 일이 아니다<a class="heading-anchor" href="#재시도는-주문을-다시-만드는-일이-아니다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>사용자는 결제 버튼을 두 번 누를 수 있고, 모바일 네트워크는 응답을 잃을 수 있다. 같은 주문 생성 요청이 다시 오면 새 예약이나 새 결제를 만들지 않고, 처음 요청의 결과를 돌려줘야 한다.</p>
<p>HTTP <code>POST</code>는 원래 여러 번 받으면 여러 번 상태를 바꿀 수 있다. <code>Idempotency-Key</code>는 같은 요청을 다시 보낼 때 서버가 한 번만 처리하도록 구분하는 방식이다.[4] Stripe도 동일 키의 재시도에 첫 요청의 결과를 돌려주고, 동일 키에 다른 파라미터가 오면 오류로 처리한다.[5]</p>
<p>주문 API에서는 이 키를 단순 로그 값으로 두면 안 된다. 최소한 다음을 함께 보관해야 한다.</p>
<ul><li>키가 가리키는 주문 또는 예약 ID</li><li><code>processing</code>, <code>completed</code>, <code>failed</code> 같은 처리 상태</li><li>처음 요청의 입력 fingerprint</li><li>응답 결과와 보관 기간</li></ul>
<p>이미 처리 중인 같은 키가 오면 새 주문을 만들지 말고 <code>처리 중</code>을 돌려준다. 완료된 키라면 저장한 결과를 돌려준다. 중복 실행 방지는 멱등성 키의 처리 결과를 재사용한다의 핵심도 키 자체가 아니라, <strong>같은 업무 요청의 결과를 재사용하는 계약</strong>이다.</p>
<p>AWS도 멱등성 토큰과 관련 상태를 영속 저장하고, 토큰 기록과 실제 상태 변경을 적절한 동시성 제어 아래 함께 다뤄야 race condition을 막을 수 있다고 권한다.[6]</p>
<h2 id="결제와-재고를-하나의-db-트랜잭션으로-묶으려-하지-않는다">결제와 재고를 하나의 DB 트랜잭션으로 묶으려 하지 않는다<a class="heading-anchor" href="#결제와-재고를-하나의-db-트랜잭션으로-묶으려-하지-않는다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>재고 DB와 외부 결제사는 하나의 원자적 트랜잭션으로 묶기 어렵다. 그래서 필요한 것은 “둘을 동시에 끝내기”가 아니라, 각 단계의 성공과 되돌림을 명시하는 것이다.</p>
<pre><code class="language-text">판매 가능
  → 예약 생성 / 재고 선점
  → 결제 요청
  → 결제 성공: 예약 확정 / 주문 완료
  → 결제 명시 실패: 예약 해제
  → 고객 이탈: 예약 만료 후 해제
  → 응답 타임아웃: 결제 확인 중 → 조회·기한 확인 후 확정 또는 해제</code></pre>
<p>이런 순차적 로컬 변경과 보상 변경을 다루는 방식이 saga다. AWS는 결제처럼 업무 규칙 때문에 실패한 경우에는 앞 단계의 재고·주문 상태를 되돌리는 보상 트랜잭션을, 인프라 수준 실패에는 재시도 같은 전진 복구를 선택할 수 있다고 설명한다.[2]</p>
<p>주문 확정 뒤 알림·출고·분석 이벤트까지 이어진다면, 재고 상태만 커밋하고 이벤트 발행 전에 죽는 경우도 남는다. 이때는 상태 변경과 이벤트 기록을 같은 트랜잭션으로 남기고, 별도 릴레이가 발행하는 트랜잭셔널 아웃박스를 검토할 수 있다. 다만 단일 서비스의 작은 주문 흐름에 처음부터 복잡한 이벤트 구조를 넣을 이유는 없다. 먼저 예약·확정·해제의 상태 전이가 정확한지 확인해야 한다.</p>
<h2 id="반론-결제보다-재고를-먼저-잡으면-전환을-떨어뜨리지-않나">반론: 결제보다 재고를 먼저 잡으면 전환을 떨어뜨리지 않나<a class="heading-anchor" href="#반론-결제보다-재고를-먼저-잡으면-전환을-떨어뜨리지-않나" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>그럴 수 있다. 예약 시간이 길수록 실제로 결제하지 않는 고객이 마지막 상품을 붙잡고, 다른 고객은 품절만 본다. 희소 상품에는 짧은 예약이 고객에게 압박으로 느껴질 수도 있다.</p>
<p>그래서 이 글의 결론은 모든 커머스에 긴 재고 선점을 넣자는 것이 아니다.</p>
<ul><li>충분히 재고가 많은 상품은 선점보다 결제 성공 뒤 차감이 단순할 수 있다.</li><li>마지막 수량, 한정 판매, 좌석·티켓처럼 초과 판매 비용이 큰 상품은 예약이 더 낫다.</li><li>예약 시간은 UX 문구가 아니라 결제사 응답 한계, 실제 결제 완료 시간, 품절의 비용을 보고 정한다.</li><li>복구 작업은 같은 예약을 두 번 풀지 않도록 멱등해야 한다.</li></ul>
<p>내 기준은 이것이다.</p>
<blockquote><p>결제 취소 경험보다 품절 경험이 낫다면, 마지막 재고는 결제 결과가 아니라 예약 상태로 관리해야 한다.</p></blockquote>
<p>마지막 재고 1개는 먼저 도착한 HTTP 요청의 것이 아니다. 재고를 한 번만 선점하고, 결제 결과가 명확해질 때까지 그 약속을 일관되게 지킬 수 있는 주문의 것이다.</p>
<h2 id="sources">Sources<a class="heading-anchor" href="#sources" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p><a href="https://docs.stripe.com/payments/checkout/managing-limited-inventory?locale=en-GB">1</a> <a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/saga-patterns.html">2</a> <a href="https://www.postgresql.org/docs/current/sql-update.html">3</a> <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Idempotency-Key">4</a> <a href="https://docs.stripe.com/api/idempotent_requests">5</a> <a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/rel_prevent_interaction_failure_idempotent.html">6</a> <a href="https://www.postgresql.org/docs/current/transaction-iso.html">7</a> <a href="https://www.postgresql.org/docs/current/explicit-locking.html">8</a></p>]]></content:encoded>
    </item>
    <item>
      <title>중복 주문은 왜 동시에 실행하면 재고를 두 번 차감할까</title>
      <link>https://changelog.encer.me/posts/order-idempotency-and-inventory</link>
      <guid isPermaLink="true">https://changelog.encer.me/posts/order-idempotency-and-inventory</guid>
      <pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate>
      <description>중복 주문은 버튼을 막는 문제가 아니다. 같은 주문 시도 식별, 주문 하나의 확정, 재고 차감, 후속 이벤트 중복 방지를 분리해 설계하는 방법.</description>
      <category>backend</category>
      <category>data-integrity</category>
      <category>concurrency</category>
      <category>idempotency</category>
      <category>inventory</category>
      <content:encoded><![CDATA[<p>주문 API에 아래 같은 코드가 있다.</p>
<pre><code class="language-text">1. 같은 주문이 있는지 조회한다.
2. 없으면 주문을 만든다.
3. 재고를 차감한다.</code></pre>
<p>평소에는 잘 동작한다. 그런데 결제가 끝난 뒤 응답이 끊겼다. 사용자는 주문이 실패했다고 생각해 다시 누른다. 앱이 타임아웃 뒤 같은 요청을 재시도할 수도 있다. 두 요청이 거의 동시에 첫 조회를 통과하면, 둘 다 아직 주문이 없다고 판단한다. 유일성 제약 없이 사전 조회에만 의존하는 삽입은 이 경쟁을 막지 못한다.[1]</p>
<p>그 결과는 주문이 두 개 생기는 데서 끝나지 않는다. <strong>재고가 두 번 차감된다.</strong></p>
<p>이 문제를 &quot;버튼을 두 번 눌렀다&quot;로 축소하면 해결책도 버튼 비활성화에 머문다. 하지만 실제로는 같은 주문 시도인지 식별하는 책임, 주문 하나만 확정하는 책임, 재고를 한 번만 차감하는 책임, 다음 시스템에서도 다시 실행되지 않게 하는 책임이 서로 다르다.</p>
<h2 id="같은-주문-재시도와-서로-다른-주문은-다르다">같은 주문 재시도와 서로 다른 주문은 다르다<a class="heading-anchor" href="#같은-주문-재시도와-서로-다른-주문은-다르다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>먼저 두 장면을 구분해야 한다.</p>
<ul><li><strong>같은 주문 시도의 재전송</strong>: 응답 유실, 타임아웃, 앱 재시작, 중복 클릭으로 하나의 논리적 주문 요청이 여러 번 서버에 도착한다. 멱등성은 바로 이런 재시도를 중복 부수효과 없이 처리하기 위한 API 성질이다.[2][5]</li><li><strong>서로 다른 정상 주문의 경쟁</strong>: 마지막 재고 하나를 두 고객이 동시에 주문한다. 이 경우에는 요청 키가 아니라 재고 행의 동시 변경을 제어해야 한다.[4]</li></ul>
<p>둘 다 재고 문제처럼 보이지만 같은 실패가 아니다. 같은 주문 재시도는 한 번만 처리해야 하고, 서로 다른 주문은 공정하게 경쟁하되 재고보다 많이 팔면 안 된다.</p>
<blockquote><p>멱등성 키는 &quot;이 요청이 이미 처리한 같은 주문인가&quot;를 묻는다. 잠금과 조건부 갱신은 &quot;지금 이 재고를 바꿔도 되는가&quot;를 묻는다.</p></blockquote>
<p>하나가 다른 하나를 대체하지 않는다.</p>
<h2 id="같은-주문-시도에는-식별자가-필요하다">같은 주문 시도에는 식별자가 필요하다<a class="heading-anchor" href="#같은-주문-시도에는-식별자가-필요하다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p><code>POST /orders</code>는 같은 요청을 두 번 보내면 주문을 두 개 만들기 쉬운 변경 API다. 주문 버튼을 누르는 순간 클라이언트는 <code>idempotencyKey</code>를 만들고, 그 주문 시도를 재시도하는 동안에는 같은 키를 보내야 한다.</p>
<p><code>Idempotency-Key</code>는 POST나 PATCH 같은 변경 요청을 다시 보내도 같은 작업을 한 번만 수행하게 만드는 방법이다. 같은 키는 <strong>같은 재시도</strong>에만 쓰고, 사용자가 새 주문을 시작하면 새 키를 써야 한다.[5]</p>
<p>이 구분이 없으면 흔히 이런 규칙을 만든다.</p>
<blockquote><p>같은 고객이 같은 상품을 5분 안에 다시 주문하면 중복으로 본다.</p></blockquote>
<p>안전해 보이지만 정상 주문도 막는다. 같은 상품을 두 번 사는 사용자는 존재한다. 장바구니를 나눠 결제하거나 선물용으로 한 번 더 주문하는 것도 가능하다. 고객·상품·짧은 시간창은 중복을 <strong>추측</strong>할 뿐, 사용자의 의도를 증명하지 못한다.</p>
<p>멱등성 키는 반대로 사용자가 시작한 주문 시도에 이름을 붙인다. 다만 키만 저장해서는 부족하다. 같은 키로 수량이나 배송지를 바꿔 새 주문을 보내는 실수도 막아야 한다. 요청의 핵심 필드를 지문으로 보관하고, 같은 키에 다른 요청이 오면 오류로 처리하는 정책이 필요하다.[5]</p>
<h2 id="processing은-실패도-성공도-아니다"><code>processing</code>은 실패도 성공도 아니다<a class="heading-anchor" href="#processing은-실패도-성공도-아니다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>같은 키로 들어온 요청에는 최소한 네 가지 결과가 필요하다.</p>
<table><thead><tr><th>상태</th><th>의미</th><th>두 번째 요청에 줄 응답</th></tr></thead><tbody><tr><td><code>processing</code></td><td>첫 요청이 아직 진행 중</td><td>새 주문을 만들지 않고 처리 중임을 알린다</td></tr><tr><td><code>confirmed</code></td><td>주문과 재고 예약·차감이 확정됨</td><td>기존 주문 결과를 돌려준다</td></tr><tr><td><code>rejected</code></td><td>재고 부족 또는 주문 규칙 위반</td><td>같은 거절 결과를 돌려준다</td></tr><tr><td><code>unknown</code></td><td>외부 결제·통신 오류로 결과를 확정하지 못함</td><td>새 주문을 만들지 않고 결과 확인 경로를 제공한다</td></tr></tbody></table>
<p>이 상태를 구분하지 않으면 두 번째 요청에 성공을 지어내거나, 반대로 새 주문을 만들어 버리기 쉽다.</p>
<p>특히 타임아웃은 곧 실패가 아니다. 서버가 주문과 재고 차감을 끝냈지만 응답만 잃었을 수 있다. 외부 결제 요청이 결제사에 도달했는지 알 수 없을 수도 있다. Stripe도 네트워크 오류 뒤에는 같은 키와 같은 파라미터로 재시도하고, 원래 결과가 불명확하면 새 작업을 시작하기 전에 먼저 결과를 조정·확인하라고 안내한다.[7]</p>
<p>그래서 <code>processing</code>과 <code>unknown</code>은 UI에서 불편해 보여도 숨기면 안 된다. 사용자는 &quot;주문 접수 여부를 확인 중입니다&quot;를 보고 주문 상태를 다시 조회할 수 있어야 한다. 시스템은 그 사이 새 주문을 만들지 않아야 한다.[2][7]</p>
<h2 id="조회-후-생성은-동시-요청-앞에서-보장이-아니다">조회 후 생성은 동시 요청 앞에서 보장이 아니다<a class="heading-anchor" href="#조회-후-생성은-동시-요청-앞에서-보장이-아니다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>멱등성 키를 받더라도 이 구현은 안전하지 않다.</p>
<pre><code class="language-text">SELECT * FROM orders WHERE idempotency_key = :key

-- 결과가 없으면
INSERT INTO orders (...)</code></pre>
<p>두 트랜잭션이 동시에 조회하면 둘 다 결과가 없다고 볼 수 있다. &quot;중복이 없음을 확인했다&quot;는 현재 관찰일 뿐, 바로 다음 순간에도 중복이 없다는 보장은 아니다.</p>
<p>주문 하나만 확정하는 규칙은 DB가 강제해야 한다.</p>
<pre><code class="language-sql"><span class="hl-keyword">CREATE</span> <span class="hl-keyword">UNIQUE</span> <span class="hl-keyword">INDEX</span> uq_order_request
<span class="hl-keyword">ON</span> orders (customer_id, idempotency_key);</code></pre>
<p>그리고 충돌은 DB가 판정하게 한다.</p>
<pre><code class="language-sql"><span class="hl-keyword">INSERT</span> <span class="hl-keyword">INTO</span> orders (customer_id, idempotency_key, request_hash, status)
<span class="hl-keyword">VALUES</span> (:customer_id, :<span class="hl-keyword">key</span>, :request_hash, <span class="hl-string">'processing'</span>)
<span class="hl-keyword">ON</span> CONFLICT (customer_id, idempotency_key) DO NOTHING;</code></pre>
<p>삽입에 성공한 요청만 다음 주문 처리로 간다. 충돌한 요청은 기존 행을 조회해 <code>processing</code>, <code>confirmed</code>, <code>rejected</code>, <code>unknown</code> 중 무엇을 반환할지 결정한다.</p>
<p>PostgreSQL은 <code>ON CONFLICT</code>로 유일성 충돌 때의 대체 동작을 지정할 수 있다고 설명한다.[1] 여기서 유일성 제약의 역할은 명확하다. <strong>같은 주문 요청 키를 가진 주문이 두 개 확정되는 것을 막는 것</strong>이다.[1]</p>
<p>이 인덱스를 <code>customer_id + product_id</code>에 걸면 안 된다. 그 조합은 &quot;같은 상품을 두 번 사면 안 된다&quot;는 전혀 다른 제품 정책을 강제한다.</p>
<h2 id="재고-차감은-별도의-정합성-규칙이다">재고 차감은 별도의 정합성 규칙이다<a class="heading-anchor" href="#재고-차감은-별도의-정합성-규칙이다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>이제 서로 다른 두 고객이 마지막 재고 하나를 주문한다고 가정해 보자. 두 요청의 멱등성 키는 다르다. 그러므로 둘을 중복으로 처리하면 안 된다.</p>
<p>재고는 애플리케이션에서 읽어 둔 수량을 나중에 저장하는 방식보다, DB가 현재 재고와 차감 조건을 한 번에 판정하도록 만드는 편이 안전하다.</p>
<pre><code class="language-sql"><span class="hl-keyword">UPDATE</span> inventory
<span class="hl-keyword">SET</span> available_quantity = available_quantity - :quantity
<span class="hl-keyword">WHERE</span> product_id = :product_id
  <span class="hl-keyword">AND</span> available_quantity &gt;= :quantity;</code></pre>
<p>영향받은 행이 하나면 차감 성공이다. 영이면 재고 부족이다. 이 조건부 갱신은 현재 상태를 변경 조건에 넣어, 재고가 이미 바뀐 뒤의 결정을 저장소에서 다시 판정한다.[4]</p>
<p>비관적 잠금이나 낙관적 잠금도 선택지다. 재고 예약, 한 주문의 여러 품목, 수량 계산처럼 규칙이 복잡하면 한 재고 행을 잠가 순서대로 처리해야 할 수 있다. PostgreSQL도 MVCC만으로 원하는 동작을 만들기 어려운 경우 명시적 잠금을 사용할 수 있다고 설명한다.[4]</p>
<p>하지만 잠금만으로는 같은 주문 요청의 재실행을 막지 못한다. 첫 요청이 재고를 차감하고 끝난 뒤 같은 요청이 다시 오면, 두 번째 요청도 잠금을 얻어 순서대로 재고를 또 차감할 수 있다.</p>
<blockquote><p>잠금은 재고 경쟁을 다루고, 멱등성 키는 요청 중복을 다룬다.</p></blockquote>
<h2 id="주문과-재고를-어디까지-한-트랜잭션으로-묶을-것인가">주문과 재고를 어디까지 한 트랜잭션으로 묶을 것인가<a class="heading-anchor" href="#주문과-재고를-어디까지-한-트랜잭션으로-묶을-것인가" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>주문 행 생성과 재고 차감이 같은 DB에 있다면, 둘은 하나의 트랜잭션으로 묶는 것이 출발점이다.</p>
<pre><code class="language-text">1. 멱등성 키로 processing 주문 행을 하나만 확보한다.
2. 조건부 재고 차감을 실행한다.
3. 성공하면 주문을 confirmed로 바꾼다.
4. 재고가 부족하면 주문을 rejected로 바꾼다.
5. 트랜잭션을 커밋한다.</code></pre>
<p>이 흐름은 &quot;주문은 성공인데 재고 차감은 실패&quot; 또는 &quot;재고는 줄었는데 주문이 없다&quot;는 중간 상태를 줄인다.</p>
<p>다만 결제사가 별도 시스템이라면 모든 일을 하나의 DB 트랜잭션으로 끝낼 수 없다. 결제 승인 시점에 재고를 예약할지, 결제 성공 뒤에 차감할지, 예약이 만료되면 언제 되돌릴지는 데이터베이스 기능이 아니라 판매 정책이다. 이 정책이 정해지지 않으면 기술적으로 맞는 잠금 방식을 골라도 주문 경험은 흔들린다.</p>
<h2 id="주문-api가-한-번-안전해도-다음-단계가-남는다">주문 API가 한 번 안전해도 다음 단계가 남는다<a class="heading-anchor" href="#주문-api가-한-번-안전해도-다음-단계가-남는다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>주문을 저장한 뒤 재고 서비스, 결제 서비스, 배송 생성, 알림 발송으로 이벤트를 보낸다면 중복 위험은 다음 시스템으로 이동한다.</p>
<p>DB 커밋은 성공했는데 이벤트를 발행하기 전에 프로세스가 멈출 수 있다. 반대로 이벤트가 나갔는데 DB 작업이 롤백될 수도 있다. transactional outbox는 주문 변경과 이벤트 기록을 같은 DB 트랜잭션에 넣어 이 이중 쓰기 문제를 줄이는 패턴이다.[6]</p>
<p>그렇다고 outbox가 모든 작업을 정확히 한 번 실행하게 만들지는 않는다. 메시지는 중복 전달될 수 있다.[3][6] 재고 예약, 결제 처리, 배송 생성 같은 소비자는 처리한 이벤트 ID를 영속적으로 기록하고, 같은 이벤트를 다시 받으면 기존 결과를 재사용하거나 건너뛰어야 한다.[3]</p>
<p>주문 API의 멱등성과 이벤트 소비자의 멱등성은 서로 다른 계층의 계약이다.</p>
<h2 id="redis-락만-쓰면-되지-않나-라는-반론">&quot;Redis 락만 쓰면 되지 않나&quot;라는 반론<a class="heading-anchor" href="#redis-락만-쓰면-되지-않나-라는-반론" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>Redis 같은 분산 락으로 상품 단위 처리를 한 번에 만들 수는 있다. 하지만 이것만을 최종 정합성 장치로 두면 락 만료, 네트워크 단절, 소유권 상실, 락을 잡은 뒤 DB 반영 전 실패 같은 더 어려운 상태를 다뤄야 한다.</p>
<p>분산 락은 특정 임계 구역의 부하를 줄이는 보조 수단이 될 수 있다. 하지만 주문 하나의 확정은 DB 유일성 제약으로, 재고 수량의 하한은 조건부 갱신이나 DB 잠금으로 보장하는 편이 더 직접적이다. 락은 그 규칙을 대신하기보다 보완해야 한다.</p>
<h2 id="무엇을-한-번만-하게-만들-것인가">무엇을 한 번만 하게 만들 것인가<a class="heading-anchor" href="#무엇을-한-번만-하게-만들-것인가" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>중복 주문을 막는 설계는 기술 하나를 고르는 일이 아니다. 아래 질문에 각각 답해야 한다.</p>
<ol><li>같은 주문 시도에 재시도 내내 유지되는 식별자가 있는가?</li><li>그 식별자가 DB 유일성 제약으로 보호되는가?</li><li>같은 키가 처리 중·성공·거절·결과 미상일 때 각각 다른 결과를 돌려주는가?</li><li>서로 다른 주문이 경쟁해도 재고가 음수가 되지 않도록 조건부 갱신 또는 적절한 잠금이 있는가?</li><li>주문 확정과 재고 차감이 원자적으로 묶이는가?</li><li>결제·배송·알림 이벤트가 다시 전달돼도 같은 부수효과를 반복하지 않는가?</li><li>새 주문에는 반드시 새 키가 만들어지는가?</li></ol>
<p>중복 주문을 막는 코드는 버튼을 비활성화하는 코드가 아니다. <strong>같은 주문인지, 새 주문인지, 재고가 남았는지, 후속 작업이 이미 실행됐는지를 시스템이 각자 증명하게 만드는 코드</strong>다.</p>
<h2 id="참고-자료">참고 자료<a class="heading-anchor" href="#참고-자료" aria-label="이 섹션으로 가는 링크">#</a></h2>
<ul><li>[1] <a href="https://www.postgresql.org/docs/current/sql-insert.html">PostgreSQL — INSERT / ON CONFLICT</a></li><li>[2] <a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/rel_prevent_interaction_failure_idempotent.html">AWS Well-Architected — 멱등 작업 설계</a></li><li>[3] <a href="https://microservices.io/patterns/communication-style/idempotent-consumer.html">Microservices.io — Idempotent Consumer</a></li><li>[4] <a href="https://www.postgresql.org/docs/current/explicit-locking.html">PostgreSQL — Explicit Locking</a></li><li>[5] <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Idempotency-Key">MDN — Idempotency-Key</a></li><li>[6] <a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/transactional-outbox.html">AWS Prescriptive Guidance — Transactional Outbox</a></li><li>[7] <a href="https://docs.stripe.com/error-low-level">Stripe — Advanced error handling</a></li></ul>]]></content:encoded>
    </item>
    <item>
      <title>AI가 코드를 쓰고 AI가 리뷰하면, 우리는 무엇에 책임지는가</title>
      <link>https://changelog.encer.me/posts/what-are-we-responsible-for-when-ai-writes-and-reviews-code</link>
      <guid isPermaLink="true">https://changelog.encer.me/posts/what-are-we-responsible-for-when-ai-writes-and-reviews-code</guid>
      <pubDate>Sat, 17 Oct 2026 00:00:00 GMT</pubDate>
      <description>AI가 작성하고 리뷰하는 코드의 시대에, 코드리뷰가 남겨야 할 것은 코멘트가 아니라 변경 의도·영향·복구를 설명할 책임이라는 주장.</description>
      <category>ai</category>
      <category>code-review</category>
      <category>software-quality</category>
      <category>engineering-process</category>
      <content:encoded><![CDATA[<p>AI가 코드를 만든다. 또 다른 AI가 그 코드를 리뷰한다.</p>
<p>이제는 낯선 풍경이 아니다. 큰 PR이 올라오고, AI 리뷰어가 수십 개의 코멘트를 남긴다. 작성자는 코드를 직접 한 줄씩 작성하지 않았고, 리뷰어도 전체 변경을 끝까지 읽기 어렵다. AI는 그럴듯한 요약과 수정 제안을 남긴다.</p>
<p>그런데 누군가 묻는다.</p>
<blockquote><p>왜 이 구조를 선택했나요?</p>
<p>이 변경으로 기존 동작 중 무엇이 달라지나요?</p>
<p>운영에서 실패하면 어떤 데이터와 사용자가 영향을 받나요?</p>
<p>이 테스트가 통과했다는 것이 왜 안전하다는 뜻인가요?</p></blockquote>
<p>그 질문에 제대로 답할 수 있는 사람이 없다면, 우리는 코드리뷰를 한 것이 아니다. AI의 출력을 서로 확인하는 의식을 치른 것이다.</p>
<p>문제가 생겼을 때 가장 위험한 말은 “AI가 작성한 코드입니다”가 아니다. “내가 작성한 코드가 아닙니다”라는 말이다.</p>
<h2 id="코드리뷰의-병목은-사람이-느려서만-생기지-않는다">코드리뷰의 병목은 사람이 느려서만 생기지 않는다<a class="heading-anchor" href="#코드리뷰의-병목은-사람이-느려서만-생기지-않는다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>반론은 분명하다. AI가 만드는 코드의 양을 사람이 전부 줄 단위로 읽는 방식은 오래가지 못한다.</p>
<p>실제로 기존 코드리뷰 연구도 사람이 모든 심각한 버그를 찾아낸다고 말하지 않는다. 실제 리뷰 변경의 다수는 큰 기능 결함보다 유지보수성, 가독성, 구조, 문서화 같은 진화 가능성에 관한 것이었다.[8] 변경량이 커질수록 코멘트 밀도는 낮아졌고, 코드리뷰의 효율은 리뷰 크기와 밀접하게 연결됐다.[7]</p>
<p>AI는 이 병목에 분명한 도움을 준다. 반복되는 규칙 위반, 타입 오류, 취약한 의존성, 테스트 누락 후보, 변경 파일의 영향 범위, 코드베이스 규칙 위반 후보를 사람보다 빠르게 찾아낼 수 있다. 실제 AI 코드리뷰 연구도 기능 오류 탐지에는 상대적으로 가능성을 보였고, 여러 독립 리뷰를 취합해 잡음을 거르는 방식은 성능을 개선했다.[4]</p>
<p>그러니 이 글은 “AI 리뷰를 쓰지 말자”는 글이 아니다. 인간이 모든 diff를 읽고 승인해야 한다는 주장도 아니다.</p>
<p>문제는 AI가 생성 속도를 올리는 만큼, 팀의 이해와 검증과 책임까지 자동으로 늘어난다고 믿는 데 있다.</p>
<h2 id="ai-리뷰가-남기는-것은-코멘트이고-코드리뷰가-남겨야-하는-것은-책임이다">AI 리뷰가 남기는 것은 코멘트이고, 코드리뷰가 남겨야 하는 것은 책임이다<a class="heading-anchor" href="#ai-리뷰가-남기는-것은-코멘트이고-코드리뷰가-남겨야-하는-것은-책임이다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>AI 리뷰만으로 운영되는 구조가 아직 사람 리뷰를 대체했다고 보기 어려운 근거는 있다.</p>
<p>2026년 코드리뷰 에이전트 연구는 AIDev 데이터셋의 3,109개 PR을 분석했다. AI 리뷰어만 참여한 PR의 병합률은 45.20%였고, 사람 리뷰어만 참여한 PR의 병합률은 68.37%였다. AI 리뷰 전용 PR은 더 높은 폐기율도 보였다.[1]</p>
<p>이 수치만으로 AI가 병합 실패를 일으킨다고 말할 수는 없다. 더 어려운 변경에 AI 리뷰만 달렸을 가능성도 있다. 하지만 적어도 “AI가 리뷰했으니 사람 리뷰를 뺄 수 있다”는 결론을 지지하지는 않는다.</p>
<p>더 중요한 건 피드백의 질이다. 이 연구에서 폐기된 AI 리뷰 전용 PR의 60.2%는 유의미한 피드백 비율이 30% 이하였고, 조사한 13개 AI 리뷰어 중 12개가 평균 신호 비율 60% 미만이었다.[1]</p>
<p>AI가 코멘트를 많이 남기는 것과, 팀이 더 안전한 판단을 하는 것은 다른 일이다.</p>
<p>LLM 기반 코드리뷰의 어려움은 단지 모델이 코드를 못 읽어서가 아니다. 실제 PR 리뷰에는 diff 바깥의 정보가 필요하다. 전체 저장소의 구조, 과거의 설계 결정, 테스트가 표현하지 못한 운영 제약, 팀이 지키기로 합의한 원칙, 고객에게 약속한 동작 같은 정보다.</p>
<p>실제 PR 1,000개와 전체 프로젝트 맥락을 활용한 SWR-Bench 연구는 현재의 AI 리뷰 도구가 현실적인 코드리뷰 환경에서 충분한 성능에 이르지 못했다고 평가했다. 상위 조합도 낮은 정밀도, 즉 많은 false positive가 문제였고, 그 검증 비용은 결국 사람에게 돌아간다.[4]</p>
<p>AI가 “이 코드는 null 가능성이 있습니다”라고 말할 수는 있다. 하지만 “이 값이 null이어도 되는 이유가 이전 버전 앱과의 호환성 때문인가”, “이 변경이 다음 주 프로모션 트래픽에서 어떤 비용을 만들 것인가”는 코드 조각만 보고 판단하기 어렵다.</p>
<h2 id="더-위험한-것은-ai가-놓치는-문제보다-사람이-더-이상-찾지-않는-문제다">더 위험한 것은 AI가 놓치는 문제보다, 사람이 더 이상 찾지 않는 문제다<a class="heading-anchor" href="#더-위험한-것은-ai가-놓치는-문제보다-사람이-더-이상-찾지-않는-문제다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>AI 리뷰를 먼저 보여주면 사람은 그것을 참고해서 더 잘 리뷰할 것처럼 보인다. 그러나 자동 리뷰가 리뷰어의 시선을 좁힐 위험도 있다.</p>
<p>전문 개발자 29명을 대상으로 한 통제 실험에서, 자동 코드리뷰를 먼저 받은 리뷰어는 AI가 지목한 코드 위치에 집중하는 경향을 보였다. 자동 리뷰는 저심각도 문제를 더 많이 발견하게 했지만, 고심각도 문제를 더 많이 찾게 하지는 못했다. 리뷰 시간도 절약되지 않았다.[14]</p>
<p>이 결과가 말하는 것은 AI 리뷰가 쓸모없다는 뜻이 아니다. AI가 리뷰의 출발점이 될 때, 사람의 리뷰가 AI가 정한 범위 안으로 줄어들 수 있다는 뜻이다.</p>
<p>이 변화는 조용히 일어난다.</p>
<ul><li>작성자는 AI가 만든 코드를 충분히 이해하지 못한다.</li><li>리뷰어는 AI가 남긴 코멘트를 처리하는 데 시간을 쓴다.</li><li>AI가 언급하지 않은 설계, 데이터, 운영, 고객 영향 문제는 검토 대상에서 밀린다.</li><li>결과적으로 아무도 전체 변경의 의미를 소유하지 않는다.</li></ul>
<p>그래서 AI 시대의 코드리뷰에서 더 중요한 질문은 “AI가 버그를 몇 개 잡았는가”가 아니다.</p>
<blockquote><p>이 변경을 왜 하는지, 무엇이 달라지는지, 실패하면 누가 어떻게 복구할지를 아는 사람이 있는가?</p></blockquote>
<p>이다.</p>
<h2 id="동료-리뷰가-만들던-것은-버그-탐지보다-공동-소유권이었다">동료 리뷰가 만들던 것은 버그 탐지보다 공동 소유권이었다<a class="heading-anchor" href="#동료-리뷰가-만들던-것은-버그-탐지보다-공동-소유권이었다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>코드리뷰는 기술적 절차이면서 동시에 사회적 절차다.</p>
<p>코드 품질에 대한 책임성을 연구한 논문은 전통적 동료 리뷰에서 개인의 책임이 집단적 책임으로 확장된다고 설명한다. 작성자는 동료에게 자신의 결정을 설명해야 하고, 리뷰어는 그 판단에 질문하고 참여한다. 개인의 품질 기준, 프로로서의 성실성, 좋은 코드를 만들고자 하는 자부심, 팀 안에서의 평판이 이 책임을 떠받친다.[2]</p>
<p>그 연구는 LLM 기반 리뷰가 이 상호 책임 구조를 약화시킬 수 있다고 지적한다. 사람 동료에게는 나중에 설명해야 하지만, LLM에게는 설명을 요구받지 않는다. LLM은 장애가 났을 때 호출되지 않고, 운영 중인 고객을 위해 복구를 결정하지도 않는다.[2]</p>
<p>그래서 “AI가 리뷰했다”는 문장은 품질 보증의 문장이 아니다. 책임의 주체가 없는 경우에는 오히려 책임을 흐리는 문장이 될 수 있다.</p>
<p>이 문제는 AI가 나쁘기 때문이 아니다. 팀이 AI를 추가하면서 기존의 책임 구조를 대체할 설계를 하지 않았기 때문이다.</p>
<h2 id="프로라면-미래의-모델을-믿기-전에-현재의-회사를-지켜야-한다">프로라면 미래의 모델을 믿기 전에 현재의 회사를 지켜야 한다<a class="heading-anchor" href="#프로라면-미래의-모델을-믿기-전에-현재의-회사를-지켜야-한다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>AI가 더 좋아질 가능성은 높다. 언젠가 인간이 모든 구현 코드를 읽지 않아도 되는 범위는 분명히 넓어질 것이다.</p>
<p>실제로 인간 코드리뷰의 종말을 주장하는 견해도 있다. AI 에이전트가 결함 탐지, 스타일, 지식 전달, 팀 인식이라는 코드리뷰의 목표를 더 싸고 빠르게 수행할 수 있으며, AI가 작성한 코드를 다시 인간이 필수적으로 읽는 구조는 확장 불가능하다는 주장이다.[10]</p>
<p>이 주장을 가볍게 넘길 필요는 없다. 사람 리뷰도 완벽하지 않고, AI가 생성하는 코드량을 과거 방식 그대로 감당할 수는 없다.</p>
<p>하지만 아직 오지 않은 완전 자동화의 미래를 전제로, 오늘의 품질과 책임을 포기하는 것은 다른 문제다.</p>
<p>METR이 2025년 숙련 오픈소스 개발자 16명에게 실제 작업 246개를 수행하게 한 무작위 통제 실험에서는, AI 사용 허용 조건의 작업이 평균 19% 더 오래 걸렸다. 참가자들은 AI가 자신을 더 빠르게 만들었다고 믿었지만, 실제 측정은 달랐다.[5]</p>
<p>이 결과가 AI가 개발자를 항상 느리게 만든다는 뜻은 아니다. METR도 모든 업무와 모든 팀에 일반화할 수 없다고 명시한다.[5] 다른 연구에서는 AI 지원으로 초기 구현 시간을 줄였고, 후속 개발자가 해당 코드를 발전시킬 때 품질이나 시간의 뚜렷한 불이익을 찾지 못하기도 했다.[6]</p>
<p>그러므로 결론은 “AI는 아직 못 믿는다”가 아니다.</p>
<blockquote><p>AI가 이미 충분히 믿을 만한 영역과, 아직 사람이 책임져야 하는 영역을 구분하지 않는 팀이 위험하다.</p></blockquote>
<p>돈을 받고 일하는 프로라면, 언젠가 저렴하고 완벽한 모델이 올 수 있다는 기대만으로 회사의 현재 품질 프로세스를 비울 수 없다. 프로의 책임은 모델의 미래 성능을 예측하는 데 있지 않다. 지금의 시스템이 장애가 나도 설명되고, 복구되고, 다시 개선될 수 있게 만드는 데 있다.</p>
<h2 id="ai-시대에-리뷰어가-봐야-할-것은-모든-줄이-아니다">AI 시대에 리뷰어가 봐야 할 것은 모든 줄이 아니다<a class="heading-anchor" href="#ai-시대에-리뷰어가-봐야-할-것은-모든-줄이-아니다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>AI 시대의 해답은 사람에게 더 많은 diff를 읽게 하는 것이 아니다.</p>
<p>반복 가능하고 기계적으로 검증할 수 있는 일은 최대한 코드와 자동화에 맡겨야 한다.</p>
<ul><li>lint, format, 타입 검사</li><li>빌드와 테스트</li><li>취약한 의존성, 시크릿, 라이선스 검사</li><li>정적 분석</li><li>변경 파일과 의존성 영향 범위 탐색</li><li>코드베이스 규칙 위반 후보 탐지</li><li>대규모 diff의 기능 단위 분해와 요약</li></ul>
<p>이런 작업에서 AI는 사람이 놓치기 쉬운 후보를 빠르게 넓혀주는 좋은 도구가 될 수 있다.</p>
<p>그러나 다음은 누군가가 자기 이름으로 책임져야 한다.</p>
<ul><li>왜 지금 이 변경이 필요한가</li><li>기존 동작 중 무엇이 달라지는가</li><li>사용자, 데이터, 운영, 비용에는 어떤 영향이 있는가</li><li>테스트가 통과해도 남는 위험은 무엇인가</li><li>실패하면 어떻게 감지하고 되돌릴 것인가</li><li>누가 이 위험을 이해하고 승인했는가</li></ul>
<p>그래서 AI 시대의 PR에는 코드 diff보다 먼저 변경 책임 패킷이 있어야 한다.</p>
<ol><li><strong>변경 이유</strong> — 해결하려는 문제는 무엇인가</li><li><strong>정확한 변경 내용</strong> — 무엇을 추가·수정·삭제하는가</li><li><strong>영향 범위</strong> — 사용자, 데이터, API, 운영, 비용에 미치는 영향은 무엇인가</li><li><strong>기존 동작과의 차이</strong> — 무엇이 달라지고, 호환성은 어떻게 달라지는가</li><li><strong>검증 근거</strong> — 어떤 테스트, 로그, 실행 결과, 실행계획으로 확인했는가</li><li><strong>위험과 롤백 계획</strong> — 실패하면 어떻게 감지하고 되돌리는가</li><li><strong>승인 주체</strong> — 누가 이 판단을 이해하고 승인했는가</li></ol>
<p>이 문서는 형식적인 PR 템플릿이 아니다. AI가 코드 생산량을 늘릴수록 팀이 잃기 쉬운 이해와 책임을 되돌려놓는 장치다.</p>
<h2 id="마치며">마치며<a class="heading-anchor" href="#마치며" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>AI가 코드를 쓰고 AI가 리뷰할 수 있다. 앞으로 그 비중은 더 커질 것이다.</p>
<p>하지만 장애가 난 뒤에 “AI가 그렇게 제안했습니다”라고 말하는 것으로는 고객도, 동료도, 회사도 복구할 수 없다.</p>
<p>AI 시대의 코드리뷰는 인간이 모든 줄을 읽는 절차로 남을 수 없다. 대신 더 분명해져야 한다. AI는 탐색하고, 검사하고, 요약하고, 제안할 수 있다. 사람은 변경의 의도와 영향, 위험과 복구, 최종 책임을 가진다.</p>
<p>프로는 AI가 만든 코드를 무조건 의심하는 사람이 아니다.</p>
<p><strong>AI가 더 좋아져도 회사가 지속 가능한 품질을 유지하도록, 무엇을 자동화하고 무엇을 사람이 책임질지 설계하는 사람이다.</strong></p>
<h2 id="sources">Sources<a class="heading-anchor" href="#sources" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>[1] https://dl.acm.org/doi/full/10.1145/3793302.3793614 [2] https://arxiv.org/html/2502.15963v1 [4] https://arxiv.org/html/2509.01494v1 [5] https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study [6] https://arxiv.org/abs/2507.00788v2 [7] https://ietresearch.onlinelibrary.wiley.com/doi/full/10.1049/iet-sen.2020.0134 [8] https://pmc.ncbi.nlm.nih.gov/articles/PMC9489594 [10] https://arxiv.org/html/2606.13175v1 [14] https://arxiv.org/html/2411.11401v1</p>]]></content:encoded>
    </item>
    <item>
      <title>AI 에이전트에게 일을 맡기기 전에, 승인 지점부터 설계해야 한다</title>
      <link>https://changelog.encer.me/posts/design-approval-points-before-delegating-to-agents</link>
      <guid isPermaLink="true">https://changelog.encer.me/posts/design-approval-points-before-delegating-to-agents</guid>
      <pubDate>Fri, 16 Oct 2026 00:00:00 GMT</pubDate>
      <description>AI 에이전트에 실행 권한을 주기 전에 기본 차단·실행 전 검증·기준 명시라는 승인 지점을 설계해야 하는 이유를 다룬다.</description>
      <category>ai-agent</category>
      <category>safety</category>
      <category>harness-engineering</category>
      <category>operations</category>
      <category>approval-workflow</category>
      <content:encoded><![CDATA[<p>AI 에이전트가 데이터베이스를 지우는 문제는 모델이 멍청해서 생기지 않는다. 데이터베이스를 지울 수 있게 만든 하네스와, 지우기 전에 확인할 것을 요구하지 않은 운영 방식에서 시작된다.</p>
<p>최근 팀에서 개발 중인 프로젝트의 데이터베이스를 지워버린 일이 있었다. 운영 환경에서 10분짜리 쿼리를 만든 일도 있었다. 둘 다 당사자는 일을 끝내려 했을 것이다. 문제는 실행 전 단계에 없었다. 어떤 명령을 돌리는지, 얼마나 오래 걸리는지, 어떤 데이터를 건드리는지, 되돌릴 방법이 있는지를 확인하게 만드는 장치가 없었다.</p>
<p>AI 에이전트를 쓰기 시작하면서 이 문제는 더 빨라진다. 사람은 위험한 명령 앞에서 잠깐 멈추기도 한다. 반면 에이전트는 주어진 목표를 가장 짧은 경로로 끝내려 한다. &quot;정리해줘&quot;와 &quot;삭제해줘&quot;의 경계가 시스템에 명시되어 있지 않다면, 에이전트에게는 둘 다 수행해야 할 작업일 수 있다.</p>
<p>그래서 에이전트에게 일을 맡기기 전에 먼저 설계해야 할 것은 모델이나 프롬프트가 아니다. <strong>어디까지는 제안이고, 어디부터는 승인이 필요한 실행인지</strong>다.</p>
<h2 id="실행-권한을-막는-것만으로는-부족하다">실행 권한을 막는 것만으로는 부족하다<a class="heading-anchor" href="#실행-권한을-막는-것만으로는-부족하다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>처음에는 위험한 명령을 막으면 충분하다고 생각하기 쉽다. <code>DROP</code>, <code>DELETE</code>, <code>TRUNCATE</code>, 프로덕션 배포 같은 명령을 하네스에서 차단하는 방식이다. 이것은 반드시 필요하다. 데이터 삭제처럼 되돌리기 어렵고 영향 범위가 큰 작업을 모델의 판단에 맡길 이유는 없다.</p>
<p>다만 이것만으로는 부족하다.</p>
<p>운영에서 오래 걸리는 쿼리가 좋은 예다. 쿼리 문법은 정상일 수 있다. 데이터를 지우지도 않는다. 하지만 실행계획을 보지 않고, 대상 범위를 확인하지 않고, 트래픽이 있는 시간에 돌리면 서비스에 영향을 줄 수 있다. 위험은 명령어 이름에만 있지 않다. 어떤 데이터에, 어떤 규모로, 어떤 환경에서 실행하느냐에 있다.</p>
<p>레거시 코드도 마찬가지다. 에이전트는 현재 저장소에서 가장 가까운 예시를 따라 한다. 그런데 가까이에 있는 코드가 오래된 예외 처리, 직접 SQL 실행, 무분별한 전역 상태, 이미 피해야 할 패턴이라면 그럴듯하게 복제한다. 코드를 읽고 기존 패턴을 따라 한 것이므로, 에이전트 입장에서는 합리적인 행동이다.</p>
<p>그러니 하네스가 해야 할 일은 위험 명령을 막는 데서 끝나지 않는다. <strong>따라야 할 기준을 먼저 주고, 기준 밖의 실행은 멈추게 만드는 일</strong>까지 포함해야 한다.</p>
<h2 id="에이전트는-실행자가-아니라-변경-제안자여야-한다">에이전트는 실행자가 아니라 변경 제안자여야 한다<a class="heading-anchor" href="#에이전트는-실행자가-아니라-변경-제안자여야-한다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>내가 원하는 에이전트는 &quot;완료했습니다&quot;라고 말하는 에이전트가 아니다. 변경을 제안하고, 사람이 판단할 수 있게 필요한 근거를 정리하는 에이전트다.</p>
<p>변경을 실행하기 전에 최소한 아래 내용은 보여줘야 한다.</p>
<ol><li><strong>이유</strong> — 왜 지금 이 변경이 필요한가.</li><li><strong>정확한 실행 내용</strong> — 어떤 명령, 어떤 파일, 어떤 쿼리, 어떤 환경에서 실행하는가.</li><li><strong>영향 범위</strong> — 데이터, 사용자, 성능, 다른 서비스, 배포 흐름 중 무엇이 달라지는가.</li><li><strong>위험성</strong> — 실패했을 때 무엇이 깨지고, 되돌릴 수 있는가.</li><li><strong>기존 동작과의 차이</strong> — 지금까지의 사용자 경험과 시스템 동작에서 무엇이 바뀌는가.</li><li><strong>공유 대상</strong> — 이 변경을 알고 있거나 승인해야 하는 팀원은 누구인가.</li></ol>
<p>이건 결재를 위한 문서 작업이 아니다. 에이전트가 자신이 모르는 맥락을 드러내는 방식이다. 이 정보가 없으면 사람도 승인할 수 없고, 에이전트가 놓친 조건도 발견하기 어렵다.</p>
<p>좋은 승인 지점은 사람을 매번 붙잡아 두는 장치가 아니다. 사람이 봐야 할 순간만 선명하게 만든다. 파일을 읽고, 테스트를 돌리고, 초안을 쓰고, 로컬에서 실행하는 일은 빠르게 자동화할 수 있다. 하지만 프로덕션 데이터 변경, 외부 사용자에게 영향을 주는 메시지 발송, 배포, 권한 변경처럼 되돌리기 어렵거나 영향이 넓은 일은 멈춰야 한다.</p>
<h2 id="하네스는-세-층으로-작동해야-한다">하네스는 세 층으로 작동해야 한다<a class="heading-anchor" href="#하네스는-세-층으로-작동해야-한다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>첫 번째는 <strong>기본 차단</strong>이다. 데이터베이스 삭제, 대량 삭제, 운영 자격 증명 변경, 직접 배포처럼 위험이 명확한 작업은 애초에 실행 권한을 주지 않는다. &quot;조심해서 하라&quot;는 프롬프트보다 하네스의 차단 규칙이 강하다.</p>
<p>두 번째는 <strong>실행 전 검증</strong>이다. 쿼리라면 실행계획과 대상 범위, 예상 실행 시간, 인덱스 사용 여부를 확인한다. 코드 변경이라면 테스트 결과와 변경 diff, 영향을 받는 경로를 남긴다. 에이전트가 결과를 말하는 대신, 결과를 믿을 수 있는 근거를 제출하게 하는 단계다.</p>
<p>세 번째는 <strong>따라야 할 기준의 명시</strong>다. 레거시가 아니라 현재의 설계 원칙, 허용된 라이브러리, 안전한 코드 예시, 금지 패턴, 테스트 기준을 에이전트가 먼저 읽게 해야 한다. 에이전트는 빈 저장소에서 일하지 않는다. 주변 맥락을 학습한다. 그러므로 어떤 맥락을 앞에 두는지가 결과를 결정한다.</p>
<p>이 세 층은 서로 대체되지 않는다. 차단 규칙만 있으면 문법적으로 안전하지만 운영상 위험한 실행을 놓친다. 검증만 있으면 위험한 행동을 시도하는 비용이 남는다. 기준만 있으면 마지막 실행 권한이 열려 있는 문제가 남는다.</p>
<h2 id="자동화율보다-중요한-것은-멈춤의-품질이다">자동화율보다 중요한 것은 멈춤의 품질이다<a class="heading-anchor" href="#자동화율보다-중요한-것은-멈춤의-품질이다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>최근 읽은 사례들도 같은 방향을 가리킨다.</p>
<p>라인은 AI로 E2E 회귀 테스트를 생성하면서, 테스트가 통과했지만 원래 의도와 다른 대상을 검증하는 문제를 만났다. 해결책은 더 많은 코드 생성이 아니라 locator 검증, 기대 결과 점검, 사람 리뷰를 포함한 파이프라인이었다. 자동화의 목표는 사람을 없애는 것이 아니라 사람이 봐야 할 곳을 줄이고 분명하게 만드는 것이었다.<sup class="footnote-ref"><a href="#fn-1-line" id="fnref-1-line">1</a></sup></p>
<p>로앤컴퍼니의 VOC 조사 에이전트도 조사·초안·문서화는 AI가 맡고, 검증·담당자 지정·코드 수정은 사람이 맡도록 역할을 나눴다.<sup class="footnote-ref"><a href="#fn-2-lawcompany" id="fnref-2-lawcompany">2</a></sup> 모두의주차장은 음성 에이전트를 만들면서도 결제는 사용자가 버튼으로 확인한 뒤에만 실행하게 했다.<sup class="footnote-ref"><a href="#fn-3-modooparking" id="fnref-3-modooparking">3</a></sup></p>
<p>이 사례들의 공통점은 AI를 덜 믿어서가 아니다. 오히려 AI를 실제 업무 흐름에 넣기 위해, 믿어도 되는 일과 반드시 확인해야 하는 일을 구분했다는 데 있다.</p>
<p>에이전트를 도입하는 팀이 물어야 할 질문은 &quot;어디까지 자동화할 수 있나&quot;가 아니다.</p>
<p><strong>이 실행은 누가 승인해야 하는가. 승인하려면 무엇을 봐야 하는가. 그리고 그 정보는 실행 전에 자동으로 준비되는가.</strong></p>
<p>이 세 질문에 답할 수 있어야 에이전트는 속도를 내도 팀을 불안하게 만들지 않는다.</p>
<h2 id="마치며">마치며<a class="heading-anchor" href="#마치며" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>AI 에이전트는 개발자를 대체하는 실행자가 아니라, 더 나은 판단을 위한 준비를 빠르게 만드는 동료에 가깝다. 좋은 에이전트는 많은 권한을 받은 에이전트가 아니다. 위험한 곳에서 스스로 멈추고, 사람이 판단할 근거를 빠뜨리지 않고, 이미 낡은 관행 대신 팀이 정한 기준을 따라가는 에이전트다.</p>
<p>신뢰는 한 번의 멋진 데모에서 생기지 않는다. 내가 직접 했을 때와 비슷한 결과를 반복해서 내고, 내가 놓친 위험을 먼저 찾아내고, 중요한 실행 앞에서는 정확한 제안을 남기고 기다릴 때 쌓인다.</p>
<p>그래서 에이전트의 자율성을 키우기 전에, 먼저 승인 지점을 설계해야 한다.</p>
<hr />
<h2 id="참고">참고<a class="heading-anchor" href="#참고" aria-label="이 섹션으로 가는 링크">#</a></h2>
<section class="footnotes">
<hr />
<ol><li id="fn-1-line"><a href="https://techblog.lycorp.co.jp/ko/ai-e2e-test-automation">AI로 E2E 회귀 테스트 자동화하기: ‘딸깍’을 파이프라인으로 바꾸기까지</a>, LY Corporation Tech Blog. <a href="#fnref-1-line" class="footnote-backref">↩</a></li><li id="fn-2-lawcompany"><a href="https://aws.amazon.com/ko/blogs/tech/law-and-company-hackathon-voc-agent/">AWS X 로앤컴퍼니 AI 해커톤 : VOC 자동 조사 에이전트 “민붕이” 개발기</a>, AWS 기술 블로그. <a href="#fnref-2-lawcompany" class="footnote-backref">↩</a></li><li id="fn-3-modooparking"><a href="https://aws.amazon.com/ko/blogs/tech/modooparking-ai-voice-parking-ai-dlc/">모두의주차장 AI 음성 주차 개발기: AWS AI-DLC 워크숍에서 출시까지</a>, AWS 기술 블로그. <a href="#fnref-3-modooparking" class="footnote-backref">↩</a></li></ol>
</section>]]></content:encoded>
    </item>
    <item>
      <title>AI 코딩 에이전트가 늘어날수록 개발자는 왜 더 시스템 엔지니어가 되는가</title>
      <link>https://changelog.encer.me/posts/coding-agents-and-system-engineers</link>
      <guid isPermaLink="true">https://changelog.encer.me/posts/coding-agents-and-system-engineers</guid>
      <pubDate>Wed, 14 Oct 2026 00:00:00 GMT</pubDate>
      <description>AI 코딩 에이전트가 늘수록 개발자의 산출물이 코드에서 하네스·문서·검증·권한 경계로 넓어지는 이유를 설명한다.</description>
      <category>ai</category>
      <category>coding-agent</category>
      <category>software-engineering</category>
      <category>harness-engineering</category>
      <content:encoded><![CDATA[<p>AI 코딩 에이전트가 코드를 쓴다고 해서 개발자의 일이 사라지는 것은 아니다. 정확히는, 개발자의 일이 코드 바깥으로 밀려난다.</p>
<p>예전에는 개발자의 실력을 구현 속도에서 먼저 확인했다. 요구사항을 읽고, 설계를 세우고, 코드를 쓰고, 테스트를 통과시키는 속도 말이다. 지금은 에이전트가 여러 파일을 읽고, 변경하고, 테스트와 린트를 실행한 뒤 PR까지 만들 수 있다. 구현만 놓고 보면 사람의 손이 병목이 아니게 되는 순간이 분명히 늘고 있다.</p>
<p>그렇다고 개발이 자동화되는 것은 아니다. 오히려 구현이 빨라진 만큼, 이전에는 코드 작성 과정에 흩어져 있던 판단이 더 선명한 병목으로 드러난다. 무엇을 만들지, 어디까지 바꿔도 되는지, 무엇을 성공으로 볼지, 어떤 실패는 절대 허용할 수 없는지. 에이전트는 이 질문에 대한 답을 스스로 갖고 있지 않다.</p>
<p>그래서 AI 코딩 에이전트 시대의 개발자는 코더라기보다 <strong>에이전트가 일할 수 있는 시스템을 설계하는 사람</strong>에 가까워진다.</p>
<h2 id="코드를-시키는-일보다-일을-정의하는-일이-어려워진다">코드를 시키는 일보다, 일을 정의하는 일이 어려워진다<a class="heading-anchor" href="#코드를-시키는-일보다-일을-정의하는-일이-어려워진다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>에이전트에게 “로그인 화면을 개선해 줘”라고 말하면 그럴듯한 결과는 금방 나온다. 하지만 그 안에는 아직 결정되지 않은 질문이 너무 많다.</p>
<ul><li>기존 인증 흐름은 유지해야 하는가?</li><li>실패 메시지는 어떤 상황에서, 누구에게 보여야 하는가?</li><li>모바일 접근성과 키보드 탐색은 성공 조건에 포함되는가?</li><li>새 UI는 어떤 지표를 개선해야 하는가?</li><li>장애가 발생했을 때 되돌릴 수 있는가?</li></ul>
<p>사람이 코드를 직접 쓸 때도 이 질문들은 존재했다. 다만 구현하면서 머릿속에서 함께 풀었다. 에이전트에게 작업을 넘기면 이 암묵지가 사라진다. 에이전트가 볼 수 없는 맥락은, 사실상 존재하지 않는 맥락이 된다.</p>
<p>그래서 좋은 프롬프트는 장황한 요청문이 아니다. 좋은 프롬프트는 이미 정리된 제품 의도와 기술적 제약의 압축본이다. 수용 기준, 관련 문서, 변경 가능 범위, 검증 명령, 금지해야 할 선택지가 저장소와 작업 지시에 드러나 있어야 한다.</p>
<p>개발자의 첫 번째 역할은 에이전트에게 답을 대신 주는 것이 아니라, <strong>좋은 답을 만들 수 있는 문제의 경계</strong>를 만드는 일이다.</p>
<h2 id="테스트가-있어도-판단은-끝나지-않는다">테스트가 있어도, 판단은 끝나지 않는다<a class="heading-anchor" href="#테스트가-있어도-판단은-끝나지-않는다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>에이전트는 테스트를 실행하고 실패를 고친다. 이 능력은 중요하다. 실행 결과라는 현실의 피드백을 보고 다음 행동을 바꿀 수 있기 때문이다.</p>
<p>하지만 테스트 통과는 머지 승인과 같은 말이 아니다.</p>
<p>테스트는 대부분 “작성된 기대”를 검증한다. 반면 실제 운영에서 중요한 문제는 종종 기대 바깥에 있다. 이미 있는 컴포넌트를 또 만들지는 않았는지, 에러를 조용히 삼키지는 않았는지, 도메인 규칙을 UI에 흩뜨리지는 않았는지, 지금의 편한 구현이 다음 변경을 어렵게 만들지는 않았는지 같은 문제다.</p>
<p>AI가 만든 코드는 특히 그럴듯해서 위험하다. 타입도 맞고, 테스트도 통과하고, 이름도 자연스럽다. 하지만 코드베이스의 오래된 약속을 깨거나, 제품이 감당할 수 없는 트레이드오프를 선택할 수 있다.</p>
<p>그러므로 리뷰의 역할은 “AI가 실수했는지 잡아내는 마지막 관문”에 그치면 안 된다. 리뷰에서 반복해서 발견되는 문제는 다음번에도 사람이 다시 찾지 않도록 바꿔야 한다.</p>
<ul><li>누락되는 입력 검증은 타입·스키마·경계 검사로.</li><li>반복되는 스타일 논쟁은 formatter·lint rule로.</li><li>계층 침범은 구조 테스트로.</li><li>배포 후에야 드러나는 성능 문제는 SLO와 관측성으로.</li><li>도메인 해석의 흔들림은 ADR과 예시로.</li></ul>
<p>좋은 리뷰는 코멘트를 남기는 일이 아니라, <strong>다음 리뷰에서 같은 코멘트가 필요 없게 만드는 일</strong>이다.</p>
<h2 id="에이전트가-강할수록-저장소의-품질이-중요해진다">에이전트가 강할수록 저장소의 품질이 중요해진다<a class="heading-anchor" href="#에이전트가-강할수록-저장소의-품질이-중요해진다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>AI 코딩 에이전트는 좋은 코드베이스에서 더 잘 일한다. 이는 놀랍지 않다. 새로 온 동료도 비슷하기 때문이다.</p>
<p>어디서 시작해야 하는지 알려 주는 짧은 지도, 도메인별로 분리된 문서, 실제로 실행되는 테스트 명령, 의도를 설명하는 ADR, 변경 이력, 재현 가능한 개발 환경. 이런 것들이 있으면 에이전트는 추측 대신 탐색할 수 있다.</p>
<p>반대로 문서가 낡아 있고, 규칙이 사람의 머릿속이나 메신저에만 있고, 테스트가 불안정하며, 로컬 환경이 재현되지 않는다면 에이전트는 빈칸을 그럴듯한 추측으로 채운다. 그 결과는 빠르게 만들어진 코드가 아니라 빠르게 쌓이는 불확실성이다.</p>
<p>여기서 문서는 지식 저장소 이상의 의미를 갖는다. 에이전트가 저장소를 이해하기 위한 인터페이스다. <code>AGENTS.md</code> 하나에 모든 규칙을 몰아넣는 것보다, 짧은 시작점에서 필요한 문서·규칙·검증 도구로 찾아갈 수 있게 만드는 편이 낫다.</p>
<p>사람이 읽기 좋은 문서 구조와 에이전트가 탐색하기 좋은 문서 구조는 크게 다르지 않다. 둘 다 최신이어야 하고, 서로 연결되어야 하며, 실행 가능한 사실을 담아야 한다.</p>
<h2 id="개발자의-산출물은-코드에서-harness로-넓어진다">개발자의 산출물은 코드에서 harness로 넓어진다<a class="heading-anchor" href="#개발자의-산출물은-코드에서-harness로-넓어진다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>이제 개발자가 남기는 산출물은 애플리케이션 코드만이 아니다.</p>
<ul><li>에이전트가 실패하지 않도록 하는 제약</li><li>에이전트가 성공을 확인할 수 있는 테스트</li><li>에이전트가 맥락을 찾을 수 있는 문서 지도</li><li>에이전트가 실행 중 이상을 발견할 수 있는 로그·메트릭·트레이스</li><li>에이전트가 넘지 말아야 할 권한과 배포 경계</li><li>사람이 최종적으로 승인할 수 있게 만드는 작고 검증 가능한 변경 단위</li></ul>
<p>이 묶음을 흔히 harness라고 부른다. 중요한 것은 에이전트를 세게 통제하는 것이 아니다. 에이전트가 좋은 방향으로 빠르게 움직이고, 잘못된 방향으로 갈 때는 스스로 또는 조기에 멈출 수 있게 만드는 것이다.</p>
<p>이 관점에서는 에이전트가 낸 결과가 기대에 못 미쳤을 때 “프롬프트를 더 잘 써야겠다”로 끝나지 않는다. 먼저 묻는다. 이 실패를 다음에도 반복하지 않으려면 무엇을 저장소에 추가해야 하는가? 테스트인가, 규칙인가, 도메인 예시인가, 도구 권한인가, 관측성인가?</p>
<p>이 질문에 답하는 과정에서 개발자의 판단은 일회성 지시가 아니라 팀과 에이전트가 함께 쓰는 시스템의 일부가 된다.</p>
<h2 id="결론-좋은-개발자는-더-적게-타이핑할-수-있다">결론: 좋은 개발자는 더 적게 타이핑할 수 있다<a class="heading-anchor" href="#결론-좋은-개발자는-더-적게-타이핑할-수-있다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>AI 코딩 에이전트가 늘수록 개발자는 더 적게 타이핑할 수 있다. 하지만 더 적게 생각해도 되는 것은 아니다.</p>
<p>오히려 좋은 개발자는 구현을 넘기는 대신 더 어려운 일을 맡는다. 문제를 작고 검증 가능하게 정의하고, 제품·도메인·아키텍처의 맥락을 기록하고, 품질 기준을 기계가 확인할 수 있는 형태로 바꾸고, 최종 결과가 사용자와 시스템에 미칠 영향을 책임진다.</p>
<p>AI가 코드를 만드는 시대에 개발자의 경쟁력은 얼마나 많은 코드를 작성했는지가 아니라, <strong>얼마나 좋은 판단을 반복 가능한 시스템으로 남겼는가</strong>에 달려 있다.</p>
<hr />
<h2 id="참고-자료">참고 자료<a class="heading-anchor" href="#참고-자료" aria-label="이 섹션으로 가는 링크">#</a></h2>
<ul><li><a href="https://openai.com/index/harness-engineering/">Harness engineering: leveraging Codex in an agent-first world — OpenAI</a></li><li><a href="https://www.anthropic.com/engineering/building-effective-agents">Building effective agents — Anthropic</a></li><li><a href="https://www.martinfowler.com/articles/harness-engineering.html">Harness engineering for coding agent users — Martin Fowler</a></li><li><a href="https://github.blog/ai-and-ml/generative-ai/code-review-in-the-age-of-ai-why-developers-will-always-own-the-merge-button/">Code review in the age of AI — GitHub</a></li><li><a href="https://metr.org/blog/2025-08-12-research-update-towards-reconciling-slowdown-with-time-horizons/">Research Update: Algorithmic vs. Holistic Evaluation — METR</a></li></ul>]]></content:encoded>
    </item>
    <item>
      <title>AI 시대에 개발자는 코더가 아니라 설계자가 된다</title>
      <link>https://changelog.encer.me/posts/developers-become-designers-in-the-ai-era</link>
      <guid isPermaLink="true">https://changelog.encer.me/posts/developers-become-designers-in-the-ai-era</guid>
      <pubDate>Wed, 14 Oct 2026 00:00:00 GMT</pubDate>
      <description>AI 코딩 에이전트가 구현을 가속할수록 개발자가 제품 의도·도메인·권한 경계·검증 가능한 업무 사이클을 설계해야 하는 이유.</description>
      <category>ai</category>
      <category>coding-agent</category>
      <category>software-engineering</category>
      <category>product-design</category>
      <content:encoded><![CDATA[<p>AI 코딩 에이전트를 쓰기 시작한 뒤, 개발자의 일이 줄어들었다고 느끼지는 않는다. 대신 일이 이동했다.</p>
<p>기존의 개발 흐름은 비교적 익숙했다. 기획자가 기획서를 작성하면 개발자는 이를 코드로 옮긴다. 코드와 시스템을 검증하고, 다른 개발자와 연결해 다음 단계로 넘긴다. 각 역할은 분업되어 있고, 개발자는 주로 구현과 기술적 검증의 책임을 진다.</p>
<p>에이전트를 처음 쓸 때는 그 구조 안에서 코드 작성만 맡겼다. 반복적인 구현, 테스트 초안, 리팩터링처럼 경계가 비교적 분명한 일을 위임했다. 결과는 예상보다 빨랐다. 그런데 코드 생산 속도가 올라가자, 오히려 다른 병목이 더 선명하게 보이기 시작했다.</p>
<p>기획은 충분히 구체적인가? 이 요청은 왜 필요한가? 사용자는 어디에서 막히는가? 프론트엔드에서 이 흐름은 어떤 경험이 되어야 하는가? 지금 구현하기 쉬운 선택이 나중에도 서비스에 맞는가?</p>
<p>코드를 만드는 속도가 빨라진다고 해서, 이런 질문에 대한 답이 저절로 생기지는 않는다. 오히려 에이전트가 빨라질수록 개발자는 더 빨리 이 질문들 앞에 서게 된다.</p>
<p>그래서 나는 AI 시대의 개발자가 점점 <strong>설계자</strong>가 된다고 생각한다. 시스템을 설계하고, 서비스를 설계하고, 에이전트가 작은 일을 완성하도록 조율하는 사람 말이다.</p>
<h2 id="구현이-빨라지면-기획의-빈칸이-먼저-드러난다">구현이 빨라지면, 기획의 빈칸이 먼저 드러난다<a class="heading-anchor" href="#구현이-빨라지면-기획의-빈칸이-먼저-드러난다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>에이전트에게 기능 하나를 요청하는 일은 어렵지 않다. 화면을 만들고, API를 연결하고, 테스트까지 붙이는 데 걸리는 시간은 크게 줄었다.</p>
<p>문제는 에이전트가 요구사항을 구현할 수는 있어도, 그 요구사항이 정말 문제를 푸는지까지 알지는 못한다는 데 있다.</p>
<p>가령 기획서에 “예약 변경 버튼을 추가한다”라고 적혀 있다고 하자. 에이전트는 버튼, API 호출, 성공 메시지, 테스트를 빠르게 만들 수 있다. 하지만 개발자가 “왜 이 버튼이 필요한가?”라고 묻지 않으면, 더 중요한 사실을 놓칠 수 있다. 사용자가 원하는 것은 버튼 자체가 아니라, 변경 가능한 시간대를 미리 알고 불필요한 문의를 줄이는 것일 수 있다.</p>
<p>그렇다면 해결해야 할 것은 버튼 하나가 아니다. 예약 상세에서 가능한 선택지를 보여 주고, 변경할 수 없는 경우에는 이유와 다음 행동을 알려 주는 사용자 흐름이다.</p>
<p>에이전트는 두 번째 해결책도 구현할 수 있다. 그러나 무엇을 구현해야 하는지 다시 정의하는 일은 에이전트가 대신하지 않는다.</p>
<p>AI를 쓰면서 기획자와 대화하는 시간이 늘어나는 이유도 여기에 있다. 개발자는 더 이상 “기획서에 적힌 것을 구현하는 사람”으로만 남기 어렵다. 기획의 의도를 확인하고, 그 의도를 서비스 흐름과 데이터 구조와 작업 단위로 번역해야 한다.</p>
<p>“무엇을 만들까요?”보다 먼저 “왜 이게 필요하죠?”를 묻는 일이 개발의 중심으로 돌아온다.</p>
<h2 id="프론트엔드까지-닿으면-서비스-전체가-보이기-시작한다">프론트엔드까지 닿으면, 서비스 전체가 보이기 시작한다<a class="heading-anchor" href="#프론트엔드까지-닿으면-서비스-전체가-보이기-시작한다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>구현 속도가 빨라지면 프론트엔드와의 소통도 병목이 된다. 기획 의도를 이해하고, 화면을 만들고, 다시 피드백을 주고받는 과정이 코드 작성보다 오래 걸리기도 한다.</p>
<p>그래서 개발자는 점점 프론트 작업까지 직접 만지게 된다. 이것은 단순히 역할이 하나 더 늘어나는 문제가 아니다. 사용자가 실제로 만나는 경험을 직접 설계하게 되는 변화다.</p>
<p>그때부터 개발자는 API가 정상 응답하는지만 볼 수 없다.</p>
<ul><li>사용자는 처음 무엇을 보고, 무엇을 선택하는가?</li><li>데이터가 없을 때는 무엇을 이해해야 하는가?</li><li>오류가 나면 다음 행동을 알 수 있는가?</li><li>성공한 뒤 사용자는 무엇을 기대하는가?</li><li>이 흐름은 서비스가 해결하려는 문제와 맞는가?</li></ul>
<p>이 질문에 답하려면 결국 도메인을 알아야 한다. 우리 서비스가 누구의 어떤 문제를 풀고 있는지, 그 문제를 푸는 과정에서 어떤 규칙과 예외가 있는지 이해해야 한다.</p>
<p>AI가 개발자를 도메인에서 멀어지게 하는 것이 아니다. 오히려 구현을 위임할 수 있게 될수록, 개발자가 도메인에 대해 얕게 알고 있을 여지는 줄어든다. 도메인을 이해하지 못하면 에이전트에게 무엇을 시킬지, 결과가 맞는지, 다음 작업을 어떻게 나눌지 판단할 수 없기 때문이다.</p>
<h2 id="에이전트는-작은-업무-사이클-안에서-배워야-한다">에이전트는 작은 업무 사이클 안에서 배워야 한다<a class="heading-anchor" href="#에이전트는-작은-업무-사이클-안에서-배워야-한다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>에이전트를 잘 쓰는 방법은 처음부터 거대한 기능을 통째로 던지는 데 있지 않다. 반복되는 하나의 업무 사이클을 함께 완성해 보는 데 있다.</p>
<p>예를 들어 매월 엑셀 파일을 내려받아 데이터를 조합하고, 보고용 통계를 만든 뒤, 마케팅 팀이 원하는 양식으로 정리해서 전달하는 일이 있다고 하자.</p>
<p>처음에는 에이전트에게 데이터를 읽고 초안을 만들게 할 수 있다. 그다음에는 사람이 결과를 직접 만든 결과와 비교한다. 빠진 지표는 없는지, 계산 기준은 같은지, 팀이 실제로 쓰기 좋은 형식인지 확인한다. 몇 번 반복하면서 에이전트가 무엇을 잘하는지, 어디에서 맥락을 놓치는지, 사람이 암묵적으로 하던 판단은 무엇이었는지 드러난다.</p>
<p>프로젝트의 진척과 현황을 정리하는 일도 마찬가지다. 데이터를 모으고, 막힌 일을 분류하고, 다음 행동이 필요한 지점을 드러내는 한 사이클을 같이 돌려 볼 수 있다.</p>
<p>이 과정의 목적은 단순히 자동화를 만드는 것이 아니다. 에이전트와 함께 일하는 방식을 발견하는 것이다.</p>
<ul><li>어떤 원천 데이터를 봐야 하는가?</li><li>어떤 지표는 항상 함께 봐야 하는가?</li><li>어떤 예외는 사람이 판단해야 하는가?</li><li>결과물의 독자는 무엇을 알아야 행동할 수 있는가?</li><li>에이전트가 틀렸을 때, 어디서 쉽게 발견할 수 있는가?</li></ul>
<p>작은 사이클을 여러 번 끝내면서, 개발자는 서비스와 조직의 실제 업무 흐름을 다시 배운다. 에이전트는 도구가 아니라 그 흐름 안에서 함께 일하는 실행 주체가 된다.</p>
<h2 id="신뢰는-결과로-쌓되-권한은-별도로-다뤄야-한다">신뢰는 결과로 쌓되, 권한은 별도로 다뤄야 한다<a class="heading-anchor" href="#신뢰는-결과로-쌓되-권한은-별도로-다뤄야-한다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>에이전트를 신뢰할 수 있는 기준은 단순하다.</p>
<p>첫째, 내가 직접 했을 때와 <strong>같은 결과를 반복해서</strong> 내는가.</p>
<p>둘째, 단순히 사람의 작업을 복제하는 것을 넘어, 내가 직접 했을 때 놓쳤을 문제점까지 찾아내는가.</p>
<p>이 두 가지가 확인되면 에이전트에게 더 많은 분석과 제안을 맡길 수 있다. 하지만 여기서 흔히 생기는 오해가 있다. 좋은 결과를 반복해서 낸다고 해서, 그 에이전트에게 운영 권한까지 줘도 된다는 뜻은 아니다.</p>
<p>우리의 데이터에 직접 접근해 수정하거나 입력하는 권한, 배포 권한, 실제 사용자에게 영향을 주는 권한은 별개다. 잘못된 한 번의 실행이 사용자와 시스템에 직접 영향을 줄 수 있기 때문이다.</p>
<p>그래서 에이전트는 변경을 <strong>제안</strong>할 수는 있어도, 실행은 사람이 승인해야 한다.</p>
<p>좋은 제안에는 최소한 다음이 들어가야 한다.</p>
<ul><li>왜 이 변경이 필요한가</li><li>무엇이 어떻게 바뀌는가</li><li>영향 범위는 어디까지인가</li><li>지금 해야 하는 이유는 무엇인가</li><li>실행 계획과 검증 방법은 무엇인가</li><li>문제가 생겼을 때 어떻게 되돌릴 것인가</li></ul>
<p>이 형식은 결재 문서가 아니다. 개발자가 판단할 수 있게 만드는 인터페이스다. 에이전트의 자율성을 제한하기 위한 장치이기도 하지만, 더 근본적으로는 우리가 무엇을 책임지고 있는지 드러내는 장치다.</p>
<h2 id="개발자는-에이전트에게-일을-던지는-사람이-아니다">개발자는 에이전트에게 일을 던지는 사람이 아니다<a class="heading-anchor" href="#개발자는-에이전트에게-일을-던지는-사람이-아니다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>에이전트를 잘 쓰는 개발자는 많은 프롬프트를 아는 사람이 아닐 것이다.</p>
<p>문제를 작게 나누고, 한 사이클이 끝나는 조건을 정의하고, 기획의 의도를 확인하고, 도메인 규칙을 이해하고, 시스템에 영향을 주는 권한을 분리하고, 반복된 실패를 다음번에는 막을 수 있는 규칙으로 남기는 사람일 것이다.</p>
<p>AI는 코드를 빠르게 만든다. 하지만 서비스가 무엇을 위해 존재하는지, 어떤 변경을 감당할 수 있는지, 사용자가 실제로 더 나아졌는지는 스스로 결정하지 못한다.</p>
<p>그래서 AI 시대의 개발자는 코드를 더 적게 쓸 수 있다. 그러나 더 큰 범위를 설계해야 한다.</p>
<p>시스템을 설계하고, 서비스를 설계하고, 에이전트가 안전하게 작은 일을 완성하도록 조율하는 것.</p>
<p>나는 그것이 앞으로 개발자의 중요한 일이 될 것이라고 생각한다.</p>]]></content:encoded>
    </item>
    <item>
      <title>비싼 모델은 데모를 만들고, 싼 모델은 사업을 만든다</title>
      <link>https://changelog.encer.me/posts/expensive-models-build-demos-cheap-models-build-businesses</link>
      <guid isPermaLink="true">https://changelog.encer.me/posts/expensive-models-build-demos-cheap-models-build-businesses</guid>
      <pubDate>Wed, 14 Oct 2026 00:00:00 GMT</pubDate>
      <description>AI 제품의 경쟁력은 최고급 모델 하나가 아니라 작업의 위험도·검증 가능성·호출량에 맞춰 모델 비용을 배분하는 데 있다.</description>
      <category>ai</category>
      <category>llm</category>
      <category>model-routing</category>
      <category>product-economics</category>
      <content:encoded><![CDATA[<p>AI 업계는 가장 똑똑한 모델에 열광한다.</p>
<p>새 모델이 나올 때마다 우리는 더 어려운 추론을 했다는 데모를 본다. 긴 코드를 한 번에 고치고, 복잡한 문제를 풀고, 사람이 며칠 걸릴 일을 몇 시간 만에 끝낸다. 물론 중요하다. 설계가 필요하거나, 맥락이 복잡하거나, 잘못되었을 때 영향이 큰 일에는 최고급 모델이 필요하다.</p>
<p>하지만 제품을 운영하는 관점에서는 다른 질문이 더 중요하다.</p>
<p><strong>이 모델을 사용자가 매일 써도 되는가?</strong></p>
<p>한 번 보여 주기에 놀라운 결과와, 매일 수천 번 호출해도 비용이 감당되는 결과는 다른 문제다. 최고급 모델은 훌륭한 데모를 만든다. 그러나 반복 사용되는 제품을 실제 사업으로 만드는 것은 대개 저렴한 모델이다.</p>
<h2 id="모든-문제에-가장-똑똑한-모델이-필요한-것은-아니다">모든 문제에 가장 똑똑한 모델이 필요한 것은 아니다<a class="heading-anchor" href="#모든-문제에-가장-똑똑한-모델이-필요한-것은-아니다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>생각보다 많은 일은 단순하다.</p>
<p>문서의 형식을 맞추고, QA 결과를 정리하고, 체크리스트를 만들고, 초안을 쓰고, 긴 내용을 요약하고, 정해진 기준으로 정보를 분류하는 일들이다. 이런 업무는 매번 천재적인 통찰을 요구하지 않는다. 대신 일정한 품질, 빠른 응답, 낮은 비용이 중요하다.</p>
<p>지금 이 글을 쓰는 과정도 그렇다. 좋은 글의 핵심 주장과 관점은 사람이 정한다. 하지만 질문을 정리하고, 생각의 빈칸을 찾고, 구조를 잡고, 초안을 다듬는 일에는 꼭 최고급 모델이 필요하지 않을 수 있다.</p>
<p>QA도 마찬가지다. 위험한 배포를 승인하는 판단은 사람이 해야 한다. 그러나 테스트 결과를 모으고, 빠진 확인 항목을 찾고, 재현 절차를 문서로 정리하는 일은 상대적으로 저렴한 모델로도 충분히 보조할 수 있다.</p>
<p>여기서 중요한 기준은 모델의 등급이 아니다.</p>
<ul><li>이 작업은 위험한가?</li><li>결과가 틀려도 되돌릴 수 있는가?</li><li>사람이 결과를 정확하게 검토할 수 있는가?</li></ul>
<p>위험하지 않고, 되돌릴 수 있고, 사람이 쉽게 검증할 수 있다면 저렴한 모델부터 써 보는 편이 합리적이다.</p>
<h2 id="비싼-모델은-판단에-남겨-둬야-한다">비싼 모델은 판단에 남겨 둬야 한다<a class="heading-anchor" href="#비싼-모델은-판단에-남겨-둬야-한다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>그렇다고 저렴한 모델이 모든 일을 대신할 수 있다는 뜻은 아니다.</p>
<p>시스템 구조를 설계하는 일, 여러 제약을 함께 다뤄야 하는 복잡한 작업, 사용자·데이터·배포에 직접 영향을 주는 위험한 변경은 더 높은 수준의 모델과 사람의 판단이 필요하다.</p>
<p>하지만 여기에도 순서가 있다. 비싼 모델이 낸 답이라고 해서 곧바로 실행할 수 있는 것은 아니다. 운영 데이터 수정, 실제 사용자에게 메시지 발송, 권한 변경, 배포처럼 되돌리기 어려운 행동은 모델의 가격과 무관하게 사람의 승인이 필요하다.</p>
<p>좋은 AI 시스템은 비싼 모델을 신뢰해서 권한을 주는 시스템이 아니다. 작업의 위험도에 따라 모델, 검증, 권한을 각각 다르게 배치하는 시스템이다.</p>
<p>예를 들어 저렴한 모델은 문서를 요약하고 QA 초안을 만들 수 있다. 더 좋은 모델은 변경 계획을 검토하고 빠진 위험을 찾을 수 있다. 하지만 실제 변경은 사람이 이유, 변경 사항, 영향 범위, 실행·롤백 계획을 확인한 뒤에만 승인한다.</p>
<p>모델 선택은 성능표를 보고 1등을 고르는 일이 아니다. <strong>업무의 위험도와 검증 가능성에 맞춰 비용을 배분하는 설계</strong>다.</p>
<h2 id="사업은-호출량에서-무너진다">사업은 호출량에서 무너진다<a class="heading-anchor" href="#사업은-호출량에서-무너진다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>AI 기능 하나의 비용은 작아 보인다. 하지만 사용자가 매일, 매시간, 제품의 여러 화면에서 그 기능을 쓰기 시작하면 이야기가 달라진다.</p>
<p>처음에는 최고급 모델을 붙여도 된다. 제품이 가능한지, 사용자가 가치를 느끼는지, 어떤 결과를 원하는지 확인하는 시기이기 때문이다. 데모와 초기 실험에서는 성능이 비용보다 중요할 수 있다.</p>
<p>그러나 제품이 실제 사용 습관이 되려면 질문이 바뀐다.</p>
<ul><li>이 기능은 하루에 몇 번 호출되는가?</li><li>사용자가 늘어나도 단가를 감당할 수 있는가?</li><li>같은 품질을 더 낮은 비용으로 낼 수 있는 작업은 무엇인가?</li><li>꼭 모델을 호출해야 하는 단계와, 규칙이나 캐시로 처리할 수 있는 단계는 무엇인가?</li></ul>
<p>이 질문을 피하면 AI 제품은 사용자가 늘수록 좋은 소식이 아니라 비용 문제가 된다. 사용량이 증가할수록 적자가 커지는 기능은 좋은 데모일 수는 있어도 오래 운영할 사업이 되기 어렵다.</p>
<p>그래서 개인 프로젝트든 큰 서비스든, 중요한 것은 최고급 모델을 한 번 잘 활용하는 능력만이 아니다. 어떤 일을 최고급 모델에 맡기고, 어떤 일을 보급형 모델에 넘기고, 어떤 일은 애초에 모델 없이 처리할지를 구분하는 능력이 중요하다.</p>
<h2 id="보급형-모델은-타협재가-아니다">보급형 모델은 타협재가 아니다<a class="heading-anchor" href="#보급형-모델은-타협재가-아니다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>저렴한 모델을 쓰자는 말은 품질을 포기하자는 말이 아니다.</p>
<p>오히려 제품의 성격에 맞는 품질을 정의하자는 말이다. 모든 답변이 최고 수준의 추론을 보여 줄 필요는 없다. 사용자는 대개 정확한 형식의 문서, 빠르게 정리된 현황, 놓치지 않은 체크리스트, 이해하기 쉬운 초안을 원한다.</p>
<p>이런 일에서 중요한 것은 모델이 얼마나 인상적인가가 아니라, 얼마나 일관되게 쓸모 있는가다.</p>
<p>모델 업체도 마찬가지다. 가장 높은 벤치마크 점수의 모델만 제공해서는 충분하지 않다. 개발자가 작업의 위험도와 비용에 맞춰 선택할 수 있도록, 속도·가격·품질이 다른 모델의 층위를 제공해야 한다.</p>
<p>매일 써도 부담 없는 가격, 반복 업무에 충분한 신뢰도, 제품 안에 자연스럽게 넣을 수 있는 속도. 이런 보급형 모델이 있어야 AI는 특별한 기능이 아니라 일상적인 제품 경험이 된다.</p>
<h2 id="진짜-경쟁력은-모델을-고르는-데-있다">진짜 경쟁력은 모델을 고르는 데 있다<a class="heading-anchor" href="#진짜-경쟁력은-모델을-고르는-데-있다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>앞으로 AI를 잘 쓰는 사람은 가장 비싼 모델의 이름을 많이 아는 사람이 아닐 것이다.</p>
<p>자기 업무를 보고, 위험한 일과 안전한 일을 나누고, 복잡한 판단과 반복 작업을 분리하고, 사람이 확인해야 할 경계를 남겨 두는 사람일 것이다. 그리고 그 위에 가장 적절한 비용의 모델을 배치하는 사람일 것이다.</p>
<p>비싼 모델은 가능성을 보여 준다. 복잡한 설계를 돕고, 어려운 문제를 푼다. 그래서 데모를 만든다.</p>
<p>하지만 매일 쓰이는 제품, 계속 유지되는 운영, 사용자가 늘어도 살아남는 사업은 다른 곳에서 결정된다.</p>
<p>반복되는 일상을 감당할 수 있는 모델. 충분히 빠르고, 충분히 싸고, 충분히 믿을 수 있는 모델.</p>
<p>결국 AI의 진짜 경쟁력은 가장 비싼 모델 하나가 아니라, <strong>보급형 모델을 얼마나 잘 제공하고, 얼마나 잘 사용하는가</strong>에 있다.</p>]]></content:encoded>
    </item>
    <item>
      <title>AI가 개인 프로젝트의 계산법을 바꾸고 있다</title>
      <link>https://changelog.encer.me/posts/ai-changes-personal-project-economics</link>
      <guid isPermaLink="true">https://changelog.encer.me/posts/ai-changes-personal-project-economics</guid>
      <pubDate>Wed, 14 Oct 2026 00:00:00 GMT</pubDate>
      <description>AI가 구현과 운영의 비용 구조를 바꾸며 개인 프로젝트의 중심을 개발에서 가설 검증, 기획, 마케팅, 운영으로 옮기는 과정을 다룬다.</description>
      <category>AI</category>
      <category>개인 프로젝트</category>
      <category>인디 해킹</category>
      <category>Encer</category>
      <content:encoded><![CDATA[<h1 id="ai가-개인-프로젝트의-계산법을-바꾸고-있다">AI가 개인 프로젝트의 계산법을 바꾸고 있다<a class="heading-anchor" href="#ai가-개인-프로젝트의-계산법을-바꾸고-있다" aria-label="이 섹션으로 가는 링크">#</a></h1>
<p>개인 프로젝트를 한다는 것은 오랫동안 개발 중심의 일이었다.</p>
<p>아이디어가 있어도 먼저 계산해야 할 것이 많았다. 이걸 만들 수 있는가. 얼마나 걸릴까. 서버는 어떻게 운영할까. 기능 하나를 더 넣는 데 며칠이 걸릴까. 혼자 만들 수 있는 범위는 어디까지일까.</p>
<p>좋은 아이디어를 떠올리는 것보다, 그 아이디어를 구현 가능한 범위로 줄이는 데 더 많은 시간이 들었다. 개인 프로젝트는 대개 하고 싶은 일을 만드는 과정이 아니라, 만들 수 있는 일만 남기는 과정에 가까웠다.</p>
<p>AI를 사용한 뒤 가장 크게 달라진 것은 구현 시간이다. 기능을 처음부터 끝까지 손으로 쌓는 시간은 짧아졌고, 막힌 지점을 조사하고, 초안을 만들고, 다시 고치는 속도도 훨씬 빨라졌다.</p>
<p>그렇다고 개인 프로젝트가 쉬워진 것은 아니다. 다만 일이 옮겨 갔다.</p>
<p>예전에는 개발이 프로젝트의 대부분이었다면, 지금은 아이디어 검증, 시장 조사, 구현 후 기획 수정, 운영이 더 큰 비중을 차지한다. 만들 수 있느냐는 질문이 약해진 대신, <strong>이걸 왜 만들어야 하는가</strong>라는 질문이 더 중요해졌다.</p>
<h2 id="구현이-빨라지면-가설을-더-자주-바꿀-수-있다">구현이 빨라지면, 가설을 더 자주 바꿀 수 있다<a class="heading-anchor" href="#구현이-빨라지면-가설을-더-자주-바꿀-수-있다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>개인 프로젝트에서 가장 비싼 비용은 개발 시간이었다. 기능 하나를 만들어 보기 위해 며칠이나 몇 주를 써야 했다면, 아이디어를 검증하기 전에 이미 상당한 비용을 지불한 셈이다.</p>
<p>그래서 예전에는 출시 전에 오래 생각했다. 기능 목록을 다듬고, 기술 스택을 고르고, 화면을 설계하고, 최대한 틀리지 않으려고 했다. 하지만 긴 시간 끝에 만든 결과가 시장에서 필요 없는 것이라면, 잘 구현된 실패가 된다.</p>
<p>AI는 이 순서를 바꾼다.</p>
<p>아이디어를 완성된 계획으로 만든 뒤 구현하는 대신, 작은 형태로 구현하고 반응을 본 뒤 기획을 고친다. 만들어 보고, 시장을 조사하고, 다시 정의한다. 구현이 빨라졌다는 것은 단지 출시를 빨리 한다는 뜻이 아니다. <strong>틀린 가설을 더 빨리 버리고, 맞는 가설에 더 오래 투자할 수 있다</strong>는 뜻이다.</p>
<p>그래서 개인 프로젝트에서 개발자의 역할도 바뀐다. 이제 중요한 것은 코드를 가장 빨리 치는 능력만이 아니다. 어떤 가설을 먼저 검증할지, 무엇을 만들지 말아야 할지, 반응을 보고 무엇을 바꿀지를 판단하는 능력이다.</p>
<h2 id="ai는-새-시장을-만들기보다-기존-시장의-비용을-다시-계산하게-한다">AI는 새 시장을 만들기보다, 기존 시장의 비용을 다시 계산하게 한다<a class="heading-anchor" href="#ai는-새-시장을-만들기보다-기존-시장의-비용을-다시-계산하게-한다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>AI를 활용한 개인 프로젝트를 생각할 때, 우리는 종종 완전히 새로운 서비스를 떠올린다. 하지만 더 현실적인 기회는 이미 존재하는 시장에서 나올 수 있다.</p>
<p>이미 수요는 있지만 사업성이 없었던 서비스들이다.</p>
<p>문제는 분명하다. 사용자는 존재한다. 비슷한 서비스도 있다. 그러나 사람이 계속 붙어야 하는 운영, 고객 응대, 콘텐츠 정리, 데이터 가공, 반복적인 보고와 관리 때문에 비용이 너무 커진다. 작은 팀이나 개인이 운영하기에는 수익이 남지 않는 구조가 된다.</p>
<p>AI는 그 문제를 바꿀 수 있다. 모든 운영을 자동화한다는 뜻이 아니다. 반복되는 일을 보조하고, 정보를 정리하고, 초안을 만들고, 놓친 부분을 찾아내는 방식으로 운영 비용과 시간을 낮춘다.</p>
<p>그렇게 되면 과거에는 큰 조직의 비용 구조에서만 가능했던 서비스가, 개인이나 작은 팀에게도 다시 시장성이 생길 수 있다.</p>
<p>개인 프로젝트의 기회는 “AI로 무엇이든 만들 수 있다”는 데 있지 않다. <strong>이전에는 비용 때문에 성립하지 않던 일을, 지금은 성립하게 만들 수 있는가</strong>에 있다.</p>
<h2 id="그래서-지금은-개발보다-시장을-더-오래-봐야-한다">그래서 지금은 개발보다 시장을 더 오래 봐야 한다<a class="heading-anchor" href="#그래서-지금은-개발보다-시장을-더-오래-봐야-한다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>구현이 빨라질수록 개발자는 개발 화면보다 시장을 더 오래 봐야 한다.</p>
<p>이미 비슷한 제품이 있는지 찾는 것만으로는 부족하다. 왜 그 서비스는 성장하지 못했는지, 사용자는 어느 지점에서 이탈했는지, 운영자는 어떤 비용을 감당해야 했는지, 가격을 올리지 못한 이유가 무엇인지 살펴봐야 한다.</p>
<p>그 안에서 AI가 줄일 수 있는 비용을 찾을 수 있다. 또한 AI가 줄일 수 없는 비용도 함께 봐야 한다. 사용자 신뢰, 제품의 취향, 유통, 고객 관계, 브랜드는 코드가 빨리 나온다고 자동으로 생기지 않는다.</p>
<p>AI는 개인 개발자에게 더 많은 제품을 만들 수 있게 해 준다. 하지만 더 많은 제품을 만드는 것 자체가 목표가 되면 안 된다. 제품이 빨리 만들어질수록, 무엇을 계속 운영할 가치가 있는지 더 냉정하게 판단해야 한다.</p>
<h2 id="encer를-만드는-이유">Encer를 만드는 이유<a class="heading-anchor" href="#encer를-만드는-이유" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>나는 지금 <strong>Encer</strong>를 만들고 있다. Encer는 퍼스널브랜딩을 위한 페이지다.</p>
<p>개인을 알리는 페이지를 만든다는 것은 겉으로 보기에는 단순한 일이다. 소개를 적고, 링크를 모으고, 콘텐츠와 연락 수단을 연결하면 된다. 하지만 실제로는 한 사람이 자신을 어떻게 설명하고, 어떤 작업을 보여 주고, 어떤 사람과 연결되고 싶은지를 계속 바꾸고 다듬는 과정에 가깝다.</p>
<p>그래서 Encer는 페이지 하나를 만드는 데서 끝나면 안 된다고 생각한다. 개인이 자신의 정체성과 작업을 더 잘 드러내고, 반응을 보고, 다시 다듬을 수 있는 공간이어야 한다.</p>
<p>AI가 Encer를 가능하게 한다는 말은, AI가 페이지를 대신 만들어 준다는 뜻만은 아니다. 개발에 걸리는 시간을 줄여서 더 자주 실험하게 하고, 운영의 반복 비용을 낮춰서 작은 팀도 제품을 계속 개선할 수 있게 한다는 뜻에 가깝다.</p>
<p>기획을 수정하고, 사용자 흐름을 다시 만들고, 시장의 반응에 맞춰 제품을 바꾸는 일. 이전에는 구현 비용 때문에 미뤘을 선택을 지금은 더 자주 해볼 수 있다.</p>
<p>Encer는 그런 변화 속에서 만드는 개인 프로젝트다. 퍼스널브랜딩이라는 오래된 필요를, AI로 달라진 개발과 운영의 조건 위에서 다시 풀어 보는 실험이기도 하다.</p>
<h2 id="개인-프로젝트의-중심은-이제-출시-이후에-있다">개인 프로젝트의 중심은 이제 출시 이후에 있다<a class="heading-anchor" href="#개인-프로젝트의-중심은-이제-출시-이후에-있다" aria-label="이 섹션으로 가는 링크">#</a></h2>
<p>AI를 쓰면 개인 프로젝트를 더 많이 시작할 수 있다. 하지만 더 중요한 변화는 더 많이 <strong>끝까지 운영해 볼 수 있다</strong>는 데 있다.</p>
<p>출시는 끝이 아니다. 출시 후에야 시장 조사가 시작되고, 기획이 수정되고, 사용자의 반응에 맞춰 제품의 방향이 정해진다.</p>
<p>개발 시간이 짧아진 시대에 개인 프로젝트의 경쟁력은 얼마나 빨리 기능을 만드는지에만 있지 않다. 사용자의 문제를 얼마나 정확히 이해하는지, 운영 비용을 얼마나 현실적으로 설계하는지, 그리고 배운 것을 얼마나 빠르게 다음 기획에 반영하는지에 있다.</p>
<p>AI는 개인 개발자의 손을 빠르게 만든다. 하지만 그 손이 어디로 가야 하는지는 여전히 사람이 결정해야 한다.</p>
<p>그래서 지금 개인 프로젝트를 한다는 것은, 혼자 제품을 만드는 일이 아니라 작은 회사를 설계하는 일에 더 가까워지고 있다.</p>
<hr />
<h2 id="연결-노트">연결 노트<a class="heading-anchor" href="#연결-노트" aria-label="이 섹션으로 가는 링크">#</a></h2>
<ul><li>[[AI 시대에 개발자는 코더가 아니라 설계자가 된다 - 초안 v2]]</li><li>[[AI 코딩 에이전트와 개발자 역할 - 리서치 인덱스]]</li></ul>]]></content:encoded>
    </item>
  </channel>
</rss>
