<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>sy_develop_note</title>
    <link>https://sy-develop-note.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 10 Jul 2026 01:24:35 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>누구세연</managingEditor>
    <image>
      <title>sy_develop_note</title>
      <url>https://tistory1.daumcdn.net/tistory/6670028/attach/d12b04592342451891305fb4fb892225</url>
      <link>https://sy-develop-note.tistory.com</link>
    </image>
    <item>
      <title>REST vs gRPC, MSA에서 어떻게 고를까?</title>
      <link>https://sy-develop-note.tistory.com/176</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 MSA 환경을 운영하다 보니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일관성 있는 데이터 동기화와 안정적인 운영을 위해 서비스 간 통신 방식을 정확히 정리하고 선택해야겠다는 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 REST와 gRPC를 비교해 언제 어떤 걸 쓰면 좋을지, 기준을 잡아보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;REST&amp;nbsp;vs&amp;nbsp;gRPC&amp;nbsp;한눈에&amp;nbsp;비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST와 gRPC는 모두 서비스 간 통신을 위한 기술이지만, &lt;b&gt;출발점이 다르고, 잘 맞는 사용 시나리오도 다릅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;320&quot; data-start=&quot;216&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;320&quot; data-start=&quot;216&quot; data-ke-size=&quot;size16&quot;&gt;REST는 HTTP를 기반으로 한 &lt;b&gt;리소스 중심의 통신 방식&lt;/b&gt;이고,&lt;/p&gt;
&lt;p data-end=&quot;320&quot; data-start=&quot;216&quot; data-ke-size=&quot;size16&quot;&gt;gRPC는 원격 메서드를 호출하는 &lt;b&gt;RPC(Remote Procedure Call) 모델&lt;/b&gt;에 가깝습니다.&lt;/p&gt;
&lt;p data-end=&quot;357&quot; data-start=&quot;322&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;357&quot; data-start=&quot;322&quot; data-ke-size=&quot;size16&quot;&gt;이 차이가 설계 방식, 성능, 운영 경험 전반에 영향을 줍니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;REST&lt;/td&gt;
&lt;td&gt;gRPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;통신 모델&lt;/td&gt;
&lt;td&gt;리소스 중심 (URL + HTTP Method)&lt;/td&gt;
&lt;td&gt;메서드 호출 중심 (RPC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전송 프로토콜&lt;/td&gt;
&lt;td&gt;HTTP/1.1 (주로)&lt;/td&gt;
&lt;td&gt;HTTP/2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 포맷&lt;/td&gt;
&lt;td&gt;JSON (사람 친화적)&lt;/td&gt;
&lt;td&gt;Protobuf (바이너리, 경량)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;요청/응답&lt;/td&gt;
&lt;td&gt;요청 1 &amp;harr; 응답 1&lt;/td&gt;
&lt;td&gt;Unary + Streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스트리밍&lt;/td&gt;
&lt;td&gt;기본 미지원 (SSE/WebSocket 필요)&lt;/td&gt;
&lt;td&gt;서버/클라이언트/양방향 기본 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스키마 관리&lt;/td&gt;
&lt;td&gt;느슨함 (OpenAPI 등 별도 문서)&lt;/td&gt;
&lt;td&gt;.proto로 강한 계약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코드 생성&lt;/td&gt;
&lt;td&gt;수동 구현&lt;/td&gt;
&lt;td&gt;클라이언트/서버 Stub 자동 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;브라우저 접근&lt;/td&gt;
&lt;td&gt;매우 쉬움&lt;/td&gt;
&lt;td&gt;직접 호출 어려움 (프록시 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;디버깅&lt;/td&gt;
&lt;td&gt;curl, 브라우저로 즉시 가능&lt;/td&gt;
&lt;td&gt;grpcurl 등 전용 도구 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주 사용처&lt;/td&gt;
&lt;td&gt;외부 API, BFF, CRUD&lt;/td&gt;
&lt;td&gt;내부 서비스 통신, 고성능 RPC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-end=&quot;912&quot; data-start=&quot;899&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;912&quot; data-start=&quot;899&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;한 줄로 정리하면&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1067&quot; data-start=&quot;914&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;992&quot; data-start=&quot;914&quot;&gt;&lt;b&gt;REST&lt;/b&gt;는 사람이 읽기 쉽고, 브라우저와 잘 맞으며, 외부에 공개하기 좋은 &lt;b&gt;범용 API&lt;/b&gt;에 적합합니다.&lt;/li&gt;
&lt;li data-end=&quot;1067&quot; data-start=&quot;994&quot;&gt;&lt;b&gt;gRPC&lt;/b&gt;는 서비스 간 계약이 명확하고, 호출이 많거나 지연에 민감한 &lt;b&gt;내부 통신&lt;/b&gt;에 적합합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1084&quot; data-start=&quot;1069&quot; data-ke-size=&quot;size16&quot;&gt;그래서 MSA 환경에서는 &lt;b&gt;&amp;ldquo;외부는 REST, 내부는 gRPC&amp;rdquo; &lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;같은 혼합 패턴이 자주 사용됩니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;REST와 gRPC의 근본적인 차이: RPC 관점에서 보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST는 리소스를 중심으로 한 통신 방식입니다.&lt;br /&gt;URL과 HTTP 메서드로 &amp;ldquo;무엇을 한다&amp;rdquo;를 표현하고, 응답은 상태 코드와 JSON으로 돌려받습니다.&lt;br /&gt;사람이 읽기 쉽고, 브라우저와 궁합이 좋다는 점이 가장 큰 장점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1561&quot; data-start=&quot;1395&quot; data-ke-size=&quot;size16&quot;&gt;반면 gRPC는 &lt;b&gt;&amp;ldquo;원격 메서드를 호출한다&amp;rdquo;는 RPC 모델&lt;/b&gt;에 가깝습니다.&lt;br /&gt;URL보다 메서드 시그니처가 중요하고, 요청과 응답은 .proto로 정의된 메시지를 그대로 주고받습니다.&lt;br /&gt;그래서 gRPC를 쓰다 보면 API를 만든다기보다 &lt;b&gt;인터페이스를 정의한다는 느낌&lt;/b&gt;에 더 가깝습니다.&lt;/p&gt;
&lt;p data-end=&quot;1561&quot; data-start=&quot;1395&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1561&quot; data-start=&quot;1395&quot; data-ke-size=&quot;size16&quot;&gt;아래&amp;nbsp;그림은&amp;nbsp;gRPC가&amp;nbsp;내부적으로&amp;nbsp;어떻게&amp;nbsp;RPC&amp;nbsp;호출을&amp;nbsp;처리하는지&amp;nbsp;보여줍니다.&lt;/p&gt;
&lt;p data-end=&quot;1561&quot; data-start=&quot;1395&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트는&amp;nbsp;메서드를&amp;nbsp;호출하지만,&amp;nbsp;실제로는&amp;nbsp;Stub과&amp;nbsp;Runtime이&amp;nbsp;직렬화와&amp;nbsp;네트워크&amp;nbsp;전송을&amp;nbsp;담당합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/om142/dJMcaiaS7Cq/0d0GoRGHdKdZLEukjBKRyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/om142/dJMcaiaS7Cq/0d0GoRGHdKdZLEukjBKRyk/img.png&quot; data-origin-width=&quot;2374&quot; data-origin-height=&quot;1508&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2025-12-28 오후 6.42.36.png&quot; style=&quot;width: 47.6201%; margin-right: 10px;&quot; data-widthpercent=&quot;48.18&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/om142/dJMcaiaS7Cq/0d0GoRGHdKdZLEukjBKRyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fom142%2FdJMcaiaS7Cq%2F0d0GoRGHdKdZLEukjBKRyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2374&quot; height=&quot;1508&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mBsc7/dJMb99ZjWZJ/9wCcw0cg5oeAyZi3B1Sko0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mBsc7/dJMb99ZjWZJ/9wCcw0cg5oeAyZi3B1Sko0/img.png&quot; data-origin-width=&quot;2384&quot; data-origin-height=&quot;1408&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2025-12-28 오후 6.43.05.png&quot; style=&quot;width: 51.2171%;&quot; data-widthpercent=&quot;51.82&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mBsc7/dJMb99ZjWZJ/9wCcw0cg5oeAyZi3B1Sko0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmBsc7%2FdJMb99ZjWZJ%2F9wCcw0cg5oeAyZi3B1Sko0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2384&quot; height=&quot;1408&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;API 설계 방식의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST와 gRPC의 가장 큰 차이는 &lt;b&gt;API를 바라보는 관점&lt;/b&gt;에서 드러납니다.&lt;br /&gt;REST가 리소스를 중심으로 설계된다면, gRPC는 &lt;b&gt;서비스가 제공하는 동작(행위)&lt;/b&gt; 을 중심으로 설계됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;REST: 리소스를 기준으로 설계하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST에서는 URL이 곧 리소스를 의미합니다.&lt;br /&gt;HTTP 메서드를 조합해 &amp;ldquo;이 리소스에 대해 무엇을 할 것인지&amp;rdquo;를 표현합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1766915631073&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET    /users/{userId}
POST   /users
DELETE /users/{userId}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;558&quot; data-start=&quot;502&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;521&quot; data-start=&quot;502&quot;&gt;&lt;b&gt;URL = 리소스의 위치&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;558&quot; data-start=&quot;522&quot;&gt;&lt;b&gt;HTTP 메서드 = 행위&lt;/b&gt;&lt;br /&gt;라는 역할 분담이 명확합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;661&quot; data-start=&quot;560&quot; data-ke-size=&quot;size16&quot;&gt;그래서 CRUD 위주의 API나 외부 공개 API에서는 REST 방식이 직관적이고 문서화도 쉽습니다.&lt;br /&gt;브라우저나 curl로 바로 호출해 볼 수 있다는 점 역시 큰 장점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;gRPC: 동작(메서드)을 기준으로 설계하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC에서는 URL보다 &lt;b&gt;메서드 시그니처&lt;/b&gt;가 중심이 됩니다.&lt;br /&gt;어떤 리소스를 다루는 지보다, &lt;b&gt;&amp;ldquo;이 서비스가 어떤 기능을 제공하는가&amp;rdquo;&lt;/b&gt;가 먼저 정의됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1766915736432&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;service VacationService {
  rpc GetUserVacations(GetUserVacationsRequest)
      returns (GetUserVacationsResponse);

  rpc CreateVacation(CreateVacationRequest)
      returns (CreateVacationResponse);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1090&quot; data-start=&quot;1022&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1052&quot; data-start=&quot;1022&quot;&gt;API가 아니라 &lt;b&gt;인터페이스를 정의한다는 느낌&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1090&quot; data-start=&quot;1053&quot;&gt;요청/응답 구조가 .proto로 강하게 고정된다는 점입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1163&quot; data-start=&quot;1092&quot; data-ke-size=&quot;size16&quot;&gt;REST처럼 &amp;ldquo;이 URL이 무슨 의미지?&amp;rdquo;를 고민하기보다, 메서드 이름과 시그니처만 봐도 &lt;b&gt;서비스의 책임이 드러납니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설계할 때 고민 포인트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST와 gRPC를 각각 설계해보니, 자연스럽게 고민의 출발점이 달라졌습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1402&quot; data-start=&quot;1246&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1315&quot; data-start=&quot;1246&quot;&gt;REST를 설계할 때는&lt;br /&gt;&amp;rarr; &amp;ldquo;이 리소스를 어떻게 쪼갤까?&amp;rdquo;&lt;br /&gt;&amp;rarr; &amp;ldquo;URL을 어떻게 표현하면 REST스럽지?&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;1402&quot; data-start=&quot;1317&quot;&gt;gRPC를 설계할 때는&lt;br /&gt;&amp;rarr; &amp;ldquo;이 서비스가 외부에 제공해야 할 기능은 무엇일까?&amp;rdquo;&lt;br /&gt;&amp;rarr; &amp;ldquo;이 메서드의 입력과 출력은 어디까지가 책임일까?&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1463&quot; data-start=&quot;1404&quot; data-ke-size=&quot;size16&quot;&gt;그래서 gRPC에서는 API 설계보다는 &lt;b&gt;서비스 인터페이스 설계&lt;/b&gt;에 더 가까운 사고가 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;같은 기능을 REST와 gRPC로 표현하면&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 &amp;ldquo;사용자의 휴가 목록 조회&amp;rdquo; 기능이라도 표현 방식은 확연히 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;REST&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766915930405&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET /users/{userId}/vacations&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;gRPC&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766915957180&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rpc GetUserVacations(GetUserVacationsRequest)
    returns (GetUserVacationsResponse);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST는 &lt;b&gt;리소스 구조&lt;/b&gt;가 먼저 보이고,&lt;br /&gt;gRPC는 &lt;b&gt;행위와 계약&lt;/b&gt;이 먼저 보입니다.&lt;/p&gt;
&lt;p data-end=&quot;1835&quot; data-start=&quot;1756&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1835&quot; data-start=&quot;1756&quot; data-ke-size=&quot;size16&quot;&gt;이 차이 때문에, 외부 공개 API나 BFF에서는 REST가 자연스럽고 내부 서비스 간 통신에서는 gRPC가 더 명확하게 느껴졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1857&quot; data-start=&quot;1842&quot; data-ke-size=&quot;size20&quot;&gt;설계 관점에서의 정리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2002&quot; data-start=&quot;1859&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1919&quot; data-start=&quot;1859&quot;&gt;REST는&lt;br /&gt;&amp;rarr; 리소스 모델링이 중요하고&lt;br /&gt;&amp;rarr; HTTP 스펙과 잘 어울리는 설계가 핵심입니다.&lt;/li&gt;
&lt;li data-end=&quot;2002&quot; data-start=&quot;1921&quot;&gt;gRPC는&lt;br /&gt;&amp;rarr; 서비스의 책임과 기능 경계를 명확히 정의하는 것이 중요하고&lt;br /&gt;&amp;rarr; 강한 스키마 계약을 기반으로 한 통신에 적합합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2098&quot; data-start=&quot;2004&quot; data-ke-size=&quot;size16&quot;&gt;그래서 MSA 환경에서는 &lt;b&gt;&amp;ldquo;어떤 기능을 어디에 노출할 것인가&amp;rdquo;&lt;/b&gt;를 먼저 정한 뒤, &lt;br /&gt;그 성격에 맞춰 REST와 gRPC를 선택하는 편이 가장 안정적인 것이라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능과 스트리밍에서의 차이&lt;/h3&gt;
&lt;h4 data-end=&quot;251&quot; data-start=&quot;220&quot; data-ke-size=&quot;size20&quot;&gt;REST와 gRPC의 성능 차이는 어디서 나올까?&lt;/h4&gt;
&lt;p data-end=&quot;331&quot; data-start=&quot;253&quot; data-ke-size=&quot;size16&quot;&gt;REST와 gRPC의 성능 차이는 단순히 구현체의 문제가 아니라, &lt;b&gt;기반이 되는 프로토콜과 데이터 전송 방식의 차이&lt;/b&gt;에서 비롯됩니다.&lt;/p&gt;
&lt;p data-end=&quot;428&quot; data-start=&quot;333&quot; data-ke-size=&quot;size16&quot;&gt;REST는 주로 HTTP/1.1과 JSON을 사용합니다.&lt;br /&gt;요청과 응답이 텍스트 기반이고, 호출이 늘어날수록 헤더 크기와 JSON 파싱 비용이 반복적으로 발생합니다.&lt;/p&gt;
&lt;p data-end=&quot;507&quot; data-start=&quot;430&quot; data-ke-size=&quot;size16&quot;&gt;반면 gRPC는 HTTP/2를 기반으로 동작하며, Protobuf라는 바이너리 포맷을 사용해 전송 데이터가 작고 처리 비용이 낮습니다.&lt;/p&gt;
&lt;p data-end=&quot;507&quot; data-start=&quot;430&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;507&quot; data-start=&quot;430&quot; data-ke-size=&quot;size16&quot;&gt;HTTP/1.1&lt;/p&gt;
&lt;pre id=&quot;code_1766916824019&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Client ---[Req1: HTML]---&amp;gt; Server
Client &amp;lt;---[Resp1: HTML]-- Server
Client ---[Req2: CSS]---&amp;gt; Server
Client &amp;lt;---[Resp2: CSS]-- Server
Client ---[Req3: JS]----&amp;gt; Server
Client &amp;lt;---[Resp3: JS]--- Server&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/2&lt;/p&gt;
&lt;pre id=&quot;code_1766916866357&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Client ---[Stream 1: Req HTML]---&amp;gt; Server ---[Stream 2: Req CSS]---&amp;gt; [Stream 3: Req JS]---&amp;gt;
         &amp;lt;---[Stream 3: Resp JS]--- &amp;lt;---[Stream 2: Resp CSS]--- &amp;lt;---[Stream 1: Resp HTML]---&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;호출이 많아질수록 체감되는 차이&lt;/h4&gt;
&lt;p data-end=&quot;813&quot; data-start=&quot;694&quot; data-ke-size=&quot;size16&quot;&gt;단건 요청에서는 REST와 gRPC의 차이가 크게 느껴지지 않을 수 있습니다.&lt;br /&gt;하지만 MSA 환경처럼 &lt;b&gt;서비스 간 호출이 빈번해질수록&lt;/b&gt;&lt;br /&gt;작은 지연과 오버헤드가 쌓여 전체 응답 시간에 영향을 주게 됩니다.&lt;/p&gt;
&lt;p data-end=&quot;825&quot; data-start=&quot;815&quot; data-ke-size=&quot;size16&quot;&gt;이런 상황에서는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;871&quot; data-start=&quot;826&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;842&quot; data-start=&quot;826&quot;&gt;요청&amp;middot;응답 크기가 작고&lt;/li&gt;
&lt;li data-end=&quot;871&quot; data-start=&quot;843&quot;&gt;연결 재사용과 헤더 압축이 기본으로 제공되는&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;896&quot; data-start=&quot;873&quot; data-ke-size=&quot;size16&quot;&gt;gRPC 쪽이 더 안정적으로 느껴졌습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HU8gs/dJMcaiPryQq/yVLRkIWzIqSJKTsj2ud010/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HU8gs/dJMcaiPryQq/yVLRkIWzIqSJKTsj2ud010/img.png&quot; data-origin-width=&quot;2358&quot; data-origin-height=&quot;1526&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2025-12-28 오후 7.19.13.png&quot; style=&quot;width: 44.0505%; margin-right: 10px;&quot; data-widthpercent=&quot;44.57&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HU8gs/dJMcaiPryQq/yVLRkIWzIqSJKTsj2ud010/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHU8gs%2FdJMcaiPryQq%2FyVLRkIWzIqSJKTsj2ud010%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2358&quot; height=&quot;1526&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U8Ulg/dJMcadAA6b9/5NI6bmE8bDysVKb1iBP5CK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U8Ulg/dJMcadAA6b9/5NI6bmE8bDysVKb1iBP5CK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;2360&quot; data-origin-height=&quot;1228&quot; data-filename=&quot;스크린샷 2025-12-28 오후 7.19.30.png&quot; style=&quot;width: 54.7867%;&quot; data-widthpercent=&quot;55.43&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U8Ulg/dJMcadAA6b9/5NI6bmE8bDysVKb1iBP5CK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU8Ulg%2FdJMcadAA6b9%2F5NI6bmE8bDysVKb1iBP5CK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2360&quot; height=&quot;1228&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-end=&quot;960&quot; data-start=&quot;945&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;960&quot; data-start=&quot;945&quot; data-ke-size=&quot;size20&quot;&gt;스트리밍 방식의 차이&lt;/h4&gt;
&lt;p data-end=&quot;1008&quot; data-start=&quot;962&quot; data-ke-size=&quot;size16&quot;&gt;스트리밍 지원 여부는 REST와 gRPC를 가르는 또 하나의 중요한 기준이었습니다.&lt;/p&gt;
&lt;p data-end=&quot;1103&quot; data-start=&quot;1010&quot; data-ke-size=&quot;size16&quot;&gt;REST는 기본적으로 요청 &amp;rarr; 응답 구조이기 때문에, 연속적인 데이터 전송이 필요할 경우 SSE나 WebSocket 같은 별도의 방식을 추가로 고려해야 합니다.&lt;/p&gt;
&lt;p data-end=&quot;1171&quot; data-start=&quot;1105&quot; data-ke-size=&quot;size16&quot;&gt;반면 gRPC는 서버 스트리밍, 클라이언트 스트리밍, 그리고 양방향 스트리밍을 &lt;b&gt;통신 모델 자체로 제공&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-end=&quot;1171&quot; data-start=&quot;1105&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1171&quot; data-start=&quot;1105&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-end=&quot;1171&quot; data-start=&quot;1105&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1171&quot; data-start=&quot;1105&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;449&quot; data-start=&quot;428&quot; data-ke-size=&quot;size23&quot;&gt;운영 / 디버깅 / 현실적인 제약&lt;/h3&gt;
&lt;p data-end=&quot;503&quot; data-start=&quot;451&quot; data-ke-size=&quot;size16&quot;&gt;REST와 gRPC의 차이는 성능보다, 운영하면서&lt;b&gt; 겪는 경험에서 더 분명해졌습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;503&quot; data-start=&quot;451&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;609&quot; data-start=&quot;505&quot; data-ke-size=&quot;size16&quot;&gt;REST는 문제가 생겼을 때 상황을 재현하기가 쉽습니다.&lt;br /&gt;curl이나 브라우저로 바로 호출해 볼 수 있고, JSON 기반 응답이라 로그만 봐도 흐름을 빠르게 파악할 수 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;609&quot; data-start=&quot;505&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;696&quot; data-start=&quot;611&quot; data-ke-size=&quot;size16&quot;&gt;반면 gRPC는 바이너리 통신이라 전용 도구(grpcurl 등)가 없으면 디버깅이 쉽지 않았습니다.&lt;br /&gt;초기에는 운영 난이도가 더 높게 느껴졌습니다.&lt;/p&gt;
&lt;p data-end=&quot;863&quot; data-start=&quot;698&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;863&quot; data-start=&quot;698&quot; data-ke-size=&quot;size16&quot;&gt;에러 처리 방식도 달랐습니다.&lt;/p&gt;
&lt;p data-end=&quot;863&quot; data-start=&quot;698&quot; data-ke-size=&quot;size16&quot;&gt;REST는 HTTP 상태 코드와 메시지를 비교적 자유롭게 표현할 수 있었지만, gRPC는 Status Code 체계를 잘 정하지 않으면 모든 에러가 비슷하게 보이기 쉬웠습니다.&lt;br /&gt;대신 인터셉터를 통해 로깅이나 트레이싱을 공통으로 적용하기는 수월했습니다.&lt;/p&gt;
&lt;p data-end=&quot;970&quot; data-start=&quot;865&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;970&quot; data-start=&quot;865&quot; data-ke-size=&quot;size16&quot;&gt;또 하나는 인프라 제약입니다.&lt;br /&gt;gRPC는 HTTP/2 지원이 필요하고, 브라우저에서 직접 호출하기 어렵기 때문에 외부 공개 API보다는 내부 서비스 간 통신에 더 잘 맞았습니다.&lt;/p&gt;
&lt;p data-end=&quot;1025&quot; data-start=&quot;972&quot; data-ke-size=&quot;size16&quot;&gt;이런 점들 때문에, REST와 gRPC는 우열보다는 &lt;b&gt;역할이 다른 기술&lt;/b&gt;로 느껴졌습니다.&lt;/p&gt;
&lt;p data-end=&quot;1025&quot; data-start=&quot;972&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1025&quot; data-start=&quot;972&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-end=&quot;1025&quot; data-start=&quot;972&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1171&quot; data-start=&quot;1105&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MSA에서의&amp;nbsp;혼합&amp;nbsp;운용&amp;nbsp;패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST와 gRPC를 모두 써보니, MSA 환경에서는 둘 중 하나를 고르기보다 &lt;b&gt;역할에 따라 나눠 쓰는 방식&lt;/b&gt;이 가장 자연스럽게 느껴졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;382&quot; data-start=&quot;218&quot; data-ke-size=&quot;size16&quot;&gt;가장 흔했던 패턴은 &lt;b&gt;외부는 REST, 내부는 gRPC&lt;/b&gt;였습니다.&lt;br /&gt;브라우저나 모바일, 외부 파트너가 직접 호출하는 구간에서는 접근성과 디버깅이 쉬운 REST가 훨씬 편했습니다.&lt;br /&gt;반대로 내부 서비스 간 호출에서는 지연과 호출 빈도가 중요해지면서 gRPC의 장점이 분명해졌습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tgjsI/dJMcacPgPFw/vQSxQ2i08jcyculFwbrTkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tgjsI/dJMcacPgPFw/vQSxQ2i08jcyculFwbrTkK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;2392&quot; data-origin-height=&quot;1142&quot; data-filename=&quot;스크린샷 2025-12-28 오후 7.25.05.png&quot; style=&quot;width: 38.638%; margin-right: 10px;&quot; data-widthpercent=&quot;39.56&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tgjsI/dJMcacPgPFw/vQSxQ2i08jcyculFwbrTkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtgjsI%2FdJMcacPgPFw%2FvQSxQ2i08jcyculFwbrTkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2392&quot; height=&quot;1142&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oRwbe/dJMcac2LUCa/UwnCMLYcJNT8iarTdGjai0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oRwbe/dJMcac2LUCa/UwnCMLYcJNT8iarTdGjai0/img.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1334&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2025-12-28 오후 7.25.21.png&quot; style=&quot;width: 32.9939%; margin-right: 10px;&quot; data-widthpercent=&quot;33.78&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oRwbe/dJMcac2LUCa/UwnCMLYcJNT8iarTdGjai0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoRwbe%2FdJMcac2LUCa%2FUwnCMLYcJNT8iarTdGjai0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2386&quot; height=&quot;1334&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coFfGD/dJMcabCOZdK/e1tyLJy9EW7ayAa4Nw8VA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coFfGD/dJMcabCOZdK/e1tyLJy9EW7ayAa4Nw8VA0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;1326&quot; data-filename=&quot;스크린샷 2025-12-28 오후 7.26.01.png&quot; style=&quot;width: 26.0425%;&quot; data-widthpercent=&quot;26.66&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coFfGD/dJMcabCOZdK/e1tyLJy9EW7ayAa4Nw8VA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoFfGD%2FdJMcabCOZdK%2Fe1tyLJy9EW7ayAa4Nw8VA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1872&quot; height=&quot;1326&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서는 보통 API Gateway나 프록시가 경계 역할을 합니다.&lt;br /&gt;외부 요청은 REST로 받고, 내부로 들어갈 때 gRPC로 변환해 전달하는 방식입니다.&lt;br /&gt;덕분에 클라이언트는 기존 REST API를 그대로 사용하면서도, 내부 통신은 성능과 계약 측면에서 더 안정적으로 가져갈 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;772&quot; data-start=&quot;601&quot; data-ke-size=&quot;size16&quot;&gt;또 다른 패턴은 &lt;b&gt;BFF + 내부 gRPC&lt;/b&gt; 구조였습니다.&lt;br /&gt;BFF는 REST나 GraphQL로 클라이언트 요구에 맞게 응답을 구성하고, 실제 데이터 조회나 처리는 내부 gRPC 서비스에 위임합니다.&lt;br /&gt;이렇게 나누니 클라이언트 변화와 내부 서비스 변경을 서로 독립적으로 가져갈 수 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;772&quot; data-start=&quot;601&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;899&quot; data-start=&quot;774&quot; data-ke-size=&quot;size16&quot;&gt;실제로는 기존 REST API를 한 번에 바꾸기보다, &lt;b&gt;병목이 되는 구간부터 gRPC로 점진 전환&lt;/b&gt;하는 경우가 많았습니다.&lt;br /&gt;외부 계약을 깨지 않으면서 내부 성능만 개선할 수 있어 리스크도 상대적으로 적었습니다.&lt;/p&gt;
&lt;p data-end=&quot;899&quot; data-start=&quot;774&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1037&quot; data-start=&quot;901&quot; data-ke-size=&quot;size16&quot;&gt;이런 경험을 거치면서, REST와 gRPC는 경쟁 관계라기보다 &lt;b&gt;MSA 안에서 서로 다른 역할을 맡는 도구&lt;/b&gt;에 가깝다는 생각이 들었습니다.&lt;br /&gt;경계에 가까울수록 REST가, 내부로 깊어질수록 gRPC가 자연스럽게 자리 잡았습니다.&lt;/p&gt;
&lt;p data-end=&quot;1037&quot; data-start=&quot;901&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1037&quot; data-start=&quot;901&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&amp;nbsp;및&amp;nbsp;선택&amp;nbsp;기준&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST와 gRPC를 비교해 보면, 결국 둘 중 하나가 더 좋다기보다는 &lt;b&gt;어떤 위치에서, 어떤 역할을 맡기느냐의 문제&lt;/b&gt;에 가까웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;352&quot; data-start=&quot;217&quot; data-ke-size=&quot;size16&quot;&gt;브라우저나 모바일, 외부 파트너처럼 사람과 맞닿아 있는 지점에서는 읽기 쉽고 디버깅이 쉬운 REST가 훨씬 편했습니다.&lt;br /&gt;URL과 HTTP 메서드만으로도 의도가 드러나고, 문제가 생겼을 때 빠르게 확인할 수 있다는 점이 컸습니다.&lt;/p&gt;
&lt;p data-end=&quot;498&quot; data-start=&quot;354&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;498&quot; data-start=&quot;354&quot; data-ke-size=&quot;size16&quot;&gt;반대로 서비스 안쪽으로 들어갈수록 호출 빈도, 지연, 계약 관리 같은 요소들이 중요해졌고, 이 지점에서는 gRPC의 장점이 분명하게 느껴졌습니다.&lt;/p&gt;
&lt;p data-end=&quot;498&quot; data-start=&quot;354&quot; data-ke-size=&quot;size16&quot;&gt;강한 스키마와 코드 생성, 스트리밍 지원 덕분에 내부 통신을 더 안정적으로 가져갈 수 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;498&quot; data-start=&quot;354&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;551&quot; data-start=&quot;500&quot; data-ke-size=&quot;size16&quot;&gt;그래서 REST와 gRPC를 고를 때는 기술 자체보다 먼저 이런 질문을 던지게 됐습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;638&quot; data-start=&quot;553&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;574&quot; data-start=&quot;553&quot;&gt;이 API를 &lt;b&gt;누가 호출하는가&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;601&quot; data-start=&quot;575&quot;&gt;호출이 &lt;b&gt;얼마나 자주, 얼마나 민감한가&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;638&quot; data-start=&quot;602&quot;&gt;이 경계가 &lt;b&gt;외부 계약에 가까운가, 내부 구현에 가까운가&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;771&quot; data-start=&quot;640&quot; data-ke-size=&quot;size16&quot;&gt;이 질문에 대한 답이 정해지면, REST와 gRPC 중 무엇을 써야 할지도 자연스럽게 따라왔습니다.&lt;br /&gt;MSA 환경에서는 하나를 고집하기보다, 각각이 잘 어울리는 자리에 두는 것이 결국 가장 안전하고 현실적인 선택이었습니다&lt;/p&gt;</description>
      <category>Backend</category>
      <category>grpc</category>
      <category>MSA</category>
      <category>MSA에서의 혼합 운용 패턴</category>
      <category>REST</category>
      <category>REST VS gRPC</category>
      <category>REST와 gRPC</category>
      <category>성능과 스트리밍에서의 차이</category>
      <author>누구세연</author>
      <guid isPermaLink="true">https://sy-develop-note.tistory.com/176</guid>
      <comments>https://sy-develop-note.tistory.com/176#entry176comment</comments>
      <pubDate>Sun, 28 Dec 2025 19:34:37 +0900</pubDate>
    </item>
    <item>
      <title>어떤 메시지 큐를 선택할까? (Kafka &amp;middot; RabbitMQ &amp;middot; Redis)</title>
      <link>https://sy-develop-note.tistory.com/175</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;최근에 비동기 처리를 추가할 일이 생겼는데, 막상 시작하려니 &amp;ldquo;그래서 나는 어떤 큐를 써야 하지?&amp;rdquo;라는 고민이 제일 먼저 들었습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Kafka도 흔히 큐처럼 사용되고, RabbitMQ는 이름부터 Message Queue이고, Redis로도 큐를 만들어 쓸 수 있다 보니 더 헷갈리더라고요.&lt;br /&gt;정리해 보니 결국 중요한 건 이름이 아니라, 내가 원하는 동작이었습니다.&lt;br /&gt;메시지를 다시 처리해야 하는지, 작업을 어떻게 나눌 건지, 유실을 어디까지 허용할 수 있는지, 운영 복잡도는 감당 가능한지 같은 부분이 중요했습니다.&lt;br /&gt;그래서 이 글에서는 먼저 선택해 보고(선택표) &amp;rarr; 기준을 세운 뒤(질문) &amp;rarr; 각 큐를 이해하는(설명) 순서로 정리해보려 합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;먼저 선택표부터&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 592px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 325px; text-align: center;&quot;&gt;&lt;b&gt;상황/요구&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 267px; text-align: center;&quot;&gt;&lt;b&gt;추천&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 325px;&quot;&gt;이벤트 로그, CDC, 재처리&amp;middot;재생 가능해야 함&lt;/td&gt;
&lt;td style=&quot;width: 267px;&quot;&gt;Kafka&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 325px;&quot;&gt;작업 큐, 라우팅/재시도/DLQ가 필요함&lt;/td&gt;
&lt;td style=&quot;width: 267px;&quot;&gt;RabbitMQ (혹은 SQS/ActiveMQ)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 325px;&quot;&gt;초경량 비동기, 매우 낮은 지연, 이미 Redis 보유&lt;/td&gt;
&lt;td style=&quot;width: 267px;&quot;&gt;Redis Queue/Streams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 325px;&quot;&gt;기록용 + 즉시 처리 병행&lt;/td&gt;
&lt;td style=&quot;width: 267px;&quot;&gt;Kafka로 기록, Redis로 즉시 작업&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;여기서 끝내도 되고, &amp;ldquo;왜?&amp;rdquo;가 궁금하면 아래 &amp;ldquo;4가지 질문&amp;rdquo;으로 한 번 더 체크해 보면 좋습니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;선택을 결정짓는 4가지 질문&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;표가 애매하게 느껴지는 경우에는 이 질문 4개만 답해보면 방향이 잡힙니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 &lt;b&gt;다시 읽어야 하나?&lt;/b&gt; (과거 재생, 리플레이)&lt;/li&gt;
&lt;li&gt;여러 Consumer가 &lt;b&gt;각각 처리&lt;/b&gt;해야 하나? 아니면 하나만 처리하면 되나?&lt;/li&gt;
&lt;li&gt;장애 시 &lt;b&gt;유실을 허용&lt;/b&gt;할 수 있는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;운영 복잡도&lt;/b&gt;(클러스터, 모니터링, 튜닝)를 감당할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;용어만 정리..!&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull; Producer: 메시지를 생성해 큐/토픽으로 보내는 주체.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;bull; Consumer: 메시지를 받아 실제 작업을 수행하는 주체.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;bull; Queue/Topic: 메시지가 소비되기 전까지 머무는 논리적 단위.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;Kafka는 Topic이라는 로그 구조를 사용하고, RabbitMQ와 Redis는 Queue나 Stream/List 같은 구조로 메시지를 분배한다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;bull; Offset/Ack: 메시지 처리 진행 상황을 표시하는 방식.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;Kafka는 Offset으로 읽은 위치를 기록하고, RabbitMQ/Redis Streams는 Ack를 통해 처리 완료를 확정한다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;큐별로 뭐가 다를까?&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이제부터는 Kafka/RabbitMQ/Redis가 각각 어떤 상황에 강한지, &amp;ldquo;쓰임새 기준&amp;rdquo;으로 정리해 보겠습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Kafka (이벤트 스트리밍)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한마디로: 이벤트를 길게 쌓아두고, 필요하면 과거까지 되감아 보는 로그 창고.&lt;/li&gt;
&lt;li&gt;디스크 기반 로그, Offset으로 재처리&amp;middot;재생 가능.&lt;/li&gt;
&lt;li&gt;매우 높은 처리량, 다수 Consumer가 각자 읽어감.&lt;/li&gt;
&lt;li&gt;운영 난이도 높음(클러스터/모니터링/스키마 관리 필요).&lt;/li&gt;
&lt;li&gt;주 용도: 도메인 이벤트, CDC, 로그 파이프라인.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Kafka 아키텍처 개요&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2390&quot; data-origin-height=&quot;1428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TYHbu/dJMcadmZb2u/7wlcJ4gFR7px91T7wb7nT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TYHbu/dJMcadmZb2u/7wlcJ4gFR7px91T7wb7nT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TYHbu/dJMcadmZb2u/7wlcJ4gFR7px91T7wb7nT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTYHbu%2FdJMcadmZb2u%2F7wlcJ4gFR7px91T7wb7nT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;579&quot; height=&quot;346&quot; data-origin-width=&quot;2390&quot; data-origin-height=&quot;1428&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Producer가 메시지를 Topic/Partition에 push(실제로는 브로커에 append)합니다.&lt;/li&gt;
&lt;li&gt;다수의 Broker가 Topic을 Partition 단위로 분산 저장해 확장성과 내결함성을 확보합니다.&lt;/li&gt;
&lt;li&gt;Consumer가 pull 방식으로 읽으며, Consumer Group을 사용해 스케일아웃합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Topic/Partition/Consumer Group 동작&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2394&quot; data-origin-height=&quot;1234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b061B3/dJMcafSHp41/9HKYrzrAO1uyxzP6h40rC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b061B3/dJMcafSHp41/9HKYrzrAO1uyxzP6h40rC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b061B3/dJMcafSHp41/9HKYrzrAO1uyxzP6h40rC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb061B3%2FdJMcafSHp41%2F9HKYrzrAO1uyxzP6h40rC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2394&quot; height=&quot;1234&quot; data-origin-width=&quot;2394&quot; data-origin-height=&quot;1234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 Topic은 여러 Partition으로 나뉘어 병렬 처리&amp;middot;저장이 가능합니다.&lt;/li&gt;
&lt;li&gt;Producer는 특정 Partition에 레코드를 보냅니다(키 기반 파티셔닝 등).&lt;/li&gt;
&lt;li&gt;Consumer Group은 Partition을 나눠 맡아 읽고, 그룹 내에서 메시지가 중복 처리되지 않도록 조정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Offset으로 재생&amp;middot;재처리&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2390&quot; data-origin-height=&quot;1434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAFjrz/dJMcaajx3fz/f838IaVLeH1gVUKUFJH40k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAFjrz/dJMcaajx3fz/f838IaVLeH1gVUKUFJH40k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAFjrz/dJMcaajx3fz/f838IaVLeH1gVUKUFJH40k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAFjrz%2FdJMcaajx3fz%2Ff838IaVLeH1gVUKUFJH40k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2390&quot; height=&quot;1434&quot; data-origin-width=&quot;2390&quot; data-origin-height=&quot;1434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 Partition은 순차적인 Offset을 갖습니다. Consumer Group은 자신이 어디까지 읽었는지 Offset으로 기록합니다.&lt;/li&gt;
&lt;li&gt;Offset을 되돌리면 과거 메시지를 다시 읽어 재처리&amp;middot;재생이 가능합니다.&lt;/li&gt;
&lt;li&gt;로그가 디스크에 일정 기간 보관되므로, 재처리/분석/장애 복구 시 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;RabbitMQ (메시지 브로커)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한마디로: 작업 분배기. 들어온 일을 큐에 넣고 여러 작업자가 나눠 처리.&lt;/li&gt;
&lt;li&gt;메시지를 큐에 넣고 한 Consumer가 처리(워크 큐 모델).&lt;/li&gt;
&lt;li&gt;Exchange/Binding으로 라우팅 유연, Ack/Retry/DLQ 기본 제공.&lt;/li&gt;
&lt;li&gt;처리량&amp;middot;운영 복잡도 중간, 작업 큐에 최적.&lt;/li&gt;
&lt;li&gt;주 용도: 주문 처리, 백그라운드 잡, 워크플로 분기.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;RabbitMQ 라우팅 개요&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2330&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5lspD/dJMcac9sZKf/MLlEiygGlysEybKuEFWy00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5lspD/dJMcac9sZKf/MLlEiygGlysEybKuEFWy00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5lspD/dJMcac9sZKf/MLlEiygGlysEybKuEFWy00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5lspD%2FdJMcac9sZKf%2FMLlEiygGlysEybKuEFWy00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2330&quot; height=&quot;1360&quot; data-origin-width=&quot;2330&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher가 메시지를 Exchange에 발행하면, Binding 규칙에 따라 하나 혹은 여러 Queue로 복사&amp;middot;분기됩니다(Direct/Topic/Fanout/Headers).&lt;/li&gt;
&lt;li&gt;각 Queue는 소비자에게 분배되며, 소비자는 Ack로 처리 완료를 알립니다. Ack가 없으면 재전송되거나 DLQ로 보낼 수 있습니다.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;한 메시지 = 한 작업&amp;rdquo; 모델에 강해 주문&amp;middot;티켓 처리 같은 워크 큐, 라우팅이 많은 워크플로에 적합합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Redis Queue/Streams (인메모리 큐)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한마디로: 가벼운 전달자. 메모리에 잠깐 올려두고 바로 소비.&lt;/li&gt;
&lt;li&gt;In-memory라 매우 빠름, 설정&amp;middot;운영이 단순.&lt;/li&gt;
&lt;li&gt;기본 List/PubSub/Streams로 큐 구현. Streams는 Consumer Group/Offset 지원.&lt;/li&gt;
&lt;li&gt;내구성&amp;middot;장기 보관은 약함, 재처리&amp;middot;Retry 로직은 직접 설계 필요.&lt;/li&gt;
&lt;li&gt;주 용도: 알림 트리거, 캐시 갱신, 단기&amp;middot;경량 백그라운드 작업.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Redis Streams: Consumer Group 처리&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2Qtxt/dJMcadmZb6Z/1kJaakzszIpURZQ2Rg12L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Qtxt/dJMcadmZb6Z/1kJaakzszIpURZQ2Rg12L1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Qtxt/dJMcadmZb6Z/1kJaakzszIpURZQ2Rg12L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Qtxt%2FdJMcadmZb6Z%2F1kJaakzszIpURZQ2Rg12L1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1386&quot; height=&quot;866&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Producer가 &lt;code&gt;XADD&lt;/code&gt;로 스트림에 메시지를 append합니다(시간순 로그).&lt;/li&gt;
&lt;li&gt;Consumer Group이 &lt;code&gt;XREADGROUP&lt;/code&gt;으로 메시지를 가져가고, 처리 후 &lt;code&gt;XACK&lt;/code&gt;으로 완료를 알립니다.&lt;/li&gt;
&lt;li&gt;각 메시지는 그룹 내에서 한 번만 할당되며, Ack 되지 않은 메시지는 Pending List에 남아 재전달이 가능합니다.&lt;/li&gt;
&lt;li&gt;여러 Consumer 인스턴스가 병렬로 처리하되, 같은 메시지가 중복 소비되지 않게 조정되는 점이 핵심입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Redis Pub/Sub: 실시간 브로드캐스트&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;992&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOJGqW/dJMcacBFU3U/vOrWCujVxGRD7QXwb1EuzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOJGqW/dJMcacBFU3U/vOrWCujVxGRD7QXwb1EuzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOJGqW/dJMcacBFU3U/vOrWCujVxGRD7QXwb1EuzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOJGqW%2FdJMcacBFU3U%2FvOrWCujVxGRD7QXwb1EuzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1992&quot; height=&quot;992&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;992&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher가 채널에 메시지를 Publish 하면, 구독 중인 Subscriber가 곧바로 수신합니다.&lt;/li&gt;
&lt;li&gt;메시지가 Redis에 남지 않으므로 Subscriber가 없거나 잠시 끊겨 있으면 유실됩니다.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;실시간 알림&amp;rdquo;처럼 즉시 전달이 중요하고 유실이 치명적이지 않은 경우에 적합합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Redis List Queue: 단순 FIFO&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caiR9b/dJMcab3PxUV/WHmnZMUJsrtSHezPH5gkgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caiR9b/dJMcab3PxUV/WHmnZMUJsrtSHezPH5gkgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caiR9b/dJMcab3PxUV/WHmnZMUJsrtSHezPH5gkgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaiR9b%2FdJMcab3PxUV%2FWHmnZMUJsrtSHezPH5gkgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1216&quot; height=&quot;924&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;LPUSH&lt;/code&gt;로 넣고 &lt;code&gt;RPOP&lt;/code&gt; 또는 &lt;code&gt;BLPOP&lt;/code&gt;으로 빼는 단순 FIFO 큐입니다.&lt;/li&gt;
&lt;li&gt;별도 Ack 개념이 없어 소비자가 실패하면 재처리를 직접 구현해야 합니다.&lt;/li&gt;
&lt;li&gt;간단한 백그라운드 작업이나 일시적 큐잉에 적합하지만, 안정적 재처리나 DLQ가 필요하면 Streams나 별도 로직이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;큐별 동작 원리 요약&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위 내용을 &amp;ldquo;동작 방식&amp;rdquo; 기준으로 한 번에 요약하면 이렇게 정리됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Kafka&lt;/b&gt;: Producer가 Topic/Partition에 로그를 append(디스크). Consumer Group이 Offset을 기준으로 읽고 commit 합니다. 메시지는 보관 기간 동안 남아 여러 Consumer가 각자 읽을 수 있고, 과거 재생도 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RabbitMQ&lt;/b&gt;: Producer &amp;rarr; Exchange &amp;rarr; Binding 규칙으로 Queue에 라우팅. Consumer가 메시지를 받아 Ack 하며, Ack가 없으면 재전송(DLQ로 이동 가능)됩니다. 1개 큐의 메시지를 여러 Consumer가 &amp;ldquo;나눠 처리&amp;rdquo;하는 워크 큐에 강점이 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Redis Queue/Streams&lt;/b&gt;: List는 LPUSH/RPOP의 단순 FIFO(ack 없음). Pub/Sub은 저장 없이 즉시 전달(수신자가 없으면 유실). Streams는 append-only 로그 + Consumer Group/Offset으로 기본 ack/재전송을 지원해 Redis 내에서 가장 브로커스러운 모델입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;패턴별 추천&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;로그/이벤트 파이프라인&lt;/b&gt;: Kafka (재생&amp;middot;다중 소비 필요).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;업무/배치 작업 큐&lt;/b&gt;: RabbitMQ/SQS (Ack/Retry/DLQ, 라우팅 필요).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;알림/캐시/짧은 잡&lt;/b&gt;: Redis Queue/Streams (지연 최소, 단순 운영).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기록 + 즉시 처리 분리&lt;/b&gt;: Kafka로 기록, Redis로 알림&amp;middot;캐시 등 즉시 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;메시지 큐를 고를 때 성능 지표부터 보기보다는, 이 메시지가 다시 처리되어야 하는지, 여러 소비자가 각각 처리해야 하는지, 유실을 어디까지 허용할 수 있는지를 먼저 정하는 게 더 중요하다고 느꼈습니다.&lt;br /&gt;그 기준으로 보면 Kafka, RabbitMQ, Redis는 경쟁 관계라기보다 각자 잘 맞는 자리가 분명한 도구들입니다.&lt;br /&gt;저는 이 기준을 세운 뒤로 &amp;ldquo;어떤 큐를 써야 하지?&amp;rdquo;라는 고민이 많이 줄었습니다.&lt;/blockquote&gt;</description>
      <category>Architecture</category>
      <category>Kafka</category>
      <category>Message Queue</category>
      <category>RabbitMQ</category>
      <category>redis</category>
      <category>메시지 큐</category>
      <category>이벤트 스트리밍</category>
      <author>누구세연</author>
      <guid isPermaLink="true">https://sy-develop-note.tistory.com/175</guid>
      <comments>https://sy-develop-note.tistory.com/175#entry175comment</comments>
      <pubDate>Wed, 17 Dec 2025 22:00:12 +0900</pubDate>
    </item>
    <item>
      <title>애매한 HTTP Method, PATCH 메서드</title>
      <link>https://sy-develop-note.tistory.com/174</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;REST API를 설계하다 보면 잠깐 멈칫하게 되는 메서드가 있는 것 같습니다.&lt;br /&gt;바로 PATCH인데요. &lt;br /&gt;GET/POST/PUT/DELETE와 달리 &amp;ldquo;부분 수정&amp;rdquo;이라는 설명만으로는 실제 사용 기준이 모호하고, 여러 표준이 공존해 선택지가 많습니다.&lt;br /&gt;이 글에서는 PATCH가 왜 애매하게 느껴지는지, PUT과 무엇이 다른지, 그리고 실무에서 어떤 방식으로 선택해 사용했는지까지 정리해 보려고 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PATCH가 애매한 이유&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PATCH는 HTTP/1.1(RFC 5789)에 정의된 &lt;b&gt;부분 수정(partial update)&lt;/b&gt; 용 메서드입니다.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;부분 수정&amp;rdquo;이라는 말이 열려 있어 다음과 같은 질문이 생깁니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 필드까지 수정 가능해야 하는가?&lt;/li&gt;
&lt;li&gt;없는 필드가 오면 어떻게 처리해야 하는가? &lt;code&gt;null&lt;/code&gt;은 변경인가 삭제인가?&lt;/li&gt;
&lt;li&gt;PUT과의 경계는 어디까지로 볼 것인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스펙이 구현 방법을 강제하지 않아 팀마다 사용 방식이 달라지기 쉽습니다.&lt;/li&gt;
&lt;li&gt;특히 다음 세 가지가 애매함을 키웁니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;표현 방식의 다중 표준&lt;/b&gt;: JSON Merge Patch, JSON Patch 등 Content-Type별 규칙이 달라 어떤 포맷을 선택할 지부터 정해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부분 수정 범위의 불명확성&lt;/b&gt;: 누락 필드는 유지인지 삭제인지, &lt;code&gt;null&lt;/code&gt;은 값 설정인지 제거인지 같은 해석을 팀이 합의해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멱등성/원자성 해석 차이&lt;/b&gt;: PATCH는 멱등성이 구현에 따라 달라지고, 여러 연산을 한 요청에 묶을 때 실패 시 롤백 여부 등 원자성 정책도 명확히 정해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PUT vs PATCH&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;PUT&lt;/th&gt;
&lt;th&gt;PATCH&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;td&gt;리소스 전체 교체&lt;/td&gt;
&lt;td&gt;리소스 부분 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;요청 바디&lt;/td&gt;
&lt;td&gt;전체 리소스 표현&lt;/td&gt;
&lt;td&gt;변경하려는 필드만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;누락 필드&lt;/td&gt;
&lt;td&gt;제거될 수 있음&lt;/td&gt;
&lt;td&gt;그대로 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;멱등성&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;구현에 따라 다름&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PUT은 &lt;b&gt;&amp;ldquo;이 리소스를 이 상태로 만들어라&amp;rdquo;&lt;/b&gt; 라는 의미에 가깝습니다.&lt;/li&gt;
&lt;li&gt;PATCH는 &lt;b&gt;&amp;ldquo;이 리소스에 이 변경을 적용해라&amp;rdquo;&lt;/b&gt; 라는 의미에 가깝습니다.&lt;/li&gt;
&lt;li&gt;PATCH에서 변경을 어떻게 표현할지에 대한 명확한 기준이 없다는 점이 애매함의 핵심이라고 생각합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PATCH 표현 방식 두 가지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(표현 방식에 맞춰 Content-Type을 명시하는 게 헷갈림을 줄입니다. 대부분 &lt;code&gt;application/json&lt;/code&gt;으로 받기도 하지만, 표준은 merge patch &amp;rarr; &lt;code&gt;application/merge-patch+json&lt;/code&gt;, json patch &amp;rarr; &lt;code&gt;application/json-patch+json&lt;/code&gt;입니다.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) JSON Merge Patch (&lt;code&gt;application/merge-patch+json&lt;/code&gt;)&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;new-name&quot;,
  &quot;email&quot;: null
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동작&lt;/b&gt;: 요청에 포함된 필드만 기존 리소스에 병합합니다.&lt;br /&gt;포함되지 않은 필드는 그대로 유지합니다.&lt;br /&gt;&lt;code&gt;null&lt;/code&gt;은 보통 해당 필드를 제거(또는 비활성화)로 해석합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;강점&lt;/b&gt;: 구조가 단순하고 DTO 기반으로 쉽게 구현할 수 있어 애플리케이션 레벨에서 빠르게 적용 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;한계&lt;/b&gt;: 깊은 중첩/배열을 세밀하게 조작하기엔 표현력이 부족합니다.
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;    `null`의 의미를 팀이 명확히 합의해야 합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) JSON Patch (&lt;code&gt;application/json-patch+json&lt;/code&gt;)&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[
  { &quot;op&quot;: &quot;replace&quot;, &quot;path&quot;: &quot;/name&quot;, &quot;value&quot;: &quot;new-name&quot; },
  { &quot;op&quot;: &quot;remove&quot;, &quot;path&quot;: &quot;/email&quot; }
]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동작&lt;/b&gt;: &lt;code&gt;op/path/value&lt;/code&gt;로 변경 작업을 순서대로 명시합니다.&lt;br /&gt;&lt;code&gt;add&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt;, &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;move&lt;/code&gt;, &lt;code&gt;copy&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt; 등의 연산을 지원합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;강점&lt;/b&gt;: 배열&amp;middot;중첩 구조까지 세밀하게 지정할 수 있고, 한 요청에 여러 연산을 묶어 순차 적용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;한계&lt;/b&gt;: path 검증, 파싱, 예외 처리, 실패 시 롤백 정책 등을 별도로 설계해야 하며, 클라이언트&amp;middot;서버 모두 동일한 규칙을 이해해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주로 사용하게 된 Merge Patch&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실무에서는 요구사항이 단순했기 때문에 Merge Patch로 충분했습니다.&lt;br /&gt;주요 패턴은 필드 값 변경, on/off, &lt;code&gt;null&lt;/code&gt;을 통한 비활성화였습니다.&lt;/li&gt;
&lt;li&gt;일반 DTO로 처리할 수 있어 구현과 유지보수 비용이 낮았습니다.
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@PatchMapping(&quot;/users/{id}&quot;)
public void updateUser(@RequestBody UpdateUserRequest request) {
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;JSON Patch는 별도의 파서, path 유효성 검증, 실패 시 에러 규칙을 설계해야 해서 팀 단위 유지보수 비용이 커집니다.&lt;/li&gt;
&lt;li&gt;표준을 모두 아는 것과 서비스에 맞게 선택하는 것은 다릅니다. 우리는 &amp;ldquo;명확하고 예측 가능한 API&amp;rdquo;를 우선해 Merge Patch를 채택했습니다.&lt;/li&gt;
&lt;li&gt;다만 팀 컨벤션과 요구사항 복잡도에 따라 달라질 수 있습니다. 깊은 중첩/배열을 세밀하게 다루거나 여러 연산을 한 번에 표현해야 하면 JSON Patch가 더 적합할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제 JSON Patch를 고려할까?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열이나 중첩 구조를 &lt;b&gt;정밀하게 조작&lt;/b&gt;해야 하는 경우: 특정 index 삽입/삭제, 깊은 path 수정 등.&lt;br /&gt;ex) TODO 리스트에서 3번째 항목을 1번째로 이동, 중첩 설정 객체의 일부만 제거.&lt;/li&gt;
&lt;li&gt;한 요청에 &lt;b&gt;여러 연산을 순서대로 묶어야&lt;/b&gt; 하는 경우: add &amp;rarr; replace &amp;rarr; remove처럼 순차적 패치를 명시해야 할 때.&lt;br /&gt;(Merge Patch에서도 add/modify 필드를 나눠 흉내 낼 수 있지만, JSON Patch는 그 흐름이 표준화되어 있고 클라이언트&amp;middot;서버가 동일한 표현을 공유하기 쉽습니다.)&lt;/li&gt;
&lt;li&gt;변경 전 상태를 확인하는 &lt;b&gt;조건부 업데이트&lt;/b&gt;가 필요한 경우: &lt;code&gt;test&lt;/code&gt; 연산으로 사전 검증 후 적용.&lt;br /&gt;ex) 특정 버전/etag일 때만 replace, 값이 X일 때만 remove.&lt;/li&gt;
&lt;li&gt;외부에 공개되는 &lt;b&gt;범용/플러그인형 API&lt;/b&gt;처럼 표준화된 패치 포맷을 요구하는 경우.&lt;/li&gt;
&lt;li&gt;문서 기반 리소스(설정/정책 문서 등)처럼 &lt;b&gt;구조가 자주 변하고 호환성&lt;/b&gt;을 유지해야 하는 경우.&lt;br /&gt;ex) 사용자 정의 스키마를 가진 설정 문서, 정책 JSON을 여러 클라이언트가 패치해야 하는 경우.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;PATCH가 애매한 이유는 메서드가 모호해서라기보다, 구현 방식이 너무 열려 있기 때문입니다.&lt;br /&gt;결국 팀이 무엇을 우선순위로 두느냐가 선택을 가릅니다.&lt;br /&gt;우리처럼 단순한 요구라면 Merge Patch 하나로 충분히 깔끔하게 갈 수 있고, 구조가 복잡하거나 표준화된 패치 표현을 외부에 열어야 한다면 JSON Patch가 힘을 발휘합니다.&lt;br /&gt;중요한 건 두 표준을 모두 아는 것보다, 서비스에 맞춰 고르게 쓸 수 있어야 할 것 같습니다!&lt;/blockquote&gt;</description>
      <category>Web/HTTP</category>
      <category>HTTP Method</category>
      <category>patch</category>
      <category>PATCH VS PUT</category>
      <category>PUT</category>
      <author>누구세연</author>
      <guid isPermaLink="true">https://sy-develop-note.tistory.com/174</guid>
      <comments>https://sy-develop-note.tistory.com/174#entry174comment</comments>
      <pubDate>Fri, 12 Dec 2025 16:24:16 +0900</pubDate>
    </item>
    <item>
      <title>프록시 DB란? 읽기&amp;middot;쓰기 분리</title>
      <link>https://sy-develop-note.tistory.com/173</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 서비스를 운영하다 보면, 트래픽이 늘어날수록 DB 부하가 병목이 되는 순간이 찾아옵니다.&lt;br /&gt;트래픽 스파이크나 정기 리포트 배치처럼 단순 조회성 쿼리가 몰리면 &amp;ldquo;운영 데이터베이스의 안정성&amp;rdquo;이 흔들리기 시작하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 자주 등장하는 해결책 중 하나가 바로 프록시 DB(Proxy DB) 입니다.&lt;br /&gt;단순히 DB 앞단에 장비를 하나 더 세우는 것이 아니라, 읽기/쓰기 특성을 분리하고 운영 전략을 명시적으로 가져가겠다는 선언에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이번 글에서는 프록시 DB의 기본 개념부터, 운영 중 겪는 실전 이슈, 그리고 개발자 입장에서의 사용 패턴까지 정리해보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  프록시 DB란 무엇일까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 본 DB(Primary) 앞단에 서서 요청을 중계하는 &amp;ldquo;중간 계층&amp;rdquo;입니다. 애플리케이션은 Proxy 하나만 바라보고, Proxy가 알아서 Primary/Replica를 골라주는 구조입니다.  &lt;br /&gt;여기서 Primary는 &amp;ldquo;쓰기 전용 DB&amp;rdquo;, Replica는 &amp;ldquo;Primary를 복제한 읽기 전용 DB&amp;rdquo;라고 이해하면 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처 다이어그램은 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;1026&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4Uhs3/dJMcadUE7sF/XkrjRhPXXmKGXH9bDbZCik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4Uhs3/dJMcadUE7sF/XkrjRhPXXmKGXH9bDbZCik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4Uhs3/dJMcadUE7sF/XkrjRhPXXmKGXH9bDbZCik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4Uhs3%2FdJMcadUE7sF%2FXkrjRhPXXmKGXH9bDbZCik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1632&quot; height=&quot;1026&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;1026&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Application: 클라이언트 요청을 받아 SQL을 Proxy로 던집니다. DB가 하나인 것처럼 느끼죠.&lt;/li&gt;
&lt;li&gt;DB Proxy / Router: SQL을 파싱하고 커넥션 풀, 트랜잭션 스티키니스를 관리합니다.&lt;/li&gt;
&lt;li&gt;Primary DB (Writer): INSERT/UPDATE/DELETE를 처리하고 binlog를 생성합니다.&lt;/li&gt;
&lt;li&gt;Replica DB (Readers): Primary의 binlog를 비동기로 따라가며 읽기 트래픽을 분산합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 진영에서 ProxySQL, MaxScale, MySQL Router가 대표적이고, 클라우드에서는 RDS Proxy나 Aurora Router, 그리고 Vitess VTGate 같은 도구도 자주 언급됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  어떻게 동작할까?&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;애플리케이션이 Proxy 엔드포인트로 SQL을 보냅니다.&lt;/li&gt;
&lt;li&gt;Proxy는 SQL 타입, 세션 힌트, 트랜잭션 상태를 보고 &amp;ldquo;이건 Primary&amp;rdquo;, &amp;ldquo;이건 Replica&amp;rdquo;라고 결정합니다.&lt;/li&gt;
&lt;li&gt;Primary는 쓰기 트랜잭션을 처리하고 binlog를 남깁니다.&lt;/li&gt;
&lt;li&gt;Replica는 binlog를 적용해 최신 상태를 따라잡고, SELECT 요청을 받습니다.&lt;/li&gt;
&lt;li&gt;Proxy는 커넥션 수, Replica lag, 헬스 상태를 감시하며 Failover 전략을 실행합니다.&lt;/li&gt;
&lt;li&gt;필요하다면 Proxy가 자체 커넥션 풀과 캐시 정책을 적용해 애플리케이션의 DB 커넥션 개수를 간접적으로 조절합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 &amp;ldquo;새 글 작성&amp;rdquo; 버튼을 눌러 &lt;code&gt;POST /items&lt;/code&gt; 를 호출하면, Proxy는 INSERT 쿼리를 Primary로 보냅니다.&lt;br /&gt;곧바로 &lt;code&gt;GET /items/{id}&lt;/code&gt; 요청이 들어오면 Proxy는 &amp;ldquo;방금 쓴 글을 바로 보여줘야 한다&amp;rdquo;는 조건을 보고 Primary로 라우팅 하지만, &amp;ldquo;전체 글 목록&amp;rdquo; 같은 리스트 조회는 Replica로 분산합니다.&lt;br /&gt;이 흐름이 읽기/쓰기 분리의 기본 골격입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;쓰기 쿼리는 반드시 Primary&lt;/b&gt;, &lt;b&gt;읽기 쿼리는 Replica로 분산&lt;/b&gt;. 그리고 &amp;ldquo;쓰기 직후 조회&amp;rdquo;처럼 정합성이 민감한 흐름은 Proxy가 Primary를 고정시켜 준다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  왜 도입할까?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;읽기/쓰기 분리&lt;/b&gt;: SELECT를 Replica로 보내 Primary 부하를 줄입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트래픽 분산&lt;/b&gt;: 리포트, 리스트 API 같은 읽기를 Replica에서 처리해 전체 응답 시간을 안정화합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장애 대비&lt;/b&gt;: Primary 장애 시 Replica를 임시 조회 노드로 쓰고, Proxy가 커넥션을 차단해 장애 범위를 제한합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안/정책 분리&lt;/b&gt;: 외부에서는 Proxy만 노출하고, 내부 정책을 Proxy에서 통제할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 프록시 DB는 &amp;ldquo;DB를 더 많이 장만하는 것&amp;rdquo;이 아니라, &lt;b&gt;읽기와 쓰기의 속성을 나눠 다루는 운영 방식&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ 도입 전에 생각할 것&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;운영 복잡도 증가&lt;/b&gt;: Proxy 자체도 장애 포인트이므로 이중화, 패치, 모니터링 계획이 선행돼야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SQL 파싱 한계&lt;/b&gt;: Stored Procedure나 복잡한 커스텀 함수는 Proxy가 읽기/쓰기를 잘못 판단할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정합성 정책 수립&lt;/b&gt;: &amp;ldquo;eventual consistency를 허용하는 범위&amp;rdquo;를 UX 관점에서 합의하지 않으면 도입 효과가 반감됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  운영 중 자주 맞닥뜨리는 이슈&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Proxy를 세우면 마치 모든 것이 자동화될 것 같지만, 실제 운영에서는 다음과 같은 함정이 반복해서 등장합니다.&lt;br /&gt;각 이슈가 왜 생기는지, 현장에서 즉시 적용할 수 있는 대응법을 함께 정리했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1)   복제 지연(Replication Lag)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Replica는 Primary의 binlog를 따라가는데, 트래픽이 쏠리거나 대량 쓰기가 붙으면 지연이 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;증상&lt;/b&gt;: &amp;ldquo;방금 저장한 데이터가 조회되지 않는다&amp;rdquo;, 빈 리스트, 오래된 캐시 갱신.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;왜 생길까?&lt;/b&gt;: Primary &amp;rarr; Replica 동기화는 네트워크와 I/O에 의존하므로, 초당 수천 건의 INSERT가 들어오면 Replica가 그 속도를 따라잡지 못합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;팁&lt;/b&gt;: CloudWatch나 PMM처럼 &lt;code&gt;Replica_Lag&lt;/code&gt; 지표에 알람을 걸고, 허용 지연(예: 1초 이상) 시 Proxy가 해당 Replica를 읽기 대상에서 제외하도록 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대응&lt;/b&gt;: 쓰기 직후 조회는 반드시 Primary, Replica lag가 일정 기준 초과하면 Proxy에서 제외, 도메인별로 &amp;ldquo;읽기라도 Primary 강제&amp;rdquo; 정책을 두는 것도 방법입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2)   스키마 불일치(DDL 타이밍)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Primary에는 새로운 컬럼이 있는데 Replica에는 없어 &lt;code&gt;Unknown column&lt;/code&gt; 에러가 나는 상황입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;왜 생길까?&lt;/b&gt;: Replica는 binlog를 순차적으로 따라가기 때문에, Primary에서 &lt;code&gt;ALTER TABLE&lt;/code&gt; 을 실행한 직후 Replica는 아직 예전 스키마로 SELECT를 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;팁&lt;/b&gt;: &lt;code&gt;pt-online-schema-change&lt;/code&gt;, Liquibase 같은 도구로 DDL 계획을 코드화하고, 배포 일정과 Proxy 라우팅 정책을 한 문서에서 관리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대응&lt;/b&gt;: DDL 적용 순서를 Primary &amp;rarr; Replica로 명확히 하고, 마이그레이션 동안 해당 도메인의 읽기는 Primary로 고정합니다. 호환 스키마(old/new 필드 동시 지원)를 설계하는 것도 안전합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3)   Failover(장애 전환) 복잡성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Primary 장애 시 Replica를 Primary로 승격해야 하는데, 비동기 복제라면 binlog가 다 적용되지 않았을 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;왜 생길까?&lt;/b&gt;: 장애는 대개 새벽이나 주말에 오고, 운영자가 직접 커넥션을 끊고 호스트를 바꾸는 동안 수천 개 커넥션이 타임아웃 나 버립니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;팁&lt;/b&gt;: ProxySQL의 &lt;code&gt;scheduler&lt;/code&gt;, RDS Proxy의 자동 failover 같은 기능을 미리 테스트하고, &amp;ldquo;누가 어떤 명령을 실행할지&amp;rdquo;까지 체크리스트로 만들어 두면 당황하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대응&lt;/b&gt;: 세미동기 복제를 고려하고, Proxy의 자동 Failover 기능을 적극 활용합니다. 장애 시 어떤 순서로 hostgroup 업데이트, DNS 변경, 애플리케이션 재연결을 할지 문서화해 두는 것이 정말 중요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) ⚙️ 트랜잭션 스티키니스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;BEGIN; SELECT ...; UPDATE ...;&lt;/code&gt; 가 서로 다른 노드로 라우팅되면 정합성이 깨집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;왜 생길까?&lt;/b&gt;: Proxy가 각 SQL을 독립적으로 판단하면, 트랜잭션 중간 SELECT를 Replica로 보내 버릴 수 있기 때문입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;팁&lt;/b&gt;: ProxySQL은 hostgroup &amp;ldquo;stickiness&amp;rdquo; 옵션이 있고, RDS Proxy는 트랜잭션 단위로 Primary에 붙어 있게 만들 수 있습니다. 프레임워크 단에서도 &lt;code&gt;@Transactional&lt;/code&gt; 에 readOnly=false 같은 속성을 명시합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대응&lt;/b&gt;: 트랜잭션 시작 시 Proxy가 세션을 Primary에 고정하고, &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; 는 무조건 Primary로 보내도록 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5)   관측과 모니터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Proxy, Primary, Replica 각각의 지표를 보지 않으면 장애 원인을 Primary 탓으로만 돌리게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;무엇을 볼까?&lt;/b&gt;: Replica lag, Proxy 커넥션 풀 사용률, hostgroup 별 큐 길이, 라우팅 실패율, Primary CPU/IO, slow query 비율이 기본 세트입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;팁&lt;/b&gt;: 한 대시보드에서 Proxy 지표와 DB 지표를 함께 보고, 월 1회라도 장애 리허설을 하면 &amp;ldquo;Proxy 설정은 잘돼 있는데 Primary가 버티지 못했다&amp;rdquo; 같은 오해를 줄일 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대응&lt;/b&gt;: Replica lag, Proxy 큐 길이, 라우팅 실패율 등을 모니터링하고, 주기적으로 Failover 리허설을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj;  개발자 입장에서의 사용 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 DB를 도입한다고 애플리케이션 코드가 모두 바뀌는 것은 아닙니다..!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실적으로는 (찾아보며) 아래 두 패턴을 가장 많이 보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;핵심은 &amp;ldquo;Proxy가 자동으로 판단하게 둘 것인가&amp;rdquo;, &amp;ldquo;애플리케이션이 읽기/쓰기를 명시할 것인가&amp;rdquo;의 선택입니다.&lt;br /&gt;도입한 뒤에도 &amp;ldquo;어떤 쿼리가 왜 특정 노드로 가는지&amp;rdquo;를 전제와 함께 문서화해야 지속적으로 운용할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 패턴 A &amp;mdash; 프록시가 모든 라우팅 담당&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시가 &amp;ldquo;SELECT는 Replica, INSERT/UPDATE는 Primary&amp;rdquo;를 자동으로 결정합니다. 모놀리식이나 레거시 시스템에서 도입하기 쉽습니다.&lt;br /&gt;개발자는 기존 커넥션 문자열만 Proxy 주소로 바꾸면 되기 때문에 &amp;ldquo;비침투적&amp;rdquo;인 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정합성 규칙&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;INSERT 직후 데이터는 이미 알고 있다&lt;/b&gt;&lt;br /&gt;Primary에서 INSERT를 했으므로 PK, 입력 값, default 값, trigger 결과까지 애플리케이션이 보유하고 있습니다. &amp;rArr; POST 응답은 Replica 지연과 무관하게 정확합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제는 그다음 GET이 Replica로 갈 수 있다는 점&lt;/b&gt;&lt;br /&gt;&lt;code&gt;POST /items&lt;/code&gt; 다음 바로 &lt;code&gt;GET /items/{id}&lt;/code&gt; 를 호출하면 Replica에서 아직 복제가 안 끝났을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;쓰기 직후 조회는 Primary로 강제&lt;/b&gt;&lt;br /&gt;Proxy 세션 스티키니스(read-your-writes), SQL 힌트(&lt;code&gt;/* read_from_primary */&lt;/code&gt;), hostgroup routing을 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;POST 응답을 풍부하게&lt;/b&gt;&lt;br /&gt;생성된 리소스 정보를 충분히 내려주면 클라이언트가 즉시 GET을 호출할 이유가 줄어듭니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리스트 조회는 Replica 기반으로&lt;/b&gt;&lt;br /&gt;&amp;ldquo;조금 늦게 보여도 되는 데이터&amp;rdquo;는 Replica에서, 단건 상세 조회는 Primary로 분리합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 패턴 B &amp;mdash; 애플리케이션이 R/W 의도 명시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring+JPA 환경에서 많이 쓰는 방식으로 DataSource/Port를 나눕니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;writerDataSource &amp;rarr; Primary
readerDataSource &amp;rarr; Replica&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 코드 레벨에서 &amp;ldquo;이 쿼리는 읽기용, 저 쿼리는 쓰기용&amp;rdquo;을 분리해 Proxy에게 명확한 신호를 줍니다.&lt;br /&gt;MSA 구조나 이벤트 기반 시스템처럼 서비스별 특성을 살리고 싶을 때 유리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정합성 규칙&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;쓰기와 조회를 같은 트랜잭션 안에서 처리&lt;/b&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Transactional
public ItemResponse createItem(CreateItemReq req) {
    Item item = itemWriteRepo.save(...);    // INSERT &amp;rarr; Primary
    return ItemResponse.from(item);         // 엔티티로 바로 응답
}&lt;/code&gt;&lt;/pre&gt;
별도 SELECT 없이도 정확한 응답을 만들 수 있어 가장 안전합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상세 조회는 필요에 따라 Primary 강제&lt;/b&gt;&lt;br /&gt;&lt;code&gt;force_primary=true&lt;/code&gt; 같은 파라미터로 Primary를 선택하거나, 쓰기 직후 상세 API는 writerDataSource를 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리스트/통계는 Replica 전용&lt;/b&gt;&lt;br /&gt;eventual consistency를 UX로 허용하고, 읽기 부하 대부분을 Replica로 넘깁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reader/Writer 혼용 금지&lt;/b&gt;&lt;br /&gt;readOnly 트랜잭션에서 write 호출 금지, reader 포트에서 INSERT/UPDATE 금지 등 규칙을 코드로 강제합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✍️ 마무리 &amp;amp; 체크리스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 DB는 &amp;ldquo;읽기/쓰기 분리를 자동으로 해 주는 중간 계층&amp;rdquo;이라는 단순한 정의 뒤에, 운영 디테일과 정합성 전략이 숨어 있습니다.&lt;br /&gt;아래 체크리스트만 꾸준히 확인해도 Proxy 도입 효과를 확실히 체감할 수 있었습니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;질문&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;체크 포인트&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;어느 API가 최신성을 요구하는가?&lt;/td&gt;
&lt;td&gt;라우팅 힌트나 Primary 강제가 필요한 API를 목록화합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proxy가 SQL을 정확히 분류할 수 있는가?&lt;/td&gt;
&lt;td&gt;Prepared statement, 세션 변수, Stored Procedure 사용 여부를 점검합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replica lag와 Failover 상태를 모니터링하는가?&lt;/td&gt;
&lt;td&gt;Proxy/DB 지표를 한 대시보드로 묶고 알람을 정의합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장애 시나리오는 문서화되어 있는가?&lt;/td&gt;
&lt;td&gt;Primary 승격, hostgroup 업데이트, 애플리케이션 재연결 순서를 정리합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 DB를 도입하면 단순히 &amp;ldquo;DB를 분산했다&amp;rdquo;가 아니라, &lt;b&gt;쓰기 직후 조회는 Primary로&lt;/b&gt;, &lt;b&gt;eventual consistency를 허용할 영역은 Replica로&lt;/b&gt;, &lt;b&gt;장애 시나리오를 미리 연습&lt;/b&gt;한다는 습관을 가지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지 원칙만 실천해도 트래픽 폭증 순간마다 Proxy가 든든한 방패가 되어 줄 거라는 생각이 듭니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Data/DataBase</category>
      <category>db 운영</category>
      <category>DB 읽기쓰기 분리</category>
      <category>Failover전략</category>
      <category>ProxyDB</category>
      <category>ReplicationLag</category>
      <category>읽기쓰기 분리</category>
      <category>프록시 DB</category>
      <author>누구세연</author>
      <guid isPermaLink="true">https://sy-develop-note.tistory.com/173</guid>
      <comments>https://sy-develop-note.tistory.com/173#entry173comment</comments>
      <pubDate>Wed, 19 Nov 2025 22:29:27 +0900</pubDate>
    </item>
    <item>
      <title>nGrinder로 시작하는 부하 테스트 가이드</title>
      <link>https://sy-develop-note.tistory.com/172</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;JMeter는 다양한 부하 테스트를 수행할 수 있는 강력한 도구지만, 팀 단위 운영이나 리포트 공유 측면에서는 한계가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이런 불편함을 해소하기 위해 등장한 도구가 바로 nGrinder입니다. nGrinder는 분산 부하 테스트, 웹 기반 시나리오 관리, 자동 리포트 생성 등 팀 단위 테스트 환경에 필요한 기능을 기본으로 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;특히 JMeter에서 직접 설정해야 했던 분산 실행이나 리포팅 작업을 nGrinder에서는 웹 UI에서 클릭 몇 번으로 간단히 처리할 수 있죠.  &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이번 글에서는 JMeter에서는 다소 번거로웠던 부분들을 nGrinder에서는 어떻게 더 쉽게 해결할 수 있는지 함께 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;p data-end=&quot;490&quot; data-start=&quot;410&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;nGrinder란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nGrinder는 네이버에서 진행한 오픈 소스 프로젝트로 서버의 부하 테스트를 위한 도구입니다.&lt;br /&gt;쉽게 말해, 여러 대의 컴퓨터(Agent)를 동시에 이용해서 &lt;b&gt;대규모 트래픽을 한꺼번에 발생시켜 서버의 성능을 테스트&lt;/b&gt;할 수 있는 도구예요. ⚒️&lt;/p&gt;
&lt;p data-end=&quot;308&quot; data-start=&quot;229&quot; data-ke-size=&quot;size16&quot;&gt;JMeter가 개인용 테스트 툴이라면, nGrinder는 &lt;b&gt;팀이나 조직이 함께 사용하는 부하 테스트 플랫폼&lt;/b&gt;이라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;308&quot; data-start=&quot;229&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;308&quot; data-start=&quot;229&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;nGrinder 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nGrinder는 크게 두 가지 역할로 나뉩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;548&quot; data-start=&quot;365&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;448&quot; data-start=&quot;365&quot;&gt;&lt;b&gt;Controller&lt;/b&gt;: 테스트를 관리하는 중앙 서버예요.&lt;br /&gt;웹 UI를 통해 테스트를 만들고, 실행하고, 결과를 확인할 수 있습니다.&lt;/li&gt;
&lt;li data-end=&quot;548&quot; data-start=&quot;449&quot;&gt;&lt;b&gt;Agent&lt;/b&gt;: 실제로 요청을 보내 부하를 발생시키는 역할이에요.&lt;br /&gt;Controller의 지시에 따라 동시에 트래픽을 발생시키고, 결과 데이터를 다시 전달합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller가 언제, 몇 명이, 어떤 요청을 보낼지를 정하면 Agent들이 동시에 실행해 서버를 몰아붙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 이런 구조를 시각적으로 보여주는 &lt;b&gt;nGrinder 시스템 아키텍처&lt;/b&gt;입니다  &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 4.50.27.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;1172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tB6H2/btsQ5DTVfFe/IJKfhoNGsKBovXXhKR15Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tB6H2/btsQ5DTVfFe/IJKfhoNGsKBovXXhKR15Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tB6H2/btsQ5DTVfFe/IJKfhoNGsKBovXXhKR15Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtB6H2%2FbtsQ5DTVfFe%2FIJKfhoNGsKBovXXhKR15Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;537&quot; height=&quot;1172&quot; data-filename=&quot;스크린샷 2025-10-10 오후 4.50.27.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;1172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 사용자가 Controller에 테스트 실행을 요청&lt;br /&gt;2️⃣ Controller가 테스트 환경을 세팅하고 Agent들을 연결&lt;br /&gt;3️⃣ Agent들이 실제로 타깃 서버(Target Server)에 트래픽을 발생&lt;br /&gt;(이때 각 Agent는 Controller와 통신하며 상태를 유지하고)&lt;br /&gt;4️⃣ 실행 결과와 서버의 모니터링 데이터를 다시 Controller로 보내 리포트가 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설치 및 환경 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 nGrinder의 개념과 구조를 이해했으니, 직접 설치해서 &lt;b&gt;Controller와 Agent가 어떻게 동작하는지&lt;/b&gt; 확인해 보겠습니다!  &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;290&quot; data-start=&quot;276&quot; data-ke-size=&quot;size20&quot;&gt;1️⃣ 사전 준비&lt;/h4&gt;
&lt;p data-end=&quot;318&quot; data-start=&quot;292&quot; data-ke-size=&quot;size16&quot;&gt;설치를 시작하기 전에 아래 환경을 준비해 주세요.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;요구사항&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Java&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;JDK 8 이상 (JDK 17 권장)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;OS&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux 모두 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;메모리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Controller 최소 2GB, Agent는 부하 크기에 따라 4GB 이상 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;네트워크&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Controller &amp;harr; Agent 간 통신 가능해야 함 (기본 포트 16001, 16002)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt; &lt;br /&gt;Controller와 Agent는 같은 서버에 설치해도 되지만,&lt;br /&gt;실제 대규모 테스트를 위해서는 별도의 서버(혹은 VM, Docker 컨테이너)에 분리하는 게 좋습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2️⃣ Controller 설치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nGrinder Controller는 &lt;b&gt;웹 UI를 제공하는 중앙 서버&lt;/b&gt;입니다.&lt;br /&gt;여기서 테스트를 만들고, Agent를 등록하며, 실행 결과를 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법 1. WAR 파일로 직접 실행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://github.com/naver/ngrinder/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/naver/ngrinder/releases&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1760167681433&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Releases &amp;middot; naver/ngrinder&quot; data-og-description=&quot;enterprise level performance testing solution. Contribute to naver/ngrinder development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/naver/ngrinder/releases&quot; data-og-url=&quot;https://github.com/naver/ngrinder/releases&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dimOMK/hyZJ9DlNy2/y6fqhZWX8ybFeGcynfdKO1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bqHVOB/hyZKHNky3m/owPRx8NK7JKCmhxt6j6nX0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/naver/ngrinder/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/naver/ngrinder/releases&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dimOMK/hyZJ9DlNy2/y6fqhZWX8ybFeGcynfdKO1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bqHVOB/hyZKHNky3m/owPRx8NK7JKCmhxt6j6nX0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Releases &amp;middot; naver/ngrinder&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;enterprise level performance testing solution. Contribute to naver/ngrinder development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 공식 릴리즈 페이지에서 최신 버전을 다운로드합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.31.47.png&quot; data-origin-width=&quot;2962&quot; data-origin-height=&quot;1454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fab1h/btsQ5WZ6Tvh/OiXWl6WHnNIM3R2300QYR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fab1h/btsQ5WZ6Tvh/OiXWl6WHnNIM3R2300QYR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fab1h/btsQ5WZ6Tvh/OiXWl6WHnNIM3R2300QYR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFab1h%2FbtsQ5WZ6Tvh%2FOiXWl6WHnNIM3R2300QYR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;324&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.31.47.png&quot; data-origin-width=&quot;2962&quot; data-origin-height=&quot;1454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드한 후에 아래 명령어로 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1760167725880&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java -jar ngrinder-controller-3.5.9-p1.war&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 포트는 8080이나 저는 이미 사용 중이어서 포트를 지정하여 실행하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(--port=8081)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.43.09.png&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW9hth/btsQ4W7Qg9D/aSi7UwTdn805ghuETdDZQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW9hth/btsQ4W7Qg9D/aSi7UwTdn805ghuETdDZQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW9hth/btsQ4W7Qg9D/aSi7UwTdn805ghuETdDZQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW9hth%2FbtsQ4W7Qg9D%2FaSi7UwTdn805ghuETdDZQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1992&quot; height=&quot;172&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.43.09.png&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저로 http://localhost:8081 접속하면 nGrinder 로그인 화면이 나타납니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.44.33.png&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ztt3r/btsQ5SpMdc0/MgeMsbvu7OOVwpaSBuVdBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ztt3r/btsQ5SpMdc0/MgeMsbvu7OOVwpaSBuVdBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ztt3r/btsQ5SpMdc0/MgeMsbvu7OOVwpaSBuVdBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fztt3r%2FbtsQ5SpMdc0%2FMgeMsbvu7OOVwpaSBuVdBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;293&quot; height=&quot;236&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.44.33.png&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;856&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;초기 로그인 정보&lt;br /&gt;ID: admin&lt;br /&gt;PW: admin&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1208&quot; data-start=&quot;1186&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법 2. Docker로 실행&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1242&quot; data-start=&quot;1209&quot; data-ke-size=&quot;size16&quot;&gt;Docker 환경이라면 훨씬 간단히 실행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1760168769341&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -d --name ngrinder-controller \
  --platform linux/amd64 \
  -p 8080:80 \                 # UI
  -p 16001:16001 \             # Agent 제어포트
  -p 12000-12009:12000-12009 \ # ✅ 콘솔 포트(동시 테스트용 범위)
  ngrinder/controller:3.5.9-p1&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;✅ 실행 확인&lt;br /&gt;브라우저에서 http://localhost:8080으로 접속했을 때&lt;br /&gt;nGrinder Console 화면이 보이면 Controller 설치가 완료된 것입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1483&quot; data-start=&quot;1466&quot; data-ke-size=&quot;size20&quot;&gt;3️⃣ Agent 설치&lt;/h4&gt;
&lt;p data-end=&quot;1569&quot; data-start=&quot;1485&quot; data-ke-size=&quot;size16&quot;&gt;Agent는 실제로 &lt;b&gt;부하를 발생시키는 역할&lt;/b&gt;을 합니다.&lt;br /&gt;Controller에서 테스트 명령을 받아 요청을 보내고, 결과를 다시 전달합니다.&lt;/p&gt;
&lt;p data-end=&quot;1569&quot; data-start=&quot;1485&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Controller 웹 UI &amp;rarr; 상단 메뉴 &lt;b&gt;&amp;ldquo;Agent Download&amp;rdquo;&lt;/b&gt; 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.52.59.png&quot; data-origin-width=&quot;2526&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etAqzb/btsQ6ee4TRN/9Tdc5TJACwnu3ctD4gxFA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etAqzb/btsQ6ee4TRN/9Tdc5TJACwnu3ctD4gxFA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etAqzb/btsQ6ee4TRN/9Tdc5TJACwnu3ctD4gxFA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FetAqzb%2FbtsQ6ee4TRN%2F9Tdc5TJACwnu3ctD4gxFA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;753&quot; height=&quot;219&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.52.59.png&quot; data-origin-width=&quot;2526&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 다운로드한 파일(ngrinder-agent-3.5.9-p1-localhost.tar) 압축 해제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 아래 명령으로 실행&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.56.38.png&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PmWSl/btsQ7xE3owO/U0HwCqeXQJrKXv3WM0fqU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PmWSl/btsQ7xE3owO/U0HwCqeXQJrKXv3WM0fqU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PmWSl/btsQ7xE3owO/U0HwCqeXQJrKXv3WM0fqU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPmWSl%2FbtsQ7xE3owO%2FU0HwCqeXQJrKXv3WM0fqU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;384&quot; height=&quot;263&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.56.38.png&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1760169038996&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./run_agent.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 콘솔 로그에 아래와 같은 메시지가 뜨면 성공입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.57.49.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cy61eN/btsQ6iBMYeB/rlQq8CZm0uA5kdVKOKNWJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cy61eN/btsQ6iBMYeB/rlQq8CZm0uA5kdVKOKNWJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cy61eN/btsQ6iBMYeB/rlQq8CZm0uA5kdVKOKNWJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcy61eN%2FbtsQ6iBMYeB%2FrlQq8CZm0uA5kdVKOKNWJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1238&quot; height=&quot;76&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.57.49.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller UI의 &lt;b&gt;Agent Management&lt;/b&gt; 탭에서 Agent 상태가 &lt;b&gt;Active&lt;/b&gt;로 표시되는지 확인합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.58.44.png&quot; data-origin-width=&quot;2478&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MO5V5/btsQ5kHmFZe/0az73Jj6U5RKimJ5EyT3K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MO5V5/btsQ5kHmFZe/0az73Jj6U5RKimJ5EyT3K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MO5V5/btsQ5kHmFZe/0az73Jj6U5RKimJ5EyT3K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMO5V5%2FbtsQ5kHmFZe%2F0az73Jj6U5RKimJ5EyT3K1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;780&quot; height=&quot;186&quot; data-filename=&quot;스크린샷 2025-10-11 오후 4.58.44.png&quot; data-origin-width=&quot;2478&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-end=&quot;2024&quot; data-start=&quot;1929&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;2024&quot; data-start=&quot;1931&quot; data-ke-size=&quot;size16&quot;&gt;  여러 대의 Agent를 동시에 등록할 수도 있습니다.&lt;br /&gt;테스트 규모가 클수록 Agent 수를 늘리면 분산 부하를 효율적으로 발생시킬 수 있습니다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;첫 테스트 실행 (Hello nGrinder)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller와 Agent가 모두 준비되었으니, 실제로 nGrinder에서 부하 테스트를 만들어 실행해 볼 차례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nGrinder는 웹 UI에서 바로 스크립트를 작성하고, 실행 후 결과를 시각적으로 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1️⃣ 로그인 및 기본 화면 살펴보기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 Controller 주소로 접속합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-11 오후 5.03.16.png&quot; data-origin-width=&quot;2774&quot; data-origin-height=&quot;758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cA5uTM/btsQ5km2cMr/BmOmQfN9Smok9iQIgxhKV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cA5uTM/btsQ5km2cMr/BmOmQfN9Smok9iQIgxhKV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cA5uTM/btsQ5km2cMr/BmOmQfN9Smok9iQIgxhKV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcA5uTM%2FbtsQ5km2cMr%2FBmOmQfN9Smok9iQIgxhKV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2774&quot; height=&quot;758&quot; data-filename=&quot;스크린샷 2025-10-11 오후 5.03.16.png&quot; data-origin-width=&quot;2774&quot; data-origin-height=&quot;758&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 계정으로 로그인합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;511&quot; data-start=&quot;478&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;495&quot; data-start=&quot;478&quot;&gt;ID: &lt;b&gt;admin&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;511&quot; data-start=&quot;496&quot;&gt;PW: &lt;b&gt;admin&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;608&quot; data-start=&quot;513&quot; data-ke-size=&quot;size16&quot;&gt;테스트 작성과 실행을 빠르게 할 수 있는 &lt;b&gt;&amp;ldquo;Quick Start&amp;rdquo; 영역&lt;/b&gt;을 중심으로 구성돼 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;메뉴&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Performance Test&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;실제 부하 테스트를 설정하고 실행하는 메인 메뉴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Script&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;테스트 스크립트를 작성하고 관리하는 공간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;admin ▼&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;사용자 계정 설정, Agent 관리, 시스템 설정 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Help&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;공식 문서 및 Q&amp;amp;A 링크&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-end=&quot;440&quot; data-start=&quot;421&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Quick Start 영역&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;669&quot; data-start=&quot;441&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;575&quot; data-start=&quot;441&quot;&gt;&lt;b&gt;Type URL&lt;/b&gt;: 부하를 걸고 싶은 대상 URL을 입력하는 곳이에요.&lt;br /&gt;예를 들어 https://localhost:8080/get 입력 후 &lt;b&gt;Start Test&lt;/b&gt; 클릭하면,&lt;br /&gt;자동으로 간단한 부하 테스트가 실행됩니다.&lt;/li&gt;
&lt;li data-end=&quot;669&quot; data-start=&quot;576&quot;&gt;&lt;b&gt;Groovy / Jython 선택&lt;/b&gt;: 사용할 스크립트 언어를 선택할 수 있어요.&lt;br /&gt;(기본은 Groovy, Python 스타일을 원하면 Jython 선택)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;681&quot; data-start=&quot;671&quot; data-ke-size=&quot;size20&quot;&gt;하단 영역&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;772&quot; data-start=&quot;682&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;715&quot; data-start=&quot;682&quot;&gt;&lt;b&gt;Q&amp;amp;A&lt;/b&gt;: 자주 묻는 질문과 커뮤니티 문서 링크&lt;/li&gt;
&lt;li data-end=&quot;772&quot; data-start=&quot;716&quot;&gt;&lt;b&gt;Developer Resources&lt;/b&gt;: API 문서, GitHub 링크 등 개발 참고자료&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;829&quot; data-start=&quot;812&quot; data-ke-size=&quot;size20&quot;&gt;2️⃣ 샘플 스크립트 생성&lt;/h4&gt;
&lt;p data-end=&quot;893&quot; data-start=&quot;831&quot; data-ke-size=&quot;size16&quot;&gt;처음 사용하는 경우라면,&lt;br /&gt;nGrinder는 기본 &lt;b&gt;HTTP 테스트 스크립트 샘플&lt;/b&gt;을 자동으로 제공합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1031&quot; data-start=&quot;895&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;922&quot; data-start=&quot;895&quot;&gt;상단 메뉴 &amp;rarr; &lt;b&gt;Scripts&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li data-end=&quot;953&quot; data-start=&quot;923&quot;&gt;&lt;b&gt;&amp;ldquo;Create Script&amp;rdquo;&lt;/b&gt; 버튼 클릭&lt;/li&gt;
&lt;li data-end=&quot;1014&quot; data-start=&quot;977&quot;&gt;Script Name 입력 &lt;span style=&quot;color: #333333;&quot;&gt;(예: &lt;span style=&quot;text-align: left;&quot;&gt;get&lt;/span&gt;-test)&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1031&quot; data-start=&quot;1015&quot;&gt;&amp;ldquo;Create&amp;rdquo; 클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-18 오전 11.36.51.png&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lnhG5/dJMb9PTUir7/vpGrWVsmkSx21jN1KPuka1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lnhG5/dJMb9PTUir7/vpGrWVsmkSx21jN1KPuka1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lnhG5/dJMb9PTUir7/vpGrWVsmkSx21jN1KPuka1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlnhG5%2FdJMb9PTUir7%2FvpGrWVsmkSx21jN1KPuka1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;403&quot; height=&quot;712&quot; data-filename=&quot;스크린샷 2025-10-18 오전 11.36.51.png&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성 후 확인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트 편집 화면에는 기본 템플릿 코드가 자동으로 생성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-18 오후 5.15.29.png&quot; data-origin-width=&quot;2412&quot; data-origin-height=&quot;1480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLApe7/dJMb9YwtvaI/6DX7wiYHKWPQ4TLu0aWAQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLApe7/dJMb9YwtvaI/6DX7wiYHKWPQ4TLu0aWAQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLApe7/dJMb9YwtvaI/6DX7wiYHKWPQ4TLu0aWAQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLApe7%2FdJMb9YwtvaI%2F6DX7wiYHKWPQ4TLu0aWAQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2412&quot; height=&quot;1480&quot; data-filename=&quot;스크린샷 2025-10-18 오후 5.15.29.png&quot; data-origin-width=&quot;2412&quot; data-origin-height=&quot;1480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 이런 형태로 시작해요  &lt;/p&gt;
&lt;pre id=&quot;code_1760772037918&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import static net.grinder.script.Grinder.grinder
import static org.hamcrest.Matchers.*
import static org.junit.Assert.*

import net.grinder.script.GTest
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPRequestControl
import org.ngrinder.http.HTTPResponse
import org.ngrinder.http.cookie.Cookie
import org.ngrinder.http.cookie.CookieManager

@RunWith(GrinderRunner)
class TestRunner {

    public static GTest test
    public static HTTPRequest request

    // 필요 시 헤더/쿠키 넣고 싶을 때 여기 채워쓰면 됩니다.
    public static Map&amp;lt;String, String&amp;gt; headers = [:]
    public static List&amp;lt;Cookie&amp;gt; cookies = []

    @BeforeProcess
    public static void beforeProcess() {
        // ⚠️ setUseCookies(), getCookieManager() 같은 호출 넣지 말 것!
        HTTPRequestControl.setConnectionTimeout(300000)
        test = new GTest(1, &quot;Sample HTTP Test&quot;)
        request = new HTTPRequest()
        grinder.logger.info(&quot;before process.&quot;)
    }

    @BeforeThread
    public void beforeThread() {
        test.record(this, &quot;test&quot;)
        grinder.statistics.delayReports = true
    }

    @Before
    public void before() {
        // 공통 헤더/쿠키가 있으면 여기서 적용 (비어있으면 아무 일도 안 함)
        request.setHeaders(headers)
        CookieManager.addCookies(cookies)
    }

    @Test
    public void test() {
        String targetUrl = &quot;https://httpbin.org/get&quot;  // 필요 시 수정
        HTTPResponse res = request.GET(targetUrl)

        grinder.logger.info(&quot;GET {}&quot;, targetUrl)
        grinder.logger.info(&quot;status={}&quot;, res.statusCode)

        assertThat(&quot;Response code should be 200&quot;, res.statusCode, is(200))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;218&quot; data-start=&quot;173&quot;&gt;&lt;b&gt;@BeforeProcess&lt;/b&gt; &amp;rarr; 테스트 전 초기 설정 (전역, 1회)&lt;/li&gt;
&lt;li data-end=&quot;256&quot; data-start=&quot;219&quot;&gt;&lt;b&gt;@BeforeThread&lt;/b&gt; &amp;rarr; 각 쓰레드 시작 전 1회&lt;/li&gt;
&lt;li data-end=&quot;300&quot; data-start=&quot;257&quot;&gt;&lt;b&gt;@Before&lt;/b&gt; &amp;rarr; 매 요청 전에 실행 (쿠키, 헤더 초기화 등)&lt;/li&gt;
&lt;li data-end=&quot;329&quot; data-start=&quot;301&quot;&gt;&lt;b&gt;@Test&lt;/b&gt; &amp;rarr; 실제 요청과 검증 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3️⃣ 테스트 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단 메뉴에서 &lt;b&gt;Performance Test로&lt;/b&gt; 이동하여 &lt;b&gt;Create Test&lt;/b&gt; 버튼을 클릭합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 한 화면에서 &amp;ldquo;얼마나 많은 사용자(Vuser)&amp;rdquo;가 &amp;ldquo;어떤 스크립트&amp;rdquo;를 &amp;ldquo;얼마 동안&amp;rdquo; 돌릴지를 전부 지정하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-18 오후 5.34.28.png&quot; data-origin-width=&quot;2418&quot; data-origin-height=&quot;1726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFpePD/dJMb9NaIwHK/KCiXWEFgCaqa5GeHdXTC00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFpePD/dJMb9NaIwHK/KCiXWEFgCaqa5GeHdXTC00/img.png&quot; data-alt=&quot;Performance Test&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFpePD/dJMb9NaIwHK/KCiXWEFgCaqa5GeHdXTC00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFpePD%2FdJMb9NaIwHK%2FKCiXWEFgCaqa5GeHdXTC00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2418&quot; height=&quot;1726&quot; data-filename=&quot;스크린샷 2025-10-18 오후 5.34.28.png&quot; data-origin-width=&quot;2418&quot; data-origin-height=&quot;1726&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Performance Test&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;설정 값&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;컨트롤러에 연결된 에이전트 1개&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vuser per Agent&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;각 Agent가 10명 시뮬레이션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Processes &amp;times; Threads&lt;/td&gt;
&lt;td&gt;1 &amp;times; 10&lt;/td&gt;
&lt;td&gt;10명의 가상 사용자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script&lt;/td&gt;
&lt;td&gt;get-test.groovy&lt;/td&gt;
&lt;td&gt;실행할 테스트 스크립트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duration&lt;/td&gt;
&lt;td&gt;0:01:00&lt;/td&gt;
&lt;td&gt;1분 동안 테스트 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ramp-Up&lt;/td&gt;
&lt;td&gt;비활성&lt;/td&gt;
&lt;td&gt;모든 사용자 즉시 시작&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-end=&quot;176&quot; data-start=&quot;156&quot; data-ke-size=&quot;size16&quot;&gt;➡️ 이 설정으로 &amp;ldquo;총 10명의 가상 사용자&amp;rdquo;가 &amp;ldquo;1분 동안&amp;rdquo; get-test.groovy 스크립트를 계속 실행하게 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;176&quot; data-start=&quot;156&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;기본 정보 (상단)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;353&quot; data-start=&quot;177&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;229&quot; data-start=&quot;177&quot;&gt;&lt;b&gt;Test Name&lt;/b&gt;: 테스트 이름. 리포트에 표시됩니다. (예: Get Test)&lt;/li&gt;
&lt;li data-end=&quot;308&quot; data-start=&quot;230&quot;&gt;&lt;b&gt;Description&lt;/b&gt;: 테스트 목적이나 메모를 남기는 곳.&lt;br /&gt;&amp;rarr; &amp;ldquo;nGrinder GET 요청 테스트&amp;rdquo;처럼 기록용으로 사용.&lt;/li&gt;
&lt;li data-end=&quot;353&quot; data-start=&quot;309&quot;&gt;&lt;b&gt;Tags&lt;/b&gt;: 리포트 필터링용 태그. 여러 테스트를 구분할 때 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;393&quot; data-start=&quot;360&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Test Configuration (중앙)&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;410&quot; data-start=&quot;394&quot; data-ke-size=&quot;size16&quot;&gt;여기가 핵심 설정 구간이에요.&lt;/p&gt;
&lt;p data-end=&quot;410&quot; data-start=&quot;394&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Agent&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;545&quot; data-start=&quot;425&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;545&quot; data-start=&quot;425&quot;&gt;&lt;b&gt;Agent (Max:1)&lt;/b&gt;&lt;br /&gt;&amp;rarr; 현재 연결된 부하 발생기(Agent) 수.&lt;br /&gt;&amp;rarr; 지금은 1개가 연결되어 있으니 1로 지정해야 함.&lt;br /&gt;&amp;rarr; 여러 개 연결되어 있다면 2~3개 이상 선택 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;586&quot; data-start=&quot;547&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;586&quot; data-start=&quot;549&quot; data-ke-size=&quot;size16&quot;&gt;  Agent 1개 = 실제 테스트를 실행하는 서버 1대.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;626&quot; data-start=&quot;593&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vuser per agent (가상 사용자 수)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;755&quot; data-start=&quot;627&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;755&quot; data-start=&quot;627&quot;&gt;Agent 한 대가 동시에 시뮬레이션할 &lt;b&gt;가상 사용자(Virtual User)&lt;/b&gt; 수&lt;br /&gt;&amp;rarr; 예시 설정: 10&lt;br /&gt;&amp;rarr; 즉, Agent 1개 &amp;times; 10명 = 총 10명의 가상 사용자가 동시에 API 호출을 반복.&lt;/li&gt;
&lt;li data-end=&quot;822&quot; data-start=&quot;779&quot;&gt;&lt;b&gt;Processes&lt;/b&gt;: JVM 프로세스 수 (부하 발생 프로세스 단위)&lt;/li&gt;
&lt;li data-end=&quot;916&quot; data-start=&quot;823&quot;&gt;&lt;b&gt;Threads&lt;/b&gt;: 프로세스당 쓰레드 수&lt;br /&gt;&amp;rarr; 실제 Vuser 수 = Processes &amp;times; Threads&lt;br /&gt;&amp;rarr; 지금은 1 &amp;times; 10 = 10명.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;968&quot; data-start=&quot;918&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;968&quot; data-start=&quot;920&quot; data-ke-size=&quot;size16&quot;&gt;  대부분의 경우 프로세스 1개, 쓰레드 수로 사용자 수 조절이 효율적입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;988&quot; data-start=&quot;975&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;988&quot; data-start=&quot;975&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Script&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1084&quot; data-start=&quot;989&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1084&quot; data-start=&quot;989&quot;&gt;실행할 테스트 스크립트를 선택하는 부분.&lt;br /&gt;&amp;rarr; 방금 작성한 get-test.groovy 선택!&lt;br /&gt;(파일명은 Script &amp;gt; List 탭에서 관리됩니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1114&quot; data-start=&quot;1091&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Script Resources&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1192&quot; data-start=&quot;1115&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1192&quot; data-start=&quot;1115&quot;&gt;외부 리소스(예: CSV 데이터, 이미지, JSON 파일)를 같이 업로드할 때 사용.&lt;br /&gt;&amp;rarr; 일반적인 API 테스트에는 비워둬도 OK.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1217&quot; data-start=&quot;1199&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Target Host&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1344&quot; data-start=&quot;1218&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1344&quot; data-start=&quot;1218&quot;&gt;테스트 대상 호스트의 &lt;b&gt;공통 prefix URL&lt;/b&gt;을 지정하는 곳.&lt;br /&gt;&amp;rarr; 예: http://wiremock:8080&lt;br /&gt;&amp;rarr; 스크립트 안에서 상대 경로(/api/customers)로 호출할 수 있게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;1387&quot; data-start=&quot;1346&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;1387&quot; data-start=&quot;1348&quot; data-ke-size=&quot;size16&quot;&gt;  스크립트에서 이미 전체 URL을 쓴다면 이 필드는 비워도 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1429&quot; data-start=&quot;1394&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Duration / Run Count (하단)&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1469&quot; data-start=&quot;1430&quot; data-ke-size=&quot;size16&quot;&gt;부하 테스트의 &amp;ldquo;지속 시간&amp;rdquo; 또는 &amp;ldquo;반복 횟수&amp;rdquo;를 결정하는 부분입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1633&quot; data-start=&quot;1471&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1549&quot; data-start=&quot;1471&quot;&gt;&lt;b&gt;Duration&lt;/b&gt;&lt;br /&gt;&amp;rarr; 테스트를 정해진 시간 동안 반복 실행&lt;br /&gt;&amp;rarr; 예시: 0시간 1분 0초 &amp;rarr; 1분 동안 테스트 실행&lt;/li&gt;
&lt;li data-end=&quot;1633&quot; data-start=&quot;1551&quot;&gt;&lt;b&gt;Run Count&lt;/b&gt;&lt;br /&gt;&amp;rarr; 시간 대신 &amp;ldquo;몇 번 반복할지&amp;rdquo; 설정&lt;br /&gt;&amp;rarr; 예: 1000 &amp;rarr; 각 Vuser가 1000번 요청 보냄&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;1713&quot; data-start=&quot;1635&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;1713&quot; data-start=&quot;1637&quot; data-ke-size=&quot;size16&quot;&gt;  Duration과 Run Count 중 하나만 선택합니다.&lt;br /&gt;보통은 Duration(시간 기반) 이 더 많이 쓰입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1749&quot; data-start=&quot;1720&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ramp-Up (점진적 부하 증가)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1931&quot; data-start=&quot;1750&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1816&quot; data-start=&quot;1750&quot;&gt;체크하면 부하를 천천히 늘릴 수 있습니다.&lt;br /&gt;(모든 Vuser를 한 번에 실행하지 않고, 일정 간격으로 추가)&lt;/li&gt;
&lt;li data-end=&quot;1854&quot; data-start=&quot;1817&quot;&gt;&lt;b&gt;Initial Count&lt;/b&gt;: 처음 실행할 Vuser 수&lt;/li&gt;
&lt;li data-end=&quot;1896&quot; data-start=&quot;1855&quot;&gt;&lt;b&gt;Incremental Step&lt;/b&gt;: 한 번에 늘릴 Vuser 수&lt;/li&gt;
&lt;li data-end=&quot;1931&quot; data-start=&quot;1897&quot;&gt;&lt;b&gt;Interval&lt;/b&gt;: Vuser 증가 간격(ms 단위)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;2025&quot; data-start=&quot;1933&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;2025&quot; data-start=&quot;1935&quot; data-ke-size=&quot;size16&quot;&gt;예: Initial 0, Step 1, Interval 1000 &amp;rarr; 1초마다 1명씩 추가&lt;br /&gt;즉, 점진 부하 테스트(Gradual Ramp-Up) 용도입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;설정 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;ldquo;Save &amp;amp; Start Test&amp;rdquo;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;버튼을 클릭합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1926&quot; data-start=&quot;1906&quot; data-ke-size=&quot;size20&quot;&gt;4️⃣ 테스트 실행 및 모니터링&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 실행되면 다음과 같은 화면이 나타납니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;702&quot; data-start=&quot;577&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;624&quot; data-start=&quot;577&quot;&gt;그래프(Throughput, TPS, Response Time 등)가 실시간 갱신&lt;/li&gt;
&lt;li data-end=&quot;656&quot; data-start=&quot;625&quot;&gt;왼쪽 하단 로그 영역에서는 Agent 상태 확인 가능&lt;/li&gt;
&lt;li data-end=&quot;702&quot; data-start=&quot;657&quot;&gt;우측 상단에서는 &lt;b&gt;현재 진행률&lt;/b&gt;과 &lt;b&gt;실패율&lt;/b&gt;을 모니터링할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  팁: 테스트 도중 &amp;ldquo;Stop&amp;rdquo; 버튼을 눌러 중단할 수 있으며, 테스트 로그는 자동으로 압축되어 결과 리포트에 포함됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5️⃣ 결과 리포트 확인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 완료되면 &lt;b&gt;자동으로 결과 리포트&lt;/b&gt;가 생성됩니다.&lt;br /&gt;리포트는 다음 3가지 주요 섹션으로 나뉘어 있어요.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1286&quot; data-start=&quot;875&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;975&quot; data-start=&quot;875&quot;&gt;&lt;b&gt;Summary&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;975&quot; data-start=&quot;895&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;949&quot; data-start=&quot;895&quot;&gt;테스트 이름, 실행 시간, Vuser 수, TPS(초당 트랜잭션 수), 평균 응답 시간 등&lt;/li&gt;
&lt;li data-end=&quot;975&quot; data-start=&quot;953&quot;&gt;실패율 및 에러 코드 비율 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1164&quot; data-start=&quot;977&quot;&gt;&lt;b&gt;Charts (그래프)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1164&quot; data-start=&quot;1002&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1041&quot; data-start=&quot;1002&quot;&gt;&lt;b&gt;TPS(Time Per Second)&lt;/b&gt;: 초당 요청 처리량&lt;/li&gt;
&lt;li data-end=&quot;1081&quot; data-start=&quot;1045&quot;&gt;&lt;b&gt;Mean Response Time&lt;/b&gt;: 평균 응답 시간&lt;/li&gt;
&lt;li data-end=&quot;1123&quot; data-start=&quot;1085&quot;&gt;&lt;b&gt;Peak TPS&lt;/b&gt;: 시스템이 감당 가능한 최대 처리 속도&lt;/li&gt;
&lt;li data-end=&quot;1164&quot; data-start=&quot;1127&quot;&gt;&lt;b&gt;Errors&lt;/b&gt;: HTTP 오류(4xx, 5xx) 발생 비율&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1286&quot; data-start=&quot;1166&quot;&gt;&lt;b&gt;Details (상세 데이터)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1286&quot; data-start=&quot;1195&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1216&quot; data-start=&quot;1195&quot;&gt;스크립트별, 요청별 세부 응답 시간&lt;/li&gt;
&lt;li data-end=&quot;1259&quot; data-start=&quot;1220&quot;&gt;분당 요청 수, 응답 분포(Percentile 90, 95, 99)&lt;/li&gt;
&lt;li data-end=&quot;1286&quot; data-start=&quot;1263&quot;&gt;개별 스레드의 지연 시간까지 확인 가능&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  리포트는 .zip 형태로 다운로드 가능하며, 팀 단위로 공유하거나 이전 테스트와 비교 분석할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6️⃣ 테스트 결과 해석 팁&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nGrinder 리포트는 단순한 수치 이상의 정보를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 결과를 해석할 때 도움이 되는 핵심 포인트입니다  &lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 114px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;의미&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;해석 포인트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;TPS (Transaction Per Second)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;초당 요청 처리량&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;높을수록 처리 효율이 좋지만, 응답시간과 함께 봐야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;Mean Response Time (평균 응답시간)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;요청에 대한 평균 처리 시간&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;급격히 상승하면 서버 부하 또는 병목 가능성 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;Error %&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;실패한 요청 비율&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;1% 이상이면 네트워크, 코드 예외, 타임아웃 등을 점검 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;Vuser Stability&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;가상 유저의 응답 일관성&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;일부 유저만 느려진다면 특정 세션 또는 DB 락 이슈 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;95% Line&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;상위 5% 요청의 응답시간&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;SLA 기준을 판단할 때 자주 활용 (예: 95% 응답이 2초 이내)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;2086&quot; data-start=&quot;1892&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;1906&quot; data-start=&quot;1894&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;예시 해석&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TPS가 일정 수준 이상에서 평탄해지고 Response Time이 급상승한다면, 해당 시점이 시스템의 처리 한계 지점(Throughput Bottleneck)입니다.&lt;br /&gt;Error가 특정 시점부터 증가한다면, DB 커넥션 부족&lt;span style=&quot;letter-spacing: 0px; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt; 또는 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;Thread Pool&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt; 한계일 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nGrinder는 단순한 부하 테스트 도구를 넘어, &lt;b&gt;테스트를 운영 프로세스에 자연스럽게 녹일 수 있는 플랫폼&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;832&quot; data-start=&quot;764&quot; data-ke-size=&quot;size16&quot;&gt;한 번 환경만 세팅해 두면, 클릭 몇 번으로 팀 전체가 동일한 테스트를 재사용하고 결과를 한눈에 비교할 수 있죠.&lt;/p&gt;
&lt;p data-end=&quot;904&quot; data-start=&quot;839&quot; data-ke-size=&quot;size16&quot;&gt;앞으로는 Jenkins나 GitLab CI와 연동해 정기적인 성능 테스트 자동화까지 진행해 볼 계획입니다.  &lt;/p&gt;</description>
      <category>개발끄적</category>
      <category>api 부하테스트</category>
      <category>ngrinder</category>
      <category>TPS</category>
      <category>부하테스트</category>
      <category>성능 테스트</category>
      <category>엔그라인더</category>
      <author>누구세연</author>
      <guid isPermaLink="true">https://sy-develop-note.tistory.com/172</guid>
      <comments>https://sy-develop-note.tistory.com/172#entry172comment</comments>
      <pubDate>Mon, 20 Oct 2025 23:20:20 +0900</pubDate>
    </item>
    <item>
      <title>Apache JMeter: 설치부터 부하 테스트까지</title>
      <link>https://sy-develop-note.tistory.com/171</link>
      <description>&lt;p data-end=&quot;615&quot; data-start=&quot;427&quot; data-ke-size=&quot;size16&quot;&gt;안정적이고 확장 가능한 서비스를 운영하기 위해서는 기능 구현뿐 아니라 성능 및 부하 테스트가 필수적입니다.&lt;/p&gt;
&lt;p data-end=&quot;615&quot; data-start=&quot;427&quot; data-ke-size=&quot;size16&quot;&gt;특히 API가 동시에 많은 요청을 받을 때 잘 버텨주는지 확인하지 않으면, 실제 운영 환경에서 큰 장애로 이어질 수 있죠.&lt;br /&gt;대표적인 오픈소스 부하 테스트 도구로는 Apache JMeter와 nGrinder가 있으며, 본 글에서는 상대적으로 단순하고 접근성이 높은 JMeter를 먼저 살펴보고 간단한 실습을 진행하겠습니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-18 오후 11.44.31.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Irv8V/btsQFDM0E7l/v6ErikkEGj4fhUqjIaLt3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Irv8V/btsQFDM0E7l/v6ErikkEGj4fhUqjIaLt3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Irv8V/btsQFDM0E7l/v6ErikkEGj4fhUqjIaLt3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIrv8V%2FbtsQFDM0E7l%2Fv6ErikkEGj4fhUqjIaLt3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;418&quot; height=&quot;133&quot; data-filename=&quot;스크린샷 2025-09-18 오후 11.44.31.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JMeter란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Apache JMeter&lt;/b&gt;는 아파치 소프트웨어 재단에서 개발한 &lt;b&gt;오픈소스 부하&amp;middot;성능 테스트 툴&lt;/b&gt;입니다.&lt;br /&gt;웹 애플리케이션뿐 아니라 다양한 네트워크 서비스의 응답 성능, 처리량, 안정성을 점검할 수 있도록 설계되어 있어요.&lt;/p&gt;
&lt;p data-end=&quot;488&quot; data-start=&quot;292&quot; data-ke-size=&quot;size16&quot;&gt;원래는 &lt;b&gt;웹 애플리케이션 테스트용&lt;/b&gt;으로 시작했지만, 지금은 훨씬 더 범용적으로 진화했습니다.&lt;br /&gt;HTTP/HTTPS 요청은 물론이고, &lt;b&gt;JDBC, FTP, LDAP, SOAP, JMS, SMTP/POP3/IMAP 메일 프로토콜&lt;/b&gt;까지 지원하기 때문에&lt;br /&gt;&amp;ldquo;웹 서버 성능 측정 도구&amp;rdquo;를 넘어 &amp;ldquo;범용 성능 테스트 프레임워크&amp;rdquo;라고 할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;488&quot; data-start=&quot;292&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;488&quot; data-start=&quot;292&quot; data-ke-size=&quot;size20&quot;&gt;주요 특징&amp;nbsp;&lt;/h4&gt;
&lt;p data-end=&quot;535&quot; data-start=&quot;516&quot; data-ke-size=&quot;size18&quot;&gt;1) 100% Java 기반&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;722&quot; data-start=&quot;536&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;598&quot; data-start=&quot;536&quot;&gt;JMeter는 Java로 구현되어 있어 &lt;b&gt;운영체제에 구애받지 않고 JVM만 있으면 실행 가능&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li data-end=&quot;666&quot; data-start=&quot;599&quot;&gt;Windows, macOS, Linux 모두 지원되며, 설치도 단순히 압축을 풀고 실행 스크립트를 돌리면 됩니다.&lt;/li&gt;
&lt;li data-end=&quot;722&quot; data-start=&quot;667&quot;&gt;Java 기반이라 &lt;b&gt;멀티플랫폼 호환성&lt;/b&gt;이 뛰어나고, &lt;b&gt;라이브러리 확장성&lt;/b&gt;도 높습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;740&quot; data-start=&quot;724&quot; data-ke-size=&quot;size18&quot;&gt;2) GUI 환경 제공&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1011&quot; data-start=&quot;741&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;798&quot; data-start=&quot;741&quot;&gt;jmeter.sh 또는 jmeter.bat을 실행하면 Swing 기반의 GUI가 뜹니다.&lt;/li&gt;
&lt;li data-end=&quot;859&quot; data-start=&quot;799&quot;&gt;시나리오 구성을 &lt;b&gt;드래그 앤 드롭&lt;/b&gt;으로 할 수 있어 &lt;b&gt;비개발자도 직관적으로 학습 가능&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li data-end=&quot;941&quot; data-start=&quot;860&quot;&gt;예: &amp;ldquo;Thread Group&amp;rdquo; &amp;rarr; &amp;ldquo;HTTP Request&amp;rdquo; &amp;rarr; &amp;ldquo;Listener(결과보기)&amp;rdquo;를 추가하는 것만으로도 간단히 테스트 가능.&lt;/li&gt;
&lt;li data-end=&quot;1011&quot; data-start=&quot;942&quot;&gt;GUI뿐만 아니라 &lt;b&gt;CLI(커맨드라인) 모드&lt;/b&gt;도 지원하여, CI/CD 파이프라인이나 서버 환경에서도 자동화 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1048&quot; data-start=&quot;1013&quot; data-ke-size=&quot;size18&quot;&gt;3) 스레드 기반 가상 사용자 (Virtual User)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1276&quot; data-start=&quot;1049&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1108&quot; data-start=&quot;1049&quot;&gt;JMeter의 부하 발생 원리는 &lt;b&gt;스레드(Thread)&lt;/b&gt;를 이용한 가상 사용자 시뮬레이션이에요.&lt;/li&gt;
&lt;li data-end=&quot;1206&quot; data-start=&quot;1109&quot;&gt;Thread Group 설정에서 가상 사용자의 수(스레드 수), Ramp-up 시간(얼마나 점진적으로 늘릴지), Loop 횟수(몇 번 반복할지)를 지정할 수 있습니다.&lt;/li&gt;
&lt;li data-end=&quot;1276&quot; data-start=&quot;1207&quot;&gt;이를 통해 &lt;b&gt;&amp;ldquo;100명이 동시에 로그인하면 서버가 어떻게 버틸까?&amp;rdquo;&lt;/b&gt; 같은 시나리오를 손쉽게 구현할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1295&quot; data-start=&quot;1278&quot; data-ke-size=&quot;size18&quot;&gt;4) 플러그인 에코시스템&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1581&quot; data-start=&quot;1296&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1354&quot; data-start=&quot;1296&quot;&gt;JMeter는 &lt;b&gt;풍부한 플러그인 생태계&lt;/b&gt;를 갖추고 있어, 기본 기능 이상으로 확장 가능합니다.&lt;/li&gt;
&lt;li data-end=&quot;1507&quot; data-start=&quot;1355&quot;&gt;예시:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1507&quot; data-start=&quot;1365&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1442&quot; data-start=&quot;1365&quot;&gt;다양한 &lt;b&gt;리포트/그래프 뷰어&lt;/b&gt; (Response Times Over Time, Throughput Shaping Timer 등)&lt;/li&gt;
&lt;li data-end=&quot;1507&quot; data-start=&quot;1445&quot;&gt;&lt;b&gt;고급 제어기(Controller)&lt;/b&gt;, &lt;b&gt;추가 샘플러(Sampler)&lt;/b&gt;, &lt;b&gt;모니터링 플러그인&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1581&quot; data-start=&quot;1508&quot;&gt;덕분에 단순히 TPS/응답시간만 보는 게 아니라, &lt;b&gt;부하 패턴 제어, 응답 본문 검증, 실시간 시각화&lt;/b&gt;까지 가능해집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1601&quot; data-start=&quot;1583&quot; data-ke-size=&quot;size18&quot;&gt;5) 다양한 프로토콜 지원&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1776&quot; data-start=&quot;1602&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1642&quot; data-start=&quot;1602&quot;&gt;가장 많이 쓰이는 건 HTTP/HTTPS 기반 API 테스트지만,&lt;/li&gt;
&lt;li data-end=&quot;1721&quot; data-start=&quot;1643&quot;&gt;DB 연결(JDBC), 메시징(JMS), 메일(SMTP/POP3/IMAP), FTP 업로드/다운로드, LDAP 인증 등도 지원합니다.&lt;/li&gt;
&lt;li data-end=&quot;1776&quot; data-start=&quot;1722&quot;&gt;즉, &lt;b&gt;엔드투엔드(End-to-End) 시나리오&lt;/b&gt;를 하나의 툴에서 구성할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;1805&quot; data-start=&quot;1796&quot; data-ke-size=&quot;size16&quot;&gt;한마디로 JMeter는 &lt;b&gt;쉽게 시작할 수 있는 GUI 도구&lt;/b&gt;이면서 &lt;b&gt;멀티프로토콜 지원 &amp;amp; 플러그인 확장성&lt;/b&gt;으로 &lt;b&gt;범용 성능 테스트 플랫폼&lt;/b&gt;으로 자리 잡은 툴이에요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JMeter 설치 &amp;amp; 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;요구 사항&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;296&quot; data-start=&quot;168&quot;&gt;&lt;b&gt;Java Development Kit(JDK)&lt;/b&gt;: 최소 JDK 8 이상, 권장 JDK 11 이상&lt;br /&gt;&amp;rarr; JMeter는 100% Java 기반이라 &lt;b&gt;JDK 필수&lt;/b&gt;. java -version으로 버전 확인 가능&lt;/li&gt;
&lt;li data-end=&quot;352&quot; data-start=&quot;297&quot;&gt;&lt;b&gt;운영체제&lt;/b&gt;: Windows, macOS, Linux 모두 지원 (JVM만 있으면 OK)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;다운로드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;454&quot; data-start=&quot;372&quot;&gt;공식 사이트 접속   &lt;a href=&quot;https://jmeter.apache.org/download_jmeter.cgi&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jmeter.apache.org/download_jmeter.cgi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-25 오후 3.47.04.png&quot; data-origin-width=&quot;2748&quot; data-origin-height=&quot;1788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB9bBD/btsQQlkF3kV/3lTyg5kiZKJXspAKywP4dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB9bBD/btsQQlkF3kV/3lTyg5kiZKJXspAKywP4dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB9bBD/btsQQlkF3kV/3lTyg5kiZKJXspAKywP4dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB9bBD%2FbtsQQlkF3kV%2F3lTyg5kiZKJXspAKywP4dK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;659&quot; height=&quot;1788&quot; data-filename=&quot;스크린샷 2025-09-25 오후 3.47.04.png&quot; data-origin-width=&quot;2748&quot; data-origin-height=&quot;1788&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;525&quot; data-start=&quot;455&quot;&gt;최신 버전의 &lt;b&gt;Binary zip (압축 파일)&lt;/b&gt; 다운로드 ( 저는 apache-jmeter-5.6.3.zip로 다운로드하였습니다! )&lt;/li&gt;
&lt;li data-end=&quot;525&quot; data-start=&quot;455&quot;&gt;원하는 위치에 압축을 해제합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1758783120341&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;~/tools/apache-jmeter-5.6.3/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GUI 모드 실행로 실행해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1758783217797&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd ~/tools/apache-jmeter-5.6.3/bin
./jmeter.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-25 오후 4.49.55.png&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjrBlT/btsQN6bcEn7/4AviK0doiNVhRpHGuVn2w0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjrBlT/btsQN6bcEn7/4AviK0doiNVhRpHGuVn2w0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjrBlT/btsQN6bcEn7/4AviK0doiNVhRpHGuVn2w0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjrBlT%2FbtsQN6bcEn7%2F4AviK0doiNVhRpHGuVn2w0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;74&quot; data-filename=&quot;스크린샷 2025-09-25 오후 4.49.55.png&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 명령어를 실행하면 Swing 기반 GUI가 열리면서 &lt;b&gt;Test Plan&lt;/b&gt; 화면이 나타납니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-25 오후 4.51.12.png&quot; data-origin-width=&quot;2766&quot; data-origin-height=&quot;1794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFP8oL/btsQP0HZsEx/Fh0rDmTyS7cTLtC9xhvm8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFP8oL/btsQP0HZsEx/Fh0rDmTyS7cTLtC9xhvm8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFP8oL/btsQP0HZsEx/Fh0rDmTyS7cTLtC9xhvm8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFP8oL%2FbtsQP0HZsEx%2FFh0rDmTyS7cTLtC9xhvm8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;737&quot; height=&quot;1794&quot; data-filename=&quot;스크린샷 2025-09-25 오후 4.51.12.png&quot; data-origin-width=&quot;2766&quot; data-origin-height=&quot;1794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 환경이나 CI/CD 파이프라인에서 자동화할 때는 CLI 모드를 사용할 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1758786721432&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./jmeter.sh -n -t test-plan.jmx -l result.jtl -e -o ./report&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1080&quot; data-start=&quot;1057&quot;&gt;-n: Non-GUI 모드 실행&lt;/li&gt;
&lt;li data-end=&quot;1115&quot; data-start=&quot;1083&quot;&gt;-t: 실행할 테스트 플랜 파일 (.jmx)&lt;/li&gt;
&lt;li data-end=&quot;1145&quot; data-start=&quot;1118&quot;&gt;-l: 결과 저장 파일 (.jtl)&lt;/li&gt;
&lt;li data-end=&quot;1178&quot; data-start=&quot;1148&quot;&gt;-e: 테스트 종료 후 HTML 리포트 생성&lt;/li&gt;
&lt;li data-end=&quot;1202&quot; data-start=&quot;1181&quot;&gt;-o: 리포트 출력 디렉토리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 완료 후 ./report/index.html을 열면 브라우저에서 결과 대시보드 확인 가능!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;부하 테스트 시나리오 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실제 API를 대상으로 부하를 걸어보는 테스트를 만들어볼 차례입니다!_!&lt;/p&gt;
&lt;h4 data-end=&quot;309&quot; data-start=&quot;278&quot; data-ke-size=&quot;size20&quot;&gt;Thread Group(스레드 그룹) 추가&lt;/h4&gt;
&lt;p data-end=&quot;341&quot; data-start=&quot;311&quot; data-ke-size=&quot;size16&quot;&gt;부하를 발생시킬 &amp;ldquo;가상 사용자&amp;rdquo;를 정의하는 단계입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;435&quot; data-start=&quot;343&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;411&quot; data-start=&quot;343&quot;&gt;좌측 트리에서 &lt;b&gt;Test Plan &amp;rarr; Add &amp;rarr; Threads (Users) &amp;rarr; Thread Group&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li data-end=&quot;435&quot; data-start=&quot;412&quot;&gt;아래와 같이 주요 옵션을 설정합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.4884%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 61.8605%;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;width: 9.65116%;&quot;&gt;예시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.4884%;&quot;&gt;Number of Threads (users)&lt;/td&gt;
&lt;td style=&quot;width: 61.8605%;&quot;&gt;동시에 접속할 가상 사용자 수&lt;/td&gt;
&lt;td style=&quot;width: 9.65116%;&quot;&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.4884%;&quot;&gt;Ramp-up period (seconds)&lt;/td&gt;
&lt;td style=&quot;width: 61.8605%;&quot;&gt;사용자를 몇 초에 걸쳐 늘릴지&lt;/td&gt;
&lt;td style=&quot;width: 9.65116%;&quot;&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.4884%;&quot;&gt;Loop Count&lt;/td&gt;
&lt;td style=&quot;width: 61.8605%;&quot;&gt;각 사용자가 요청을 반복할 횟수&lt;/td&gt;
&lt;td style=&quot;width: 9.65116%;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L0hLF/btsQ2gdInlB/fLZC4xDsebhuU7283OYsEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L0hLF/btsQ2gdInlB/fLZC4xDsebhuU7283OYsEk/img.png&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;704&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;51.94&quot; id=&quot;kEditorPhotosEditingImage-4&quot; style=&quot;width: 51.3316%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L0hLF/btsQ2gdInlB/fLZC4xDsebhuU7283OYsEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL0hLF%2FbtsQ2gdInlB%2FfLZC4xDsebhuU7283OYsEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1638&quot; height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo5lUP/btsQ3ni3ItR/MnNioewQL1aNQfYtJXkagk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo5lUP/btsQ3ni3ItR/MnNioewQL1aNQfYtJXkagk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;822&quot; data-filename=&quot;스크린샷 2025-10-04 오후 12.39.02.png&quot; style=&quot;width: 47.5056%;&quot; data-widthpercent=&quot;48.06&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo5lUP/btsQ3ni3ItR/MnNioewQL1aNQfYtJXkagk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo5lUP%2FbtsQ3ni3ItR%2FMnNioewQL1aNQfYtJXkagk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1770&quot; height=&quot;822&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  예: 50명의 사용자가 10초 동안 점진적으로 늘어나면서 총 5번씩 요청 &amp;rarr; 총 250회 요청 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;756&quot; data-start=&quot;725&quot; data-ke-size=&quot;size20&quot;&gt;HTTP Request(요청) 샘플러 추가&lt;/h4&gt;
&lt;p data-end=&quot;778&quot; data-start=&quot;758&quot; data-ke-size=&quot;size16&quot;&gt;테스트할 API를 지정하는 단계예요.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;847&quot; data-start=&quot;780&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;832&quot; data-start=&quot;780&quot;&gt;Thread Group &amp;rarr; &lt;b&gt;Add &amp;rarr; Sampler &amp;rarr; HTTP Request&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;847&quot; data-start=&quot;833&quot;&gt;아래처럼 설정합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 76px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 26.6279%; height: 19px;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 47.6744%; height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;width: 25.5814%; height: 19px;&quot;&gt;예시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 26.6279%; height: 19px;&quot;&gt;Server Name or IP&lt;/td&gt;
&lt;td style=&quot;width: 47.6744%; height: 19px;&quot;&gt;테스트 대상 도메인&lt;/td&gt;
&lt;td style=&quot;width: 25.5814%; height: 19px;&quot;&gt;sy-develop-note.tistory.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 26.6279%; height: 19px;&quot;&gt;Path&lt;/td&gt;
&lt;td style=&quot;width: 47.6744%; height: 19px;&quot;&gt;API 경로&lt;/td&gt;
&lt;td style=&quot;width: 25.5814%; height: 19px;&quot;&gt;/get&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 26.6279%; height: 19px;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 47.6744%; height: 19px;&quot;&gt;요청 방식&lt;/td&gt;
&lt;td style=&quot;width: 25.5814%; height: 19px;&quot;&gt;GET&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(저는 로컬에서 실행 중인 Spring Boot API를 대상으로 테스트를 진행했기 때문에 `Server Name`에 `localhost`, `Port Number`에는 애플리케이션이 실행 중인 포트인 `8080`을 입력했습니다.  )&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beD7o8/btsQ3ZB1F4i/bxRekBsaQbSaaGiSc4DOg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beD7o8/btsQ3ZB1F4i/bxRekBsaQbSaaGiSc4DOg0/img.png&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;1046&quot; data-is-animation=&quot;false&quot; style=&quot;width: 29.4322%; margin-right: 10px;&quot; data-widthpercent=&quot;29.78&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beD7o8/btsQ3ZB1F4i/bxRekBsaQbSaaGiSc4DOg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeD7o8%2FbtsQ3ZB1F4i%2FbxRekBsaQbSaaGiSc4DOg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1610&quot; height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qpIwn/btsQ2lGnjwt/MeKb0D5kn10ChgEeFs51F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qpIwn/btsQ2lGnjwt/MeKb0D5kn10ChgEeFs51F1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;2744&quot; data-origin-height=&quot;756&quot; data-filename=&quot;스크린샷 2025-10-04 오후 12.52.04.png&quot; style=&quot;width: 69.405%;&quot; data-widthpercent=&quot;70.22&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qpIwn/btsQ2lGnjwt/MeKb0D5kn10ChgEeFs51F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqpIwn%2FbtsQ2lGnjwt%2FMeKb0D5kn10ChgEeFs51F1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2744&quot; height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1063&quot; data-start=&quot;1030&quot; data-ke-size=&quot;size20&quot;&gt;Listener(리스너) 추가 &amp;ndash; 결과 확인용&lt;/h4&gt;
&lt;p data-end=&quot;1097&quot; data-start=&quot;1065&quot; data-ke-size=&quot;size16&quot;&gt;테스트 결과를 눈으로 확인하려면 리스너를 추가해야 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1296&quot; data-start=&quot;1099&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1137&quot; data-start=&quot;1099&quot;&gt;Thread Group &amp;rarr; &lt;b&gt;Add &amp;rarr; Listener&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1296&quot; data-start=&quot;1138&quot;&gt;추천 리스너:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1296&quot; data-start=&quot;1152&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1196&quot; data-start=&quot;1152&quot;&gt;✅ &lt;b&gt;View Results Tree&lt;/b&gt; &amp;ndash; 요청/응답 상세 로그 확인&lt;/li&gt;
&lt;li data-end=&quot;1249&quot; data-start=&quot;1200&quot;&gt;  &lt;b&gt;Summary Report&lt;/b&gt; &amp;ndash; 평균 응답 시간, TPS, 에러율 확인&lt;/li&gt;
&lt;li data-end=&quot;1296&quot; data-start=&quot;1253&quot;&gt;  &lt;b&gt;View Results in Graph&lt;/b&gt; &amp;ndash; 실시간 성능 그래프&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cR4BB3/btsQ1Vucu1c/61ldrW3CSAKHUtBUWtwyWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cR4BB3/btsQ1Vucu1c/61ldrW3CSAKHUtBUWtwyWK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;984&quot; data-filename=&quot;스크린샷 2025-10-04 오후 12.53.52.png&quot; style=&quot;width: 51.8051%; margin-right: 10px;&quot; data-widthpercent=&quot;52.41&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cR4BB3/btsQ1Vucu1c/61ldrW3CSAKHUtBUWtwyWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcR4BB3%2FbtsQ1Vucu1c%2F61ldrW3CSAKHUtBUWtwyWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1370&quot; height=&quot;984&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drSimS/btsQ2t49slc/mDexY8kOB97JgYHIaHnAr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drSimS/btsQ2t49slc/mDexY8kOB97JgYHIaHnAr1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;500&quot; data-filename=&quot;스크린샷 2025-10-04 오후 12.54.37.png&quot; style=&quot;width: 47.0321%;&quot; data-widthpercent=&quot;47.59&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drSimS/btsQ2t49slc/mDexY8kOB97JgYHIaHnAr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrSimS%2FbtsQ2t49slc%2FmDexY8kOB97JgYHIaHnAr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;632&quot; height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1356&quot; data-start=&quot;1338&quot; data-ke-size=&quot;size20&quot;&gt;실행 및 결과 해석&lt;/h4&gt;
&lt;p data-end=&quot;1437&quot; data-start=&quot;1358&quot; data-ke-size=&quot;size16&quot;&gt;이제 상단의 ▶ 버튼을 눌러 테스트를 실행해 봅시다.&lt;br /&gt;테스트가 끝나면 Summary Report에서 다음과 같은 지표를 확인할 수 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.8605%;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 83.0233%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.8605%;&quot;&gt;&lt;b&gt;Samples&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 83.0233%;&quot;&gt;총 요청 수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.8605%;&quot;&gt;&lt;b&gt;Average&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 83.0233%;&quot;&gt;평균 응답 시간(ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.8605%;&quot;&gt;&lt;b&gt;Throughput&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 83.0233%;&quot;&gt;초당 처리 요청 수 (TPS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.8605%;&quot;&gt;&lt;b&gt;Error %&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 83.0233%;&quot;&gt;실패율&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-04 오후 12.57.50.png&quot; data-origin-width=&quot;2752&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxGhz6/btsQ2arzro5/odjBnc1pwGp8sd3BUetMB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxGhz6/btsQ2arzro5/odjBnc1pwGp8sd3BUetMB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxGhz6/btsQ2arzro5/odjBnc1pwGp8sd3BUetMB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxGhz6%2FbtsQ2arzro5%2FodjBnc1pwGp8sd3BUetMB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;856&quot; height=&quot;191&quot; data-filename=&quot;스크린샷 2025-10-04 오후 12.57.50.png&quot; data-origin-width=&quot;2752&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;116&quot; data-start=&quot;77&quot; data-ke-size=&quot;size16&quot;&gt;테스트를 실행한 결과, 아래와 같은 성능 지표를 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;132&quot; data-start=&quot;118&quot; data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;테스트 설정&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;174&quot; data-start=&quot;133&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;174&quot; data-start=&quot;133&quot;&gt;50 Threads &amp;times; 5 Loop &amp;rarr; &lt;b&gt;총 250개의 요청&lt;/b&gt; 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;189&quot; data-start=&quot;176&quot; data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;결과 요약&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;값&lt;/td&gt;
&lt;td&gt;해석&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Samples&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;td&gt;총 250회의 요청이 실행되었음을 의미합니다. (50명의 가상 사용자가 각각 5번 요청)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Average (평균 응답 시간)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2 ms&lt;/td&gt;
&lt;td&gt;서버가 요청에 응답하는 데 걸린 평균 시간이 약 &lt;b&gt;2ms&lt;/b&gt;로 매우 빠릅니다. 즉, 요청을 거의 즉시 처리하고 있다는 뜻이에요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Min / Max (최소&amp;middot;최대 응답 시간)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1 ms / 18 ms&lt;/td&gt;
&lt;td&gt;가장 빠른 응답은 1ms, 가장 느린 응답도 18ms 수준으로 전체적으로 안정적입니다. 큰 편차 없이 일관된 속도를 유지하고 있어요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Std. Dev (표준편차)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1.97&lt;/td&gt;
&lt;td&gt;응답 시간 변동폭이 작다는 뜻으로, 성능이 일정하게 유지되고 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Error % (오류율)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0.00%&lt;/td&gt;
&lt;td&gt;250건 요청 모두 성공했으며 실패한 요청이 없습니다. 서버가 모든 요청을 정상적으로 처리했다는 뜻이에요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Throughput (TPS)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;25.4/sec&lt;/td&gt;
&lt;td&gt;초당 약 &lt;b&gt;25.4건의 요청&lt;/b&gt;을 처리했습니다. 이 수치는 서버의 기본 처리 능력을 보여주며, 현재 설정에서 서버가 &lt;b&gt;초당 약 25건&lt;/b&gt;의 요청을 안정적으로 소화 가능하다는 뜻이에요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Received KB/sec / Sent KB/sec&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;20.61 / 3.10&lt;/td&gt;
&lt;td&gt;초당 약 20.61KB를 수신하고 3.10KB를 전송하고 있다는 뜻으로, 네트워크 사용량이 크지 않은 테스트 시나리오임을 알 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Avg. Bytes (평균 바이트 수)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;830&lt;/td&gt;
&lt;td&gt;응답 본문의 평균 크기가 약 830바이트라는 뜻이에요.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;TPS(Throughput Per Second)는 서버가 초당 처리할 수 있는 요청 수를 의미합니다. 이 수치를 기준으로 서버의 최대 처리량(capacity)을 가늠할 수 있으며, 실제 운영 환경에서 예상되는 트래픽과 비교하여 여유가 있는지 판단하는 지표로 활용할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✏️ 마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 JMeter를 활용하면 별다른 설정 없이도 간단하게 API 부하 테스트를 진행할 수 있습니다.&lt;br /&gt;특히 GUI 환경에서 몇 가지 옵션만 지정하면 &lt;b&gt;가상의 사용자를 만들어 실제 트래픽 상황을 손쉽게 시뮬레이션&lt;/b&gt;할 수 있다는 점이 큰 장점이에요.&lt;br /&gt;&amp;ldquo;성능 테스트&amp;rdquo;라고 하면 어렵고 복잡할 것 같지만, JMeter는 입문자도 금방 익힐 수 있을 만큼 직관적이라 &lt;b&gt;빠르게 성능 테스트를 시작해 보기 좋은 툴&lt;/b&gt;이라고 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 JMeter는 단일 머신에서 실행된다는 특성상 &lt;b&gt;대규모 트래픽 테스트나 분산 환경 테스트&lt;/b&gt;에는 한계가 있어요.&lt;br /&gt;실제 운영 환경과 가까운 조건에서 테스트하려면 더 강력한 도구가 필요하죠.&lt;br /&gt;그래서 다음 글에서는 분산 부하 테스트에 특화된 오픈소스 툴인 &amp;ldquo;nGrinder&amp;rdquo;를 활용해 더 발전된 부하 테스트를 진행하는 방법을 다뤄보려 합니다.  &amp;zwj; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발끄적</category>
      <category>Apache JMete</category>
      <category>api 부하테스트</category>
      <category>JMeter</category>
      <category>JMeter란?</category>
      <category>ngrinder</category>
      <category>Throughput</category>
      <category>TPS</category>
      <category>부하 테스트 시나리오 만들기</category>
      <category>부하테스트</category>
      <category>성능테스트</category>
      <author>누구세연</author>
      <guid isPermaLink="true">https://sy-develop-note.tistory.com/171</guid>
      <comments>https://sy-develop-note.tistory.com/171#entry171comment</comments>
      <pubDate>Sat, 4 Oct 2025 13:10:39 +0900</pubDate>
    </item>
    <item>
      <title>공식 문서 기반으로 이해하는 n8n 핵심 개념</title>
      <link>https://sy-develop-note.tistory.com/170</link>
      <description>&lt;p data-end=&quot;145&quot; data-start=&quot;133&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lEtiG/btsQbyePohV/D9j0YB1YVxcwVeCu1KrDQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lEtiG/btsQbyePohV/D9j0YB1YVxcwVeCu1KrDQK/img.png&quot; data-alt=&quot;n8n&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lEtiG/btsQbyePohV/D9j0YB1YVxcwVeCu1KrDQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlEtiG%2FbtsQbyePohV%2FD9j0YB1YVxcwVeCu1KrDQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;601&quot; height=&quot;220&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;n8n&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-end=&quot;145&quot; data-start=&quot;133&quot; data-ke-size=&quot;size23&quot;&gt;  n8n이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;n8n&lt;/b&gt;은 다양한 서비스와 시스템을 서로 연결해 주는 &lt;b&gt;워크플로우 자동화 툴&lt;/b&gt;입니다.&lt;br /&gt;쉽게 말해, &amp;ldquo;특정 이벤트가 발생하면 정해둔 작업들을 순서대로 실행해 주는 자동화 엔진&amp;rdquo;이에요.&lt;/p&gt;
&lt;p data-end=&quot;251&quot; data-start=&quot;188&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 SaaS 자동화 툴로는 &lt;b&gt;Zapier&lt;/b&gt;가 있지만, n8n은 몇 가지 큰 차별점을 가지고 있습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;293&quot; data-start=&quot;259&quot; data-ke-style=&quot;style2&quot;&gt;  개발자 친화적인 노코드/로우코드 자동화 플랫폼&lt;/blockquote&gt;
&lt;p data-end=&quot;271&quot; data-start=&quot;258&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;271&quot; data-start=&quot;258&quot; data-ke-size=&quot;size23&quot;&gt;  n8n의 특징&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1025&quot; data-start=&quot;273&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;458&quot; data-start=&quot;273&quot;&gt;&lt;b&gt;오픈소스 &amp;amp; 셀프호스팅&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;458&quot; data-start=&quot;298&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;359&quot; data-start=&quot;298&quot;&gt;Zapier, Make 같은 툴은 클라우드 서비스 기반이라 사용량이 늘어나면 요금이 빠르게 올라갑니다.&lt;/li&gt;
&lt;li data-end=&quot;458&quot; data-start=&quot;363&quot;&gt;n8n은 &lt;b&gt;완전히 오픈소스&lt;/b&gt;이고, 직접 서버(Docker, Kubernetes 등)에 설치해서 운영할 수 있어 &lt;b&gt;비용과 보안 측면에서 자유도가 높습니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;608&quot; data-start=&quot;460&quot;&gt;&lt;b&gt;Node.js 기반 아키텍처&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;608&quot; data-start=&quot;488&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;553&quot; data-start=&quot;488&quot;&gt;n8n은 Node.js 위에서 돌아가며, JavaScript/TypeScript 친화적인 확장성을 제공합니다.&lt;/li&gt;
&lt;li data-end=&quot;608&quot; data-start=&quot;557&quot;&gt;필요하다면 직접 &lt;b&gt;커스텀 노드(Node)&lt;/b&gt;를 만들어 기능을 추가할 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;825&quot; data-start=&quot;610&quot;&gt;&lt;b&gt;풍부한 통합 지원 (300+ 노드)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;825&quot; data-start=&quot;642&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;677&quot; data-start=&quot;642&quot;&gt;Slack, Discord, Telegram 같은 메신저&lt;/li&gt;
&lt;li data-end=&quot;717&quot; data-start=&quot;681&quot;&gt;PostgreSQL, MySQL, MongoDB 같은 DB&lt;/li&gt;
&lt;li data-end=&quot;775&quot; data-start=&quot;721&quot;&gt;AWS, GCP, GitHub, Jira, Sentry 등 다양한 SaaS/DevOps 툴&lt;br /&gt;&amp;rarr; 이미 준비된 노드만 연결하면 바로 자동화 워크플로우를 만들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1025&quot; data-start=&quot;827&quot;&gt;&lt;b&gt;노코드/로우코드 방식&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1025&quot; data-start=&quot;851&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;889&quot; data-start=&quot;851&quot;&gt;UI에서 노드를 끌어다 놓고 연결하면 워크플로우가 완성됩니다.&lt;/li&gt;
&lt;li data-end=&quot;975&quot; data-start=&quot;893&quot;&gt;필요할 때는 &lt;b&gt;Expression(자바스크립트 문법)&lt;/b&gt;이나 &lt;b&gt;Function Node&lt;/b&gt;를 활용해서 직접 로직을 추가할 수도 있습니다.&lt;/li&gt;
&lt;li data-end=&quot;1025&quot; data-start=&quot;979&quot;&gt;즉, &lt;b&gt;비개발자도 쉽게 쓰되, 개발자가 쓰면 훨씬 강력해지는 툴&lt;/b&gt;이에요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;322&quot; data-start=&quot;300&quot; data-ke-size=&quot;size23&quot;&gt;  n8n 핵심 개념&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;687&quot; data-start=&quot;323&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;364&quot; data-start=&quot;323&quot;&gt;&lt;b&gt;Workflow&amp;nbsp; - &lt;a title=&quot;https://docs.n8n.io/workflows/&quot; href=&quot;https://docs.n8n.io/workflows/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.n8n.io/workflows/&lt;/a&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;364&quot; data-start=&quot;344&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;225&quot; data-start=&quot;164&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: n8n에서 가장 기본 단위. 여러 &lt;b&gt;Node&lt;/b&gt;들이 연결되어 만들어진 자동화 프로세스.&lt;/li&gt;
&lt;li data-end=&quot;308&quot; data-start=&quot;226&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: 특정 이벤트를 트리거로 시작해서, 조건 분기 &amp;rarr; 데이터 가공 &amp;rarr; 외부 서비스 호출 &amp;rarr; 결과 저장 같은 단계들을 순서대로 실행.&lt;/li&gt;
&lt;li data-end=&quot;394&quot; data-start=&quot;309&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;394&quot; data-start=&quot;323&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;347&quot; data-start=&quot;323&quot;&gt;GUI 기반으로 시각적으로 구성 가능&lt;/li&gt;
&lt;li data-end=&quot;376&quot; data-start=&quot;350&quot;&gt;재사용 가능 (Export/Import)&lt;/li&gt;
&lt;li data-end=&quot;394&quot; data-start=&quot;379&quot;&gt;실행 이력 관리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;420&quot; data-start=&quot;366&quot;&gt;&lt;b&gt;Node - &lt;a href=&quot;https://docs.n8n.io/integrations/builtin/core-nodes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/integrations/builtin/core-nodes/&lt;/a&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;420&quot; data-start=&quot;383&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;517&quot; data-start=&quot;482&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: Workflow를 구성하는 &amp;ldquo;작업 단위&amp;rdquo;.&lt;/li&gt;
&lt;li data-end=&quot;676&quot; data-start=&quot;518&quot;&gt;&lt;b&gt;종류&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;676&quot; data-start=&quot;532&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;590&quot; data-start=&quot;532&quot;&gt;&lt;b&gt;Action Node&lt;/b&gt;: 실제 작업 실행 (DB 쿼리, HTTP 요청, Slack 전송 등)&lt;/li&gt;
&lt;li data-end=&quot;632&quot; data-start=&quot;593&quot;&gt;&lt;b&gt;Trigger Node&lt;/b&gt;: Workflow 시작 조건 담당&lt;/li&gt;
&lt;li data-end=&quot;676&quot; data-start=&quot;635&quot;&gt;&lt;b&gt;Function Node&lt;/b&gt;: JavaScript로 데이터 가공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;732&quot; data-start=&quot;677&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;732&quot; data-start=&quot;691&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;710&quot; data-start=&quot;691&quot;&gt;입력 &amp;rarr; 처리 &amp;rarr; 출력 구조&lt;/li&gt;
&lt;li data-end=&quot;732&quot; data-start=&quot;713&quot;&gt;서로 연결되어 데이터가 흐름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;496&quot; data-start=&quot;422&quot;&gt;&lt;b&gt;Trigger Node&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;496&quot; data-start=&quot;447&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;881&quot; data-start=&quot;846&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: Workflow를 실행하게 만드는 시작점.&lt;/li&gt;
&lt;li data-end=&quot;1025&quot; data-start=&quot;882&quot;&gt;&lt;b&gt;예시&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1025&quot; data-start=&quot;896&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1707&quot; data-start=&quot;1624&quot;&gt;&lt;span&gt;&lt;b&gt;Manual Trigger&lt;/b&gt; &amp;ndash; 수동 실행, 개발/디버깅에 자주 사용&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1791&quot; data-start=&quot;1708&quot;&gt;&lt;span&gt;&lt;b&gt;Schedule Trigger&lt;/b&gt; &amp;ndash; 시간 기반 자동 실행; cron 표현식도 지원&lt;/span&gt;&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1875&quot; data-start=&quot;1792&quot;&gt;&lt;span&gt;&lt;b&gt;Form Trigger&lt;/b&gt; &amp;ndash; n8n이 제공하는 간단한 웹 폼 제출을 통해 워크플로우 실행&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1959&quot; data-start=&quot;1876&quot;&gt;&lt;span&gt;&lt;b&gt;Local File Trigger&lt;/b&gt; &amp;ndash; 파일 시스템 변화를 감지해 자동 실행 (자체 호스팅에서 유용)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1118&quot; data-start=&quot;1026&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1118&quot; data-start=&quot;1040&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1075&quot; data-start=&quot;1040&quot;&gt;Workflow는 반드시 Trigger Node에서 시작&lt;/li&gt;
&lt;li data-end=&quot;1118&quot; data-start=&quot;1078&quot;&gt;여러 Trigger Node를 한 Workflow에 둘 수도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;563&quot; data-start=&quot;498&quot;&gt;&lt;b&gt;Expression - &lt;a href=&quot;https://docs.n8n.io/code/expressions/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/code/expressions/&lt;/a&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;563&quot; data-start=&quot;521&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1274&quot; data-start=&quot;1230&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: Node 간 데이터 전달 시 동적으로 값을 생성하는 방식.&lt;/li&gt;
&lt;li data-end=&quot;1336&quot; data-start=&quot;1275&quot;&gt;&lt;b&gt;사용법&lt;/b&gt;: {{ $json[&quot;fieldName&quot;] }} 같은 JavaScript 문법을 활용.&lt;/li&gt;
&lt;li data-end=&quot;1435&quot; data-start=&quot;1337&quot;&gt;&lt;b&gt;예시&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1435&quot; data-start=&quot;1351&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1382&quot; data-start=&quot;1351&quot;&gt;API 응답의 특정 필드만 다음 Node에 넘기기&lt;/li&gt;
&lt;li data-end=&quot;1435&quot; data-start=&quot;1385&quot;&gt;&quot;Hello {{ $json[&quot;username&quot;] }}&quot; &amp;rarr; Hello 세연&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1535&quot; data-start=&quot;1436&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1535&quot; data-start=&quot;1450&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1480&quot; data-start=&quot;1450&quot;&gt;단순 변수 치환부터, JS 코드로 가공까지 가능&lt;/li&gt;
&lt;li data-end=&quot;1535&quot; data-start=&quot;1483&quot;&gt;Function Node를 쓰지 않아도 간단한 로직은 Expression으로 처리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;631&quot; data-start=&quot;565&quot;&gt;&lt;b&gt;Credentials - &lt;a href=&quot;https://docs.n8n.io/credentials/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/credentials/&lt;/a&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;631&quot; data-start=&quot;589&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1700&quot; data-start=&quot;1639&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: 외부 서비스(API, DB 등)와 연결할 때 필요한 인증 정보를 안전하게 저장하는 기능.&lt;/li&gt;
&lt;li data-end=&quot;1797&quot; data-start=&quot;1701&quot;&gt;&lt;b&gt;예시&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1797&quot; data-start=&quot;1715&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1736&quot; data-start=&quot;1715&quot;&gt;Slack OAuth Token&lt;/li&gt;
&lt;li data-end=&quot;1762&quot; data-start=&quot;1739&quot;&gt;PostgreSQL DB 계정 정보&lt;/li&gt;
&lt;li data-end=&quot;1797&quot; data-start=&quot;1765&quot;&gt;GitHub Personal Access Token&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1897&quot; data-start=&quot;1798&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1897&quot; data-start=&quot;1812&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1824&quot; data-start=&quot;1812&quot;&gt;암호화되어 저장&lt;/li&gt;
&lt;li data-end=&quot;1851&quot; data-start=&quot;1827&quot;&gt;여러 Workflow에서 재사용 가능&lt;/li&gt;
&lt;li data-end=&quot;1897&quot; data-start=&quot;1854&quot;&gt;보안상 노드에 직접 입력하지 않고 Credentials에 등록 후 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;687&quot; data-start=&quot;633&quot;&gt;&lt;b&gt;Execution - &lt;a href=&quot;https://docs.n8n.io/workflows/executions/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/workflows/executions/&lt;/a&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;687&quot; data-start=&quot;655&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2072&quot; data-start=&quot;1994&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: Workflow가 실행된 기록. 어떤 Trigger로 시작되었고, 각 Node가 어떤 입력/출력을 가졌는지 확인 가능.&lt;/li&gt;
&lt;li data-end=&quot;2165&quot; data-start=&quot;2073&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2165&quot; data-start=&quot;2087&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2102&quot; data-start=&quot;2087&quot;&gt;성공/실패 여부 확인&lt;/li&gt;
&lt;li data-end=&quot;2134&quot; data-start=&quot;2105&quot;&gt;실패 시 Error Workflow 연결 가능&lt;/li&gt;
&lt;li data-end=&quot;2165&quot; data-start=&quot;2137&quot;&gt;Debug 모드에서 단계별 데이터 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2269&quot; data-start=&quot;2166&quot;&gt;&lt;b&gt;예시&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2269&quot; data-start=&quot;2180&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2227&quot; data-start=&quot;2180&quot;&gt;Cron Trigger &amp;rarr; DB Query &amp;rarr; Slack 메시지 &amp;rarr; 성공 로그&lt;/li&gt;
&lt;li data-end=&quot;2269&quot; data-start=&quot;2230&quot;&gt;Webhook Trigger &amp;rarr; API 오류 발생 &amp;rarr; 실패 로그&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;717&quot; data-start=&quot;694&quot; data-ke-size=&quot;size23&quot;&gt;⚙️ 설치 (Self-host 기준)&lt;/h3&gt;
&lt;p data-end=&quot;748&quot; data-start=&quot;718&quot; data-ke-size=&quot;size16&quot;&gt;Docker를 이용하면 가장 쉽게 설치할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1756392301499&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -it --rm \
  --name n8n \
  -p 5678:5678 \
  -e N8N_BASIC_AUTH_USER=user \
  -e N8N_BASIC_AUTH_PASSWORD=pass \
  n8nio/n8n&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실행 후 &lt;a href=&quot;http://localhost:5678&quot; data-end=&quot;383&quot; data-start=&quot;337&quot;&gt;http://localhost:5678&lt;span aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;/a&gt; 로 접속하면 n8n 에디터를 사용할 수 있습니다.&lt;br /&gt;  N8N_BASIC_AUTH_USER / N8N_BASIC_AUTH_PASSWORD 를 설정해 두면 기본 인증 로그인 화면이 뜹니다. (보안상 권장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  Docker 실행 시 주요 환경변수&lt;/h4&gt;
&lt;p data-end=&quot;590&quot; data-start=&quot;538&quot; data-ke-size=&quot;size16&quot;&gt;n8n은 다양한 환경변수 설정을 지원합니다. 실무에서는 최소한 아래 정도는 지정해두면 좋아요.&lt;/p&gt;
&lt;p data-end=&quot;590&quot; data-start=&quot;538&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 기본 보안&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1756528571324&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-e N8N_BASIC_AUTH_USER=user \
-e N8N_BASIC_AUTH_PASSWORD=pass&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;701&quot; data-start=&quot;679&quot;&gt;관리자 로그인 계정/비밀번호 설정&lt;/li&gt;
&lt;li data-end=&quot;740&quot; data-start=&quot;702&quot;&gt;기본 인증을 켜지 않으면 누구나 접속 가능하므로 꼭 설정 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 실행 환경(Workflow 저장 위치)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1756528619703&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-v ~/.n8n:/home/node/.n8n&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;915&quot; data-start=&quot;815&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;844&quot; data-start=&quot;815&quot;&gt;워크플로우와 설정을 &lt;b&gt;호스트 볼륨에 저장&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;915&quot; data-start=&quot;845&quot;&gt;--rm 옵션으로 실행하면 컨테이너 종료 시 데이터가 사라지므로, 실제 운영에서는 반드시 볼륨을 마운트해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 외부 접속용 설정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1756530201778&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-e N8N_HOST=example.com \
-e N8N_PORT=5678 \
-e N8N_PROTOCOL=https&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1109&quot; data-start=&quot;1018&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1046&quot; data-start=&quot;1018&quot;&gt;`N8N_HOST`: 서비스 도메인/호스트명&lt;/li&gt;
&lt;li data-end=&quot;1075&quot; data-start=&quot;1047&quot;&gt;`N8N_PORT`: 포트 (기본 5678)&lt;/li&gt;
&lt;li data-end=&quot;1109&quot; data-start=&quot;1076&quot;&gt;`N8N_PROTOCOL`: http 또는 https&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1165&quot; data-start=&quot;1111&quot; data-ke-size=&quot;size16&quot;&gt;  프록시/리버스 프록시(Nginx, Traefik)와 함께 HTTPS 설정 시 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Webhook&lt;/b&gt;&lt;b&gt; 관련&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1756530320457&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-e WEBHOOK_URL=https://example.com/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1282&quot; data-start=&quot;1238&quot;&gt;외부 서비스에서 n8n Webhook Node를 호출할 때 사용할 URL&lt;/li&gt;
&lt;li data-end=&quot;1311&quot; data-start=&quot;1283&quot;&gt;클라우드 환경이나 도메인 기반 서비스에 필수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1337&quot; data-start=&quot;1318&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 실행/큐 모드 (고급)&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756530361206&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-e EXECUTIONS_PROCESS=main&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1494&quot; data-start=&quot;1377&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1401&quot; data-start=&quot;1377&quot;&gt;`main`: 단일 프로세스 (기본)&lt;/li&gt;
&lt;li data-end=&quot;1448&quot; data-start=&quot;1402&quot;&gt;`queue`: BullMQ 기반 큐 모드 (여러 워커에서 병렬 실행 가능)&lt;/li&gt;
&lt;li data-end=&quot;1494&quot; data-start=&quot;1449&quot;&gt;대량 데이터/고성능 환경에서는 queue 모드와 Redis 연동을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1516&quot; data-start=&quot;1501&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 실행 이력 보관&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756530407586&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-e EXECUTIONS_DATA_SAVE_ON_SUCCESS=none \ -e EXECUTIONS_DATA_SAVE_ON_ERROR=all&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1662&quot; data-start=&quot;1608&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1645&quot; data-start=&quot;1608&quot;&gt;성공한 실행은 기록하지 않고, 실패한 실행만 기록하도록 설정&lt;/li&gt;
&lt;li data-end=&quot;1662&quot; data-start=&quot;1646&quot;&gt;DB 공간 절약에 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. SQLite 대신 Postgres 쓰기(운영/협업 권장)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1756530967398&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  -e DB_POSTGRESDB_HOST=localhost \
  -e DB_POSTGRESDB_PORT=5432 \
  -e DB_POSTGRESDB_DATABASE=n8n \
  -e DB_POSTGRESDB_USER=n8n \
  -e DB_POSTGRESDB_PASSWORD=비밀번호 \&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 DB는 &lt;b&gt;SQLite&lt;/b&gt;지만, &lt;b&gt;PostgreSQL&lt;/b&gt;을 공식 지원합니다( MySQL/MariaDB는 v1.0에서 deprecated ).&lt;/li&gt;
&lt;li data-end=&quot;2586&quot; data-start=&quot;2422&quot;&gt;스키마, SSL 등도 환경변수로 세밀하게 설정 가능합니다.&lt;br /&gt;`DB_POSTGRESDB_SCHEMA`(기본 public), `DB_POSTGRESDB_SSL_ENABLED`, ..._CA/CERT/KEY 등.&lt;/li&gt;
&lt;li data-end=&quot;2696&quot; data-start=&quot;2587&quot;&gt;&amp;ldquo;기본은 SQLite, Postgres 지원, MySQL/MariaDB deprecated&amp;rdquo;도 공식 문서에 명시되어 있습니다.&lt;br /&gt;&lt;a href=&quot;https://docs.n8n.io/hosting/configuration/environment-variables/database/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/hosting/configuration/environment-variables/database/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;966&quot; data-start=&quot;940&quot; data-ke-size=&quot;size23&quot;&gt;  주요 기능 (실무에서 많이 쓰는 것)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Webhook Node &lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;rarr; 외부 이벤트 받아서 워크플로우 실행&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl69Ch/btsQdPHWn8Q/apOGMBbex4ckAEkubKQ4Lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl69Ch/btsQdPHWn8Q/apOGMBbex4ckAEkubKQ4Lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl69Ch/btsQdPHWn8Q/apOGMBbex4ckAEkubKQ4Lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl69Ch%2FbtsQdPHWn8Q%2FapOGMBbex4ckAEkubKQ4Lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;439&quot; height=&quot;210&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;466&quot; data-start=&quot;177&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: 외부 서비스가 n8n의 엔드포인트를 호출하면 워크플로우를 시작합니다. 테스트/운영 &lt;b&gt;2개의 URL&lt;/b&gt;을 제공하고, 응답 타이밍(즉시/마지막 노드 완료/Respond to Webhook 노드 사용)도 선택 가능. &lt;br /&gt;&lt;b&gt;인증 방식&lt;/b&gt;(Basic/Header/JWT)과 &lt;b&gt;CORS, IP 화이트리스트, 바이너리 수신&lt;/b&gt;도 지원합니다. 최대 페이로드는 기본 &lt;b&gt;16MB&lt;/b&gt;(환경변수 `N8N_PAYLOAD_SIZE_MAX`로 조정)&lt;/li&gt;
&lt;li data-end=&quot;661&quot; data-start=&quot;467&quot;&gt;&lt;b&gt;Tip &lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;661&quot; data-start=&quot;479&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;556&quot; data-start=&quot;479&quot;&gt;테스트 중엔 &lt;b&gt;Test URL&lt;/b&gt;로 &amp;ldquo;Listen for Test Event&amp;rdquo;, 운영 전환 시 &lt;b&gt;Production URL&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;661&quot; data-start=&quot;559&quot;&gt;HTML을 응답으로 보낼 때는 n8n이 보안을 위해 &amp;lt;iframe&amp;gt;로 감쌀 수 있어요(1.103.0+)&lt;b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. &lt;b&gt;If / Switch / Function(Code) Node&lt;/b&gt; &amp;mdash; 분기와 가공의 3종 세트&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9MTv8/btsQdcp6G2N/cAwbV1mPVf4ERNzhss7KC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9MTv8/btsQdcp6G2N/cAwbV1mPVf4ERNzhss7KC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9MTv8/btsQdcp6G2N/cAwbV1mPVf4ERNzhss7KC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9MTv8%2FbtsQdcp6G2N%2FcAwbV1mPVf4ERNzhss7KC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;705&quot; height=&quot;213&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;943&quot; data-start=&quot;932&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;If Node&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1136&quot; data-start=&quot;944&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1097&quot; data-start=&quot;944&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: 단일 조건으로 &lt;b&gt;true/false&lt;/b&gt; 두 갈래 분기. v1.0 이후 실행 순서가 바뀌면서(레거시 v0와 다름) If+Merge 조합의 동작이 정리되었습니다(레거시 주의 섹션 참고).&lt;br /&gt;&lt;a href=&quot;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.if/?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.if/&lt;/a&gt;&lt;br /&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1136&quot; data-start=&quot;1098&quot;&gt;&lt;b&gt;예시(표현식)&lt;/b&gt;: {{$json.total &amp;gt; 100}}&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1153&quot; data-start=&quot;1138&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Switch Node&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1345&quot; data-start=&quot;1154&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1284&quot; data-start=&quot;1154&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: &lt;b&gt;여러 케이스&lt;/b&gt;로 라우팅(If의 멀티버전). &lt;b&gt;Rules 모드&lt;/b&gt;(출력별 규칙) 또는 &lt;b&gt;Expression 모드&lt;/b&gt;(인덱스 반환) 지원.&lt;br /&gt;&lt;a href=&quot;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.switch/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.switch/&lt;/a&gt;&lt;/li&gt;
&lt;li data-end=&quot;1345&quot; data-start=&quot;1285&quot;&gt;&lt;b&gt;예시&lt;/b&gt;: 상태값이 pending/shipped/cancelled일 때 1&amp;middot;2&amp;middot;3번 출력으로 분기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1371&quot; data-start=&quot;1347&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Function / Code Node&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1901&quot; data-start=&quot;1372&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1461&quot; data-start=&quot;1372&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: &lt;b&gt;JS/파이썬으로 자유로운 가공&lt;/b&gt;. v0.198.0+부터는 &lt;b&gt;Code Node&lt;/b&gt;가 Function/Function Item을 대체.&lt;/li&gt;
&lt;li data-end=&quot;1548&quot; data-start=&quot;1462&quot;&gt;&lt;b&gt;모드&lt;/b&gt;: &amp;ldquo;&lt;b&gt;Run once for all items&lt;/b&gt;&amp;rdquo;(배치 처리) vs &amp;ldquo;&lt;b&gt;Run once for each item&lt;/b&gt;&amp;rdquo;(아이템별).&lt;/li&gt;
&lt;li data-end=&quot;1741&quot; data-start=&quot;1549&quot;&gt;&lt;b&gt;외부 모듈&lt;/b&gt;: &lt;b&gt;Self-host&lt;/b&gt;에선 외부 npm 모듈 사용 가능(설정 필요). &lt;b&gt;Cloud&lt;/b&gt;에선 crypto, moment만 제공. 파일시스템/직접 HTTP 접근은 불가(전용 노드 사용). 데이터 구조/아이템 링크 규칙도 문서에 정리돼 있습니다.&lt;br /&gt;&lt;a href=&quot;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/&lt;/a&gt;&lt;/li&gt;
&lt;li data-end=&quot;1901&quot; data-start=&quot;1742&quot;&gt;&lt;b&gt;자주 쓰는 패턴(JS)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1756532683439&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 모든 입력 아이템을 가져와 가공 후 반환
const items = $input.all();
return items.map(item =&amp;gt; ({ json: { ...item.json, ok: true }}));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Wait Node &lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; &lt;/span&gt;승인/이벤트/시간까지 &amp;ldquo;잠시 멈춤&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EZTlk/btsQeqgxhae/6wchwVHvUAb3TEkJ6VZ8R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EZTlk/btsQeqgxhae/6wchwVHvUAb3TEkJ6VZ8R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EZTlk/btsQeqgxhae/6wchwVHvUAb3TEkJ6VZ8R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEZTlk%2FbtsQeqgxhae%2F6wchwVHvUAb3TEkJ6VZ8R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;497&quot; height=&quot;231&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2321&quot; data-start=&quot;1950&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2225&quot; data-start=&quot;1950&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: 실행을 &lt;b&gt;DB로 오프로딩&lt;/b&gt;하고 조건을 기다린 뒤 &lt;b&gt;재개&lt;/b&gt;. 대기 조건은 &lt;b&gt;시간 간격&lt;/b&gt;, &lt;b&gt;특정 시각&lt;/b&gt;, &lt;b&gt;웹훅 호출&lt;/b&gt;, &lt;b&gt;폼 제출&lt;/b&gt; 4가지. &lt;b&gt;On Webhook Call&lt;/b&gt;에서는 실행마다 고유한 &lt;b&gt;$execution.resumeUrl&lt;/b&gt; 이 생성돼 외부에 전달해 재개를 트리거합니다. &lt;br /&gt;&lt;b&gt;65초 미만&lt;/b&gt; 대기는 오프로딩 없이 같은 프로세스에서 이어집니다(서버 시간 기준). &lt;br /&gt;&lt;a href=&quot;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/&lt;/a&gt;&lt;br /&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2321&quot; data-start=&quot;2226&quot;&gt;&lt;b&gt;실무 예시&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2321&quot; data-start=&quot;2243&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2294&quot; data-start=&quot;2243&quot;&gt;&amp;ldquo;검토 대기&amp;rdquo; 이메일 발송 &amp;rarr; 승인자가 &lt;b&gt;resumeUrl&lt;/b&gt; 클릭 시 이어서 진행&lt;/li&gt;
&lt;li data-end=&quot;2321&quot; data-start=&quot;2297&quot;&gt;&amp;ldquo;내일 09:00에 다시 실행&amp;rdquo; 스케줄링&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. Error Workflow &lt;/b&gt;&amp;rarr; 실패 시 알림/Slack 연동&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2713&quot; data-start=&quot;2367&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2546&quot; data-start=&quot;2367&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: 워크플로우 실패 시 &lt;b&gt;Error Trigger 노드&lt;/b&gt;가 있는 별도 워크플로우를 실행해 슬랙/메일 알림, 롤백, 재시도 큐 적재 등을 수행. &lt;b&gt;Workflow Settings&lt;/b&gt;에서 Error Workflow를 지정하면 됩니다.&lt;br /&gt;&lt;a href=&quot;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.errortrigger/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.errortrigger/&lt;/a&gt;&lt;/li&gt;
&lt;li data-end=&quot;2713&quot; data-start=&quot;2547&quot;&gt;&lt;b&gt;구성 팁&lt;/b&gt;: 실패 로그를 &lt;b&gt;Executions&lt;/b&gt;에서 열람하고, Error Workflow에서 `{{$json.error.message}}`, `{{$json.workflow.id}}` 같은 정보를 슬랙 메시지에 포함. &lt;br /&gt;&lt;a href=&quot;https://docs.n8n.io/flow-logic/error-handling/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/flow-logic/error-handling/&lt;/a&gt;&lt;br /&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;2756&quot; data-start=&quot;2720&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. Looping&lt;/b&gt; &amp;mdash; 대용량/페이지네이션/레이트리밋&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;435&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B8D9R/btsQeDfyw6o/tjfegBYcl2jKDBaTlz2KO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B8D9R/btsQeDfyw6o/tjfegBYcl2jKDBaTlz2KO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B8D9R/btsQeDfyw6o/tjfegBYcl2jKDBaTlz2KO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB8D9R%2FbtsQeDfyw6o%2FtjfegBYcl2jKDBaTlz2KO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;457&quot; height=&quot;229&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;435&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3069&quot; data-start=&quot;2757&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3007&quot; data-start=&quot;2757&quot;&gt;&lt;b&gt;기본 철학&lt;/b&gt;: 대부분의 노드는 &lt;b&gt;리스트 입력을 자동으로 순회&lt;/b&gt;하므로 별도 루프가 필요 없습니다. 그래도 &lt;b&gt;페이지네이션&lt;/b&gt;, &lt;b&gt;레이트 리밋 회피&lt;/b&gt;, &lt;b&gt;특정 예외 노드&lt;/b&gt; 처리에는 &lt;b&gt;Loop Over Items(Split in Batches)&lt;/b&gt; 노드를 씁니다. &lt;b&gt;Reset&lt;/b&gt; 옵션으로 반복 시 입력을 새로 초기화하며 종료 조건(If)과 함께 사용&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://docs.n8n.io/flow-logic/looping&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/flow-logic/looping&lt;/a&gt;&lt;/li&gt;
&lt;li data-end=&quot;3069&quot; data-start=&quot;3008&quot;&gt;&lt;b&gt;패턴&lt;/b&gt;: &amp;ldquo;다음 페이지가 있으면 If로 루프 계속 &amp;rarr; 없으면 종료&amp;rdquo;, &amp;ldquo;API 100건씩 배치 전송&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;3120&quot; data-start=&quot;3076&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. Version Control(이력/롤백)&lt;/b&gt; &amp;mdash; 안전한 변경 관리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3566&quot; data-start=&quot;3121&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3241&quot; data-start=&quot;3121&quot;&gt;&lt;b&gt;Workflow History&lt;/b&gt;: &lt;b&gt;Enterprise&lt;/b&gt;(Cloud Pro는 제한적)에서 &lt;b&gt;이전 버전 열람/로딩&lt;/b&gt;이 가능합니다.&lt;br /&gt;&lt;a href=&quot;https://docs.n8n.io/release-notes&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/release-notes&lt;/a&gt;&lt;br /&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3403&quot; data-start=&quot;3242&quot;&gt;&lt;b&gt;Git 기반 환경 분리(소스 컨트롤)&lt;/b&gt;: 엔터프라이즈에서 Git 리포지토리와 연결해 &lt;b&gt;브랜치별 환경&lt;/b&gt;(dev/stage/prod)과 &lt;b&gt;Diff 비교&lt;/b&gt;를 제공합니다. 권한 요건과 사용법은 문서 참고.&lt;br /&gt;&lt;a href=&quot;https://docs.n8n.io/source-control-environments/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/source-control-environments/&lt;/a&gt;&lt;/li&gt;
&lt;li data-end=&quot;3566&quot; data-start=&quot;3404&quot;&gt;&lt;b&gt;커뮤니티/셀프호스트 백업 팁&lt;/b&gt;: CLI로 워크플로우/자격증명 &lt;b&gt;Export/Import&lt;/b&gt; 자동화(예: n8n export:workflow --all). 정기 백업 + Git에 JSON 저장을 추천합니다.&lt;br /&gt;&lt;a href=&quot;https://docs.n8n.io/hosting/cli-commands&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.n8n.io/hosting/cli-commands&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1572&quot; data-start=&quot;1543&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1572&quot; data-start=&quot;1543&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;456&quot; data-start=&quot;341&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-end=&quot;456&quot; data-start=&quot;343&quot; data-ke-size=&quot;size16&quot;&gt;n8n은 단순히 자동화를 넘어, 팀 생산성을 높여주는 오픈소스 플랫폼입니다.&lt;br /&gt;이번 글에서 정리한 기본 개념을 바탕으로, 이후에는 실제 예제와 함께 더 깊이 다뤄보겠습니다.  &lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>AI/N8N</category>
      <category>n8n</category>
      <category>n8n docker 환경변수</category>
      <category>n8n workflow</category>
      <category>n8n 개념</category>
      <category>n8n 주요기능</category>
      <author>누구세연</author>
      <guid isPermaLink="true">https://sy-develop-note.tistory.com/170</guid>
      <comments>https://sy-develop-note.tistory.com/170#entry170comment</comments>
      <pubDate>Sat, 30 Aug 2025 15:11:09 +0900</pubDate>
    </item>
    <item>
      <title>PostgreSQL 시작하기: Docker 설치부터 DBeaver로 데이터 다루기</title>
      <link>https://sy-develop-note.tistory.com/169</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W9rB4/btsP43eyPoE/N6HFEzQRZUO2bYenSOwykK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W9rB4/btsP43eyPoE/N6HFEzQRZUO2bYenSOwykK/img.png&quot; data-alt=&quot;PostgreSQL&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W9rB4/btsP43eyPoE/N6HFEzQRZUO2bYenSOwykK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW9rB4%2FbtsP43eyPoE%2FN6HFEzQRZUO2bYenSOwykK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;547&quot; height=&quot;306&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PostgreSQL&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;132&quot; data-start=&quot;114&quot; data-ke-size=&quot;size23&quot;&gt;  PostgreSQL이란?&lt;/h3&gt;
&lt;p data-end=&quot;269&quot; data-start=&quot;134&quot; data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 데이터를 &lt;b&gt;안전하게 저장하고 꺼내 쓰게 해주는 데이터베이스(DB)&lt;/b&gt;입니다.&lt;br /&gt;하지만 단순히 데이터를 넣고 빼는 수준을 넘어, &lt;b&gt;복잡한 데이터까지 똑똑하게 다룰 수 있는 고급 데이터베이스&lt;/b&gt;라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;369&quot; data-start=&quot;271&quot; data-ke-size=&quot;size16&quot;&gt;즉, PostgreSQL은 일반적인 &lt;b&gt;관계형 데이터베이스(RDBMS)&lt;/b&gt; 가 가진 기능을 모두 제공하면서도,&lt;br /&gt;그 이상으로 &lt;b&gt;더 다양한 기능과 확장성&lt;/b&gt;을 지원합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-end=&quot;369&quot; data-start=&quot;271&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ PostgreSQL의 특징&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 더 다양한 데이터 타입 지원&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 RDBMS(MySQL 등)는 INT, VARCHAR, DATE 같은 &lt;b&gt;기본 타입 위주&lt;/b&gt;로 데이터를 저장합니다.&lt;br /&gt;반면 PostgreSQL은 훨씬 다양한 타입을 기본으로 지원합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;720&quot; data-start=&quot;538&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;584&quot; data-start=&quot;538&quot;&gt;&lt;b&gt;JSON / JSONB&lt;/b&gt; &amp;rarr; JSON 데이터를 효율적으로 저장하고 검색&lt;/li&gt;
&lt;li data-end=&quot;616&quot; data-start=&quot;585&quot;&gt;&lt;b&gt;ARRAY&lt;/b&gt; &amp;rarr; 배열 형태 데이터 저장 가능&lt;/li&gt;
&lt;li data-end=&quot;645&quot; data-start=&quot;617&quot;&gt;&lt;b&gt;UUID&lt;/b&gt; &amp;rarr; 고유 식별자 저장용 타입&lt;/li&gt;
&lt;li data-end=&quot;683&quot; data-start=&quot;646&quot;&gt;&lt;b&gt;RANGE&lt;/b&gt; &amp;rarr; 범위(예: 1~10, 날짜 구간) 타입&lt;/li&gt;
&lt;li data-end=&quot;720&quot; data-start=&quot;684&quot;&gt;&lt;b&gt;HSTORE&lt;/b&gt; &amp;rarr; Key-Value 형태 데이터 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;722&quot; data-ke-size=&quot;size16&quot;&gt;  즉, PostgreSQL은 단순한 테이블 구조뿐 아니라, &lt;b&gt;반정형/비정형 데이터까지 쉽게 다룰 수 있는 장점&lt;/b&gt;이 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;722&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;797&quot; data-start=&quot;722&quot; data-ke-size=&quot;size20&quot;&gt;2. 안정성과 신뢰성 (ACID 보장)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 &lt;b&gt;트랜잭션 안전성&lt;/b&gt;을 엄격히 보장합니다.&lt;br /&gt;은행 이체 같은 작업에서 &amp;ldquo;중간에 실패하면 자동으로 취소(ROLLBACK)&amp;rdquo;되는 구조라, &lt;b&gt;데이터 정합성&lt;/b&gt;이 매우 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 동시성 처리 (MVCC)&lt;/h4&gt;
&lt;p data-end=&quot;1123&quot; data-start=&quot;970&quot; data-ke-size=&quot;size16&quot;&gt;여러 사용자가 동시에 데이터를 읽고 쓰는 상황에서도 충돌이 최소화됩니다.&lt;br /&gt;이는 PostgreSQL의 &lt;b&gt;MVCC(Multi-Version Concurrency Control)&lt;/b&gt; 덕분인데,&lt;br /&gt;읽는 쿼리는 잠금(Lock) 없이 진행되어 &lt;b&gt;읽기 성능&lt;/b&gt;이 뛰어납니다.&lt;/p&gt;
&lt;p data-end=&quot;1123&quot; data-start=&quot;970&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1123&quot; data-start=&quot;970&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MySQL(InnoDB)에서의 MVCC&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;608&quot; data-start=&quot;290&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;342&quot; data-start=&quot;290&quot;&gt;InnoDB는 &lt;b&gt;Undo Log(Undo 영역)&lt;/b&gt; 를 이용해 MVCC를 구현합니다.&lt;/li&gt;
&lt;li data-end=&quot;424&quot; data-start=&quot;343&quot;&gt;즉, 데이터를 수정할 때 원본 값을 Undo 영역에 기록해 두고, 다른 트랜잭션이 과거 데이터를 필요로 하면 Undo Log를 참조합니다.&lt;/li&gt;
&lt;li data-end=&quot;608&quot; data-start=&quot;425&quot;&gt;주요 특징
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;608&quot; data-start=&quot;438&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;507&quot; data-start=&quot;438&quot;&gt;&lt;b&gt;읽기(SELECT)&lt;/b&gt; 는 Undo Log를 참조 &amp;rarr; REPEATABLE READ 수준에서 동일한 스냅샷 보장&lt;/li&gt;
&lt;li data-end=&quot;564&quot; data-start=&quot;510&quot;&gt;&lt;b&gt;쓰기(UPDATE, DELETE)&lt;/b&gt; 는 레코드에 &lt;b&gt;잠금(Lock)&lt;/b&gt; 을 걸어야 함&lt;/li&gt;
&lt;li data-end=&quot;608&quot; data-start=&quot;567&quot;&gt;따라서 읽기와 쓰기는 동시에 가능하지만, 충돌 가능성은 여전히 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PostgreSQL에서의 MVCC&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;926&quot; data-start=&quot;641&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;696&quot; data-start=&quot;641&quot;&gt;PostgreSQL은 &lt;b&gt;각 행(Row)에 버전 정보(xmin/xmax)&lt;/b&gt; 를 저장합니다.&lt;/li&gt;
&lt;li data-end=&quot;747&quot; data-start=&quot;697&quot;&gt;데이터를 업데이트하면 &amp;rarr; 기존 행을 지우지 않고 &lt;b&gt;새 버전의 행을 추가&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li data-end=&quot;785&quot; data-start=&quot;748&quot;&gt;트랜잭션은 자신이 시작한 시점에 유효한 버전만 보게 됩니다.&lt;/li&gt;
&lt;li data-end=&quot;926&quot; data-start=&quot;786&quot;&gt;주요 특징
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;926&quot; data-start=&quot;799&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;834&quot; data-start=&quot;799&quot;&gt;Undo 영역이 아니라 &lt;b&gt;테이블 자체에서 버전 관리&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;879&quot; data-start=&quot;837&quot;&gt;SELECT는 절대 Lock을 걸지 않음 &amp;rarr; 읽기 성능이 매우 뛰어남&lt;/li&gt;
&lt;li data-end=&quot;926&quot; data-start=&quot;882&quot;&gt;단점: 오래된 버전이 계속 쌓이므로 &lt;b&gt;VACUUM(청소 작업)&lt;/b&gt; 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1123&quot; data-start=&quot;970&quot; data-ke-size=&quot;size20&quot;&gt;4. 고급 SQL 기능&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1266&quot; data-start=&quot;1147&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1188&quot; data-start=&quot;1147&quot;&gt;&lt;b&gt;Window 함수&lt;/b&gt;: 순위 매기기, 누적 합계, 이동 평균 등&lt;/li&gt;
&lt;li data-end=&quot;1228&quot; data-start=&quot;1189&quot;&gt;&lt;b&gt;CTE (WITH 구문)&lt;/b&gt;: 복잡한 쿼리를 보기 좋게 분리&lt;/li&gt;
&lt;li data-end=&quot;1266&quot; data-start=&quot;1229&quot;&gt;&lt;b&gt;JSON 쿼리&lt;/b&gt;: JSON 데이터를 SQL로 직접 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1326&quot; data-start=&quot;1268&quot; data-ke-size=&quot;size16&quot;&gt;  덕분에 단순 저장소가 아니라, &lt;b&gt;데이터 분석&amp;middot;집계&lt;/b&gt;까지 PostgreSQL에서 가능해집니다.&lt;/p&gt;
&lt;p data-end=&quot;1326&quot; data-start=&quot;1268&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-end=&quot;1326&quot; data-start=&quot;1268&quot; data-ke-size=&quot;size23&quot;&gt;  PostgreSQL 간단 실습&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1️⃣ 환경 준비하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단한 방법은 &lt;b&gt;Docker&lt;/b&gt;를 이용하는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1756025186651&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run --name pg-test -e POSTGRES_PASSWORD=pass -p 5432:5432 -d postgres&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실행 후, localhost:5432 포트에 PostgreSQL이 실행됩니다.&lt;br /&gt;  기본 계정: postgres / 비밀번호: pass&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;349&quot; data-start=&quot;327&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2️⃣ PostgreSQL 접속하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;350&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너 안에서 바로 psql을 실행해봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1756025786471&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker exec -it pg-test psql -U postgres&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  정상 접속되면 아래와 같은 프롬프트가 뜹니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmZxhU/btsP2HRkGvm/gY8OmRKB9WXA20GIRTrDYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmZxhU/btsP2HRkGvm/gY8OmRKB9WXA20GIRTrDYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmZxhU/btsP2HRkGvm/gY8OmRKB9WXA20GIRTrDYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmZxhU%2FbtsP2HRkGvm%2FgY8OmRKB9WXA20GIRTrDYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1369&quot; height=&quot;204&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3️⃣ 데이터베이스와 사용자 생성&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;실습을 위해 새로운 DB와 유저를 만들어봅시다.&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756026464588&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE DATABASE test_db;
CREATE USER test_user WITH PASSWORD 'testpass';
GRANT ALL PRIVILEGES ON DATABASE test_db TO test_user;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이제 앞으로는 test_user 계정으로 test_db를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;756&quot; data-start=&quot;734&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4️⃣ 테이블 만들고 CRUD 실습&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;767&quot; data-start=&quot;757&quot; data-ke-size=&quot;size16&quot;&gt;테이블 생성&lt;/p&gt;
&lt;pre id=&quot;code_1756032835126&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100) UNIQUE,
    created_at TIMESTAMP DEFAULT NOW()
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 삽입&lt;/p&gt;
&lt;pre id=&quot;code_1756032864569&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO users (name, email) VALUES ('홍길동', 'hong@test.com');
INSERT INTO users (name, email) VALUES ('강감찬', 'kang@test.com');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 조회&lt;/p&gt;
&lt;pre id=&quot;code_1756032942089&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM users;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  아래처럼 결과가 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T8Yzv/btsP40hRHIM/WpEBYz2RGu2rhCVNdykSy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T8Yzv/btsP40hRHIM/WpEBYz2RGu2rhCVNdykSy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T8Yzv/btsP40hRHIM/WpEBYz2RGu2rhCVNdykSy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT8Yzv%2FbtsP40hRHIM%2FWpEBYz2RGu2rhCVNdykSy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;714&quot; height=&quot;198&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1384&quot; data-start=&quot;1371&quot; data-ke-size=&quot;size16&quot;&gt;데이터 수정/삭제&lt;/p&gt;
&lt;pre id=&quot;code_1756033016039&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UPDATE users SET name = '이순신' WHERE id = 1;
DELETE FROM users WHERE id = 2;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5️⃣ PostgreSQL만의 특징 &amp;ndash; JSONB 실습&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 JSON 데이터를 효율적으로 다룰 수 있는 &lt;b&gt;JSONB 타입&lt;/b&gt;을 지원합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1756033151244&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    details JSONB
);

INSERT INTO products (name, details)
VALUES ('노트북', '{&quot;brand&quot;:&quot;Apple&quot;,&quot;spec&quot;:{&quot;cpu&quot;:&quot;M1&quot;,&quot;ram&quot;:&quot;16GB&quot;}}');

SELECT name, details-&amp;gt;'spec'-&amp;gt;&amp;gt;'cpu' AS cpu_spec
FROM products;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  결과&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qiYlI/btsP5Mwz6iS/8ysGPo9EauhSXZCqijZHGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qiYlI/btsP5Mwz6iS/8ysGPo9EauhSXZCqijZHGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qiYlI/btsP5Mwz6iS/8ysGPo9EauhSXZCqijZHGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqiYlI%2FbtsP5Mwz6iS%2F8ysGPo9EauhSXZCqijZHGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;743&quot; height=&quot;190&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;6️⃣ 뷰(View) 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 쓰는 쿼리를 뷰(View)로 저장할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1756033232049&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE VIEW recent_users AS
SELECT id, name, email, created_at
FROM users
ORDER BY created_at DESC
LIMIT 5;

SELECT * FROM recent_users;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  테이블처럼 recent_users에서 데이터를 조회할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkQpek/btsP2Hqoqoy/apxcf89iv96a4LmdWkFdO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkQpek/btsP2Hqoqoy/apxcf89iv96a4LmdWkFdO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkQpek/btsP2Hqoqoy/apxcf89iv96a4LmdWkFdO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkQpek%2FbtsP2Hqoqoy%2Fapxcf89iv96a4LmdWkFdO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;749&quot; height=&quot;196&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2185&quot; data-start=&quot;2171&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;7️⃣ 트랜잭션 체험&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2215&quot; data-start=&quot;2186&quot; data-ke-size=&quot;size16&quot;&gt;트랜잭션은 데이터 정합성을 보장하는 핵심 기능입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1756033289877&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BEGIN;
UPDATE users SET name = '세종대왕' WHERE id = 1;

-- 취소하고 싶을 때
ROLLBACK;

-- 확정하고 싶을 때
COMMIT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-end=&quot;210&quot; data-start=&quot;181&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;210&quot; data-start=&quot;181&quot; data-ke-size=&quot;size23&quot;&gt;  DBeaver로 PostgreSQL 접속하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CLI(psql) 환경도 좋지만, GUI 툴을 쓰면 데이터베이스 구조와 데이터를 &lt;b&gt;직관적으로 확인&lt;/b&gt;할 수 있습니다.&lt;br /&gt;여기서는 많이 사용하는 무료 툴인 &lt;b&gt;DBeaver&lt;/b&gt;를 이용해 PostgreSQL에 연결해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1️⃣ DBeaver 설치&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;425&quot; data-start=&quot;363&quot;&gt;&lt;a data-end=&quot;411&quot; data-start=&quot;365&quot;&gt;DBeaver 공식 사이트&lt;span aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;/a&gt;에서 다운로드 및 설치&lt;/li&gt;
&lt;li data-end=&quot;455&quot; data-start=&quot;426&quot;&gt;Mac, Windows, Linux 모두 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;479&quot; data-start=&quot;462&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2️⃣ 새로운 연결 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 상단 메뉴에서 &lt;b&gt;Database &amp;gt; New Database Connection&lt;/b&gt; 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 데이터베이스 목록에서 &lt;b&gt;PostgreSQL&lt;/b&gt; 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;1164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xfQy3/btsP4egDgV9/bHy1YkRcoWgf1PysdUNHEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xfQy3/btsP4egDgV9/bHy1YkRcoWgf1PysdUNHEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xfQy3/btsP4egDgV9/bHy1YkRcoWgf1PysdUNHEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxfQy3%2FbtsP4egDgV9%2FbHy1YkRcoWgf1PysdUNHEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;486&quot; height=&quot;406&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;1164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;758&quot; data-start=&quot;710&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;758&quot; data-start=&quot;710&quot; data-ke-size=&quot;size16&quot;&gt;3. 연결 정보 입력&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;587&quot; data-end=&quot;708&quot;&gt;
&lt;li data-start=&quot;587&quot; data-end=&quot;608&quot;&gt;Host: localhost&lt;/li&gt;
&lt;li data-start=&quot;612&quot; data-end=&quot;628&quot;&gt;Port: 5432&lt;/li&gt;
&lt;li data-start=&quot;632&quot; data-end=&quot;655&quot;&gt;Database: postgres&lt;/li&gt;
&lt;li data-start=&quot;659&quot; data-end=&quot;680&quot;&gt;User: test_user&lt;/li&gt;
&lt;li data-start=&quot;684&quot; data-end=&quot;708&quot;&gt;Password: testpass&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;1260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nBVpd/btsP4ffv7jD/uis4pBCGaKbwiWVb3dWKb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nBVpd/btsP4ffv7jD/uis4pBCGaKbwiWVb3dWKb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nBVpd/btsP4ffv7jD/uis4pBCGaKbwiWVb3dWKb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnBVpd%2FbtsP4ffv7jD%2Fuis4pBCGaKbwiWVb3dWKb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;591&quot; height=&quot;532&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;1260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;758&quot; data-start=&quot;710&quot; data-ke-size=&quot;size16&quot;&gt;  &amp;ldquo;Test Connection&amp;rdquo; 버튼 클릭 &amp;rarr; 성공 메시지가 뜨면 Finish&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQavGz/btsP4w87LiQ/RpprHCYXU6Tcz1HPgcNNT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQavGz/btsP4w87LiQ/RpprHCYXU6Tcz1HPgcNNT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQavGz/btsP4w87LiQ/RpprHCYXU6Tcz1HPgcNNT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQavGz%2FbtsP4w87LiQ%2FRpprHCYXU6Tcz1HPgcNNT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;347&quot; height=&quot;198&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;780&quot; data-start=&quot;765&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3️⃣ 데이터 확인하기&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;898&quot; data-start=&quot;781&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;809&quot; data-start=&quot;781&quot;&gt;좌측 패널에 test_db가 나타납니다. (public 하위에서 확인할 수 있음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;969&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqnvmc/btsP6dtYfGi/TWorz7yJnV75QopTmz9r41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqnvmc/btsP6dtYfGi/TWorz7yJnV75QopTmz9r41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqnvmc/btsP6dtYfGi/TWorz7yJnV75QopTmz9r41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqnvmc%2FbtsP6dtYfGi%2FTWorz7yJnV75QopTmz9r41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;384&quot; height=&quot;482&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;969&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;898&quot; data-start=&quot;781&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;861&quot; data-start=&quot;810&quot;&gt;users 테이블을 더블클릭 &amp;rarr; &lt;b&gt;Data 탭&lt;/b&gt;에서 GUI로 데이터 확인 가능&lt;/li&gt;
&lt;li data-end=&quot;898&quot; data-start=&quot;862&quot;&gt;SQL Editor에서 직접 쿼리를 실행할 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR4Jqb/btsP4nYUlKm/rCR1WqhwqRNV28C8mnfnOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR4Jqb/btsP4nYUlKm/rCR1WqhwqRNV28C8mnfnOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR4Jqb/btsP4nYUlKm/rCR1WqhwqRNV28C8mnfnOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR4Jqb%2FbtsP4nYUlKm%2FrCR1WqhwqRNV28C8mnfnOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;487&quot; height=&quot;198&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4️⃣ JSONB 데이터도 쉽게&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 만든 products 테이블도 확인해봅시다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1141&quot; data-start=&quot;1035&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1095&quot; data-start=&quot;1035&quot;&gt;JSON 컬럼은 DBeaver에서 &lt;b&gt;계층 구조(Tree)&lt;/b&gt; 로 확인할 수 있어 가독성이 좋습니다.&lt;/li&gt;
&lt;li data-end=&quot;1141&quot; data-start=&quot;1096&quot;&gt;PostgreSQL의 장점인 &lt;b&gt;JSONB 쿼리&lt;/b&gt;도 GUI에서 실행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;3622&quot; data-start=&quot;3613&quot; data-ke-size=&quot;size23&quot;&gt;  마무리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3796&quot; data-start=&quot;3623&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3675&quot; data-start=&quot;3623&quot;&gt;PostgreSQL은 &lt;b&gt;관계형 DB의 안정성 + NoSQL의 유연성&lt;/b&gt;을 동시에 제공&lt;/li&gt;
&lt;li data-end=&quot;3700&quot; data-start=&quot;3676&quot;&gt;Docker로 손쉽게 환경 구성 가능&lt;/li&gt;
&lt;li data-end=&quot;3755&quot; data-start=&quot;3701&quot;&gt;CRUD, JSONB, View, 트랜잭션 등 기본 기능을 실습하며 특징을 체감할 수 있음&lt;/li&gt;
&lt;li data-end=&quot;3796&quot; data-start=&quot;3756&quot;&gt;DBeaver 같은 GUI 툴을 이용하면 더욱 편리하게 학습 가능&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Data/PostgreSQL</category>
      <category>dbeaver</category>
      <category>DBeaver로 PostgreSQL</category>
      <category>PostgreSQL</category>
      <category>PostgreSQL 개념</category>
      <category>PostgreSQL의 특징</category>
      <category>PostgreSQL이란?</category>
      <author>누구세연</author>
      <guid isPermaLink="true">https://sy-develop-note.tistory.com/169</guid>
      <comments>https://sy-develop-note.tistory.com/169#entry169comment</comments>
      <pubDate>Sun, 24 Aug 2025 20:54:20 +0900</pubDate>
    </item>
    <item>
      <title>방화벽 인바운드(Inbound), 아웃바운드(Outbound) 이해하기</title>
      <link>https://sy-develop-note.tistory.com/168</link>
      <description>&lt;h3 data-end=&quot;158&quot; data-start=&quot;146&quot; data-ke-size=&quot;size23&quot;&gt;  방화벽이란?&lt;/h3&gt;
&lt;p data-end=&quot;331&quot; data-start=&quot;159&quot; data-ke-size=&quot;size16&quot;&gt;서비스를&amp;nbsp;운영하다&amp;nbsp;보면&amp;nbsp;항상&amp;nbsp;고민하게&amp;nbsp;되는&amp;nbsp;질문이&amp;nbsp;하나&amp;nbsp;있습니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&quot;우리 서버는 외부 공격으로부터 안전할까?&quot;&lt;br /&gt;&lt;br /&gt;이 질문에 가장 기본적이면서도 강력한 답을 주는 것이 바로&lt;b&gt; 방화벽(Firewall)&lt;/b&gt;입니다.&lt;br /&gt;&lt;br /&gt;방화벽은 네트워크의 문지기 역할을 하며, 외부에서 들어오는 트래픽과 내부에서 나가는 트래픽을 감시하고 필터링합니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;331&quot; data-start=&quot;159&quot; data-ke-style=&quot;style2&quot;&gt;쉽게 말해, 방화벽은 &amp;ldquo;네트워크의 문지기&amp;rdquo;입니다.&lt;br /&gt;어떤 손님(트래픽)을 들이고, 어떤 손님은 돌려보낼지를 규칙(Rule)에 따라 결정됩니다! &lt;/blockquote&gt;
&lt;p data-end=&quot;331&quot; data-start=&quot;159&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;331&quot; data-start=&quot;159&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;331&quot; data-start=&quot;159&quot; data-ke-size=&quot;size23&quot;&gt;인바운드(Inbound) vs 아웃바운드(Outbound)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 방화벽 규칙은 &lt;b&gt;트래픽이 오가는 방향&lt;/b&gt;을 기준으로 두 가지로 나뉩니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 72px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;방향&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;예시&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;비유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;인바운드&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;외부 &amp;rarr; 내부&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;사용자가 서버 웹페이지 접속, SSH 접속 요청&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;집 문을 두드리는 손님&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;아웃바운드&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;내부 &amp;rarr; 외부&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;서버에서 외부 API 호출, 업데이트 다운로드&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;내가 집 밖으로 나가는 상황&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인바운드(Inbound)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;841&quot; data-start=&quot;721&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;758&quot; data-start=&quot;721&quot;&gt;외부 사용자가 &lt;b&gt;내 서버/PC로 접속하려고 할 때&lt;/b&gt; 발생&lt;/li&gt;
&lt;li data-end=&quot;805&quot; data-start=&quot;759&quot;&gt;예: 웹서버에 HTTP(80번 포트) 요청, SSH(22번 포트) 접속 시도&lt;/li&gt;
&lt;li data-end=&quot;841&quot; data-start=&quot;806&quot;&gt;&lt;b&gt;보안 포인트&lt;/b&gt;: 꼭 필요한 포트만 허용, 나머지는 차단&lt;/li&gt;
&lt;li data-end=&quot;841&quot; data-start=&quot;806&quot;&gt;인바운드(Inbound)&amp;nbsp;예시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;841&quot; data-start=&quot;806&quot;&gt;클라이언트가&amp;nbsp;브라우저에서&amp;nbsp;`&lt;a href=&quot;https://example.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://example.com&lt;/a&gt;`&amp;nbsp;접속&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr; 443 포트로 서버에 요청 (인바운드 트래픽)&lt;/li&gt;
&lt;li data-end=&quot;841&quot; data-start=&quot;806&quot;&gt;&amp;nbsp;외부 개발자가 SSH로 원격 접속 시도 (`22번 포트` 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;아웃바운드(Outbound)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;982&quot; data-start=&quot;867&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;896&quot; data-start=&quot;867&quot;&gt;내가 &lt;b&gt;외부 서버에 요청을 보낼 때&lt;/b&gt; 발생&lt;/li&gt;
&lt;li data-end=&quot;933&quot; data-start=&quot;897&quot;&gt;예: 서버에서 외부 API 호출, 패키지 업데이트 다운로드&lt;/li&gt;
&lt;li data-end=&quot;982&quot; data-start=&quot;934&quot;&gt;&lt;b&gt;보안 포인트&lt;/b&gt;: 악성코드&amp;middot;랜섬웨어가 외부로 데이터를 빼돌리지 않도록 제한 가능&lt;/li&gt;
&lt;li data-end=&quot;982&quot; data-start=&quot;934&quot;&gt;아웃바운드(Outbound)&amp;nbsp;예시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;982&quot; data-start=&quot;934&quot;&gt;&amp;nbsp;백엔드 서버가 외부 날씨 API(`api.weather.com`)로 요청 보냄&lt;/li&gt;
&lt;li data-end=&quot;982&quot; data-start=&quot;934&quot;&gt;서버가 npm install이나 apt update로 패키지를 내려받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  네트워크 흐름 예시&lt;/h3&gt;
&lt;pre id=&quot;code_1755050295644&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[외부 사용자] --(인바운드)--&amp;gt; [내부 서버]
[내부 서버] --(아웃바운드)--&amp;gt; [외부 API 서버]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1112&quot; data-start=&quot;1076&quot;&gt;인바운드 규칙: 외부에서 내 서버로 들어오는 요청을 필터링&lt;/li&gt;
&lt;li data-end=&quot;1147&quot; data-start=&quot;1113&quot;&gt;아웃바운드 규칙: 내 서버에서 외부로 나가는 요청을 필터링&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  실무에서 자주 겪는 방화벽 이슈&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인바운드 포트 차단 &amp;rarr; 외부에서 서버 접근 불가&lt;/li&gt;
&lt;li&gt;아웃바운드 차단 &amp;rarr; 서버에서 외부 API 호출 실패&lt;/li&gt;
&lt;li&gt;SSH 포트를 전체 IP에 열어둠 &amp;rarr; 보안 취약점 발생&lt;/li&gt;
&lt;li&gt;방화벽 설정 실수로 실서버 트래픽 끊김 &amp;rarr; 장애 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;방화벽&amp;nbsp;설정&amp;nbsp;시&amp;nbsp;꼭&amp;nbsp;기억할&amp;nbsp;점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기본은 차단, 필요한 것만 허용&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;보안의&amp;nbsp;기본&amp;nbsp;원칙은&amp;nbsp;&quot;Deny&amp;nbsp;All,&amp;nbsp;Allow&amp;nbsp;Some&quot;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;예:&amp;nbsp;HTTP(80),&amp;nbsp;HTTPS(443),&amp;nbsp;SSH(22)만&amp;nbsp;열고&amp;nbsp;나머지는&amp;nbsp;닫기&lt;br /&gt;&lt;br /&gt;2. 인바운드는 최대한 좁게 열기&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;외부에서&amp;nbsp;접근&amp;nbsp;가능한&amp;nbsp;포트는&amp;nbsp;해커&amp;nbsp;입장에선&amp;nbsp;진입&amp;nbsp;지점&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;웹서버라면&amp;nbsp;80,&amp;nbsp;443만&amp;nbsp;허용&lt;br /&gt;&lt;br /&gt;3. 아웃바운드도 통제가 필요함&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 보통은 다 열어두지만, 이 경로로 데이터 유출이 가능&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;기업&amp;nbsp;보안에선&amp;nbsp;아웃바운드도&amp;nbsp;허용&amp;nbsp;목록&amp;nbsp;기반&amp;nbsp;관리함&lt;br /&gt;&lt;br /&gt;4. UDP보다 TCP가 일반적으로 더 안전&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;TCP는&amp;nbsp;연결&amp;nbsp;기반이라&amp;nbsp;추적&amp;middot;제어가&amp;nbsp;쉬움&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;DNS&amp;nbsp;같은&amp;nbsp;UDP&amp;nbsp;트래픽은&amp;nbsp;따로&amp;nbsp;주의&amp;nbsp;필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;방화벽은 단순히 외부 트래픽을 막는 게 아니라, 서비스의 생명줄을 지키는 중요한 보안 도구입니다.&lt;br /&gt;무조건 막는 것도, 무조건 여는 것도 위험합니다.&lt;br /&gt;반드시 필요한 포트만 허용하고, 변경 시에는 이중 체크가 필요합니다.&lt;br /&gt;네트워크 문제 발생 시, 방화벽 설정을 의심해 보는 습관을 들이면 디버깅 속도가 훨씬 빨라질 수 있습니다. &lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infra</category>
      <category>INBOUND</category>
      <category>Outbound</category>
      <category>방화벽</category>
      <category>방화벽 아웃바운드</category>
      <category>방화벽 인바운드</category>
      <category>인바운드 아웃바운드</category>
      <author>누구세연</author>
      <guid isPermaLink="true">https://sy-develop-note.tistory.com/168</guid>
      <comments>https://sy-develop-note.tistory.com/168#entry168comment</comments>
      <pubDate>Sun, 17 Aug 2025 22:30:56 +0900</pubDate>
    </item>
    <item>
      <title>MapStruct와 ObjectMapper, 언제 어떤 걸 써야 할까?</title>
      <link>https://sy-develop-note.tistory.com/167</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DTO &amp;harr; Entity 변환, 여러분은 어떻게 처리하고 계신가요? &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 개발자들이 처음에는 직접 생성자나 Builder를 사용해서 매핑합니다.&lt;br /&gt;필드가 몇 개 없을 때는 괜찮지만, 점점 DTO/Entity 구조가 복잡해지고, 중복 코드가 많아지면서 유지보수가 어려워지는 것 같습니다.&lt;br /&gt;&lt;br /&gt;이럴 때 보통 두 가지 대안이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;ObjectMapper&lt;/b&gt;: 빠르고 간편하게 쓸 수 있는 JSON 매핑 도구&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MapStruct&lt;/b&gt;: 컴파일 타임에 타입 안전하게 매핑 코드를 생성해 주는 프레임워크&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 두 방식의 차이점과 실무에서&amp;nbsp;어떤&amp;nbsp;상황에&amp;nbsp;어떤&amp;nbsp;방식을&amp;nbsp;선택하는&amp;nbsp;것이&amp;nbsp;좋은지&amp;nbsp;정리해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ObjectMapper란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ObjectMapper는 Jackson 라이브러리에서 제공하는 객체-JSON 변환 도구입니다.&lt;br /&gt;(사실 JSON 직렬화/역직렬화가 주목적이지만, 내부적으로 필드 기반 매핑도 지원해요.)&lt;/p&gt;
&lt;pre id=&quot;code_1753274515140&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ObjectMapper objectMapper = new ObjectMapper();
UserDto dto = objectMapper.convertValue(userEntity, UserDto.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1012&quot; data-start=&quot;1004&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;✅ 장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1066&quot; data-start=&quot;1013&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1036&quot; data-start=&quot;1013&quot;&gt;개발이 빠름 (코드 1줄로 매핑 가능)&lt;/li&gt;
&lt;li data-end=&quot;1066&quot; data-start=&quot;1037&quot;&gt;유연한 타입 변환 지원 (JSON과 궁합이 좋음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1076&quot; data-start=&quot;1068&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;❌ 단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1155&quot; data-start=&quot;1077&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1099&quot; data-start=&quot;1077&quot;&gt;리플렉션 기반이라 &lt;b&gt;성능이 느림&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1132&quot; data-start=&quot;1100&quot;&gt;런타임 오류 발생 가능 (컴파일 타임에 안전하지 않음)&lt;/li&gt;
&lt;li data-end=&quot;1155&quot; data-start=&quot;1133&quot;&gt;필드명이 다르면 동작 안 하거나 에러&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MapStruct란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MapStruct는 Java Annotation 기반의 &lt;b&gt;정적 매핑 코드 생성기&lt;/b&gt;입니다.&lt;br /&gt;런타임이 아니라 &lt;b&gt;컴파일 타임에 매핑 코드를 만들어주는&lt;/b&gt; 게 핵심입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1753274548036&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Mapper(componentModel = &quot;spring&quot;)
public interface UserMapper {
    UserDto toDto(User user);
    User toEntity(UserDto dto);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 위처럼 인터페이스만 정의하면,&lt;br /&gt;UserMapperImpl 클래스가 자동으로 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1489&quot; data-start=&quot;1481&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;✅ 장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1553&quot; data-start=&quot;1490&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1507&quot; data-start=&quot;1490&quot;&gt;빠른 성능 (리플렉션 없음)&lt;/li&gt;
&lt;li data-end=&quot;1533&quot; data-start=&quot;1508&quot;&gt;타입 안정성 보장 (컴파일 에러로 알려줌)&lt;/li&gt;
&lt;li data-end=&quot;1553&quot; data-start=&quot;1534&quot;&gt;코드 자동 생성, 유지보수 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1563&quot; data-start=&quot;1555&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;❌ 단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1661&quot; data-start=&quot;1564&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1612&quot; data-start=&quot;1564&quot;&gt;복잡한 매핑은 추가 설정 필요 (@Mapping, custom method 등)&lt;/li&gt;
&lt;li data-end=&quot;1661&quot; data-start=&quot;1613&quot;&gt;사용 초기 진입장벽이 약간 있음 (라이브러리 설정, Gradle, lombok 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  성능 비교 &amp;ndash; 실제 코드 예시로 보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 UserDto &amp;harr; User 간 변환을 &lt;b&gt;100만 번 반복&lt;/b&gt;하여 실행 시간을 측정한 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1️⃣ 테스트 대상 클래스&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DJ69Q/btsPw2zYC08/KbM0hgskaKrVOc0ULXQnK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DJ69Q/btsPw2zYC08/KbM0hgskaKrVOc0ULXQnK1/img.png&quot; data-alt=&quot;User class&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DJ69Q/btsPw2zYC08/KbM0hgskaKrVOc0ULXQnK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDJ69Q%2FbtsPw2zYC08%2FKbM0hgskaKrVOc0ULXQnK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;326&quot; height=&quot;278&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;User class&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckto0G/btsPw3yT3Dd/BIXBpz2aKzoNm1Ah2pFeRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckto0G/btsPw3yT3Dd/BIXBpz2aKzoNm1Ah2pFeRK/img.png&quot; data-alt=&quot;UserDto class&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckto0G/btsPw3yT3Dd/BIXBpz2aKzoNm1Ah2pFeRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fckto0G%2FbtsPw3yT3Dd%2FBIXBpz2aKzoNm1Ah2pFeRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;393&quot; height=&quot;286&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;UserDto class&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2️⃣ MapStruct 매퍼 정의&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-07-23 오후 10.13.51.png&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1k2Lp/btsPwXr4uzx/GEVCFULMlMlapfwJWVvxa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1k2Lp/btsPwXr4uzx/GEVCFULMlMlapfwJWVvxa1/img.png&quot; data-alt=&quot;UserMapper class&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1k2Lp/btsPwXr4uzx/GEVCFULMlMlapfwJWVvxa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1k2Lp%2FbtsPwXr4uzx%2FGEVCFULMlMlapfwJWVvxa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;523&quot; height=&quot;540&quot; data-filename=&quot;스크린샷 2025-07-23 오후 10.13.51.png&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;UserMapper class&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3️⃣ 성능 측정 테스트 코드&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-07-23 오후 10.14.09.png&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;1206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o2gJH/btsPwrUGy3T/mkENSkEjwzXEXfdSF0eVG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o2gJH/btsPwrUGy3T/mkENSkEjwzXEXfdSF0eVG0/img.png&quot; data-alt=&quot;Test class&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o2gJH/btsPwrUGy3T/mkENSkEjwzXEXfdSF0eVG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo2gJH%2FbtsPwrUGy3T%2FmkENSkEjwzXEXfdSF0eVG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;1206&quot; data-filename=&quot;스크린샷 2025-07-23 오후 10.14.09.png&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;1206&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Test class&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;⏱️ 결과&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-07-23 오후 10.19.59.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQE1aY/btsPv9UcDdH/1nz8ph1KgdgDxSi2uHXvQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQE1aY/btsPv9UcDdH/1nz8ph1KgdgDxSi2uHXvQ1/img.png&quot; data-alt=&quot;console log&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQE1aY/btsPv9UcDdH/1nz8ph1KgdgDxSi2uHXvQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQE1aY%2FbtsPv9UcDdH%2F1nz8ph1KgdgDxSi2uHXvQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;473&quot; height=&quot;82&quot; data-filename=&quot;스크린샷 2025-07-23 오후 10.19.59.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;console log&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플렉션을 사용하는 ObjectMapper는 상대적으로 느리고, MapStruct는 단순 메서드 호출 수준이기 때문에 훨씬 빠릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;실시간 트래픽이 많은 시스템이나 대용량 데이터 처리 시에는 MapStruct가 유리&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 어떤 걸 써야 할까?  &lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;상황&lt;/td&gt;
&lt;td style=&quot;width: 31.9767%;&quot;&gt;추천 방식&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 39.3023%;&quot;&gt;이유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;개발 초기, 필드 적음&lt;/td&gt;
&lt;td style=&quot;width: 31.9767%;&quot;&gt;수동 Builder&lt;/td&gt;
&lt;td style=&quot;width: 39.3023%;&quot;&gt;빠르고 직관적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;빠르게 프로토타이핑&lt;/td&gt;
&lt;td style=&quot;width: 31.9767%;&quot;&gt;ObjectMapper&lt;/td&gt;
&lt;td style=&quot;width: 39.3023%;&quot;&gt;코드 1~2줄로 끝&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;성능/안정성 중요&lt;/td&gt;
&lt;td style=&quot;width: 31.9767%;&quot;&gt;MapStruct&lt;/td&gt;
&lt;td style=&quot;width: 39.3023%;&quot;&gt;정적 코드, 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;필드명이 다름&lt;/td&gt;
&lt;td style=&quot;width: 31.9767%;&quot;&gt;MapStruct (@Mapping)&lt;/td&gt;
&lt;td style=&quot;width: 39.3023%;&quot;&gt;명시적 매핑 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;커스텀 로직 포함&lt;/td&gt;
&lt;td style=&quot;width: 31.9767%;&quot;&gt;MapStruct + default method&lt;/td&gt;
&lt;td style=&quot;width: 39.3023%;&quot;&gt;로직 분리 깔끔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;JSON &amp;harr; 객체 변환&lt;/td&gt;
&lt;td style=&quot;width: 31.9767%;&quot;&gt;ObjectMapper&lt;/td&gt;
&lt;td style=&quot;width: 39.3023%;&quot;&gt;원래 목적에 딱 맞음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;실무에서는 대부분 아래와 같은 흐름을 거칩니다.&lt;br /&gt;Builder로 수동 매핑 &amp;rarr; 코드 많아짐 &amp;rarr; ObjectMapper 써봄 &amp;rarr; 성능 이슈 발생 &amp;rarr; MapStruct 도입!&lt;br /&gt;&lt;br /&gt;복잡한 매핑이 많아질수록, 그리고 성능이 중요할수록 MapStruct를 일찍 도입하는 게 장기적으로 이득이에요.&lt;br /&gt;ObjectMapper는 여전히 JSON 변환이나 간단한 DTO 테스트용으로는 아주 훌륭합니다.&lt;br /&gt;핵심은 &amp;ldquo;상황에 맞는 도구를 쓰는 것&amp;rdquo;이라는 점을 기억하면 좋을 것 같습니다!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;mapstruct 공식 문서&quot; href=&quot;https://mapstruct.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mapstruct.org/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753277245941&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MapStruct &amp;ndash; Java bean mappings, the easy way!&quot; data-og-description=&quot;MapStruct Spring Extensions 1.1.3 released March 14, 2025 It is my pleasure to announce the next official release of MapStruct Spring Extensions. What started out as a StackOverflow question turned into its own (sub-)project within the MapStruct organizati&quot; data-og-host=&quot;mapstruct.org&quot; data-og-source-url=&quot;https://mapstruct.org/&quot; data-og-url=&quot;https://mapstruct.org/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cLIz8i/hyZnh9tc3I/g6z5FlHc4guH3EpIuqkjKK/img.png?width=2177&amp;amp;height=600&amp;amp;face=0_0_2177_600&quot;&gt;&lt;a href=&quot;https://mapstruct.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mapstruct.org/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cLIz8i/hyZnh9tc3I/g6z5FlHc4guH3EpIuqkjKK/img.png?width=2177&amp;amp;height=600&amp;amp;face=0_0_2177_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MapStruct &amp;ndash; Java bean mappings, the easy way!&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MapStruct Spring Extensions 1.1.3 released March 14, 2025 It is my pleasure to announce the next official release of MapStruct Spring Extensions. What started out as a StackOverflow question turned into its own (sub-)project within the MapStruct organizati&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mapstruct.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/FasterXML/jackson&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/FasterXML/jackson&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753277252493&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - FasterXML/jackson: Main Portal page for the Jackson project&quot; data-og-description=&quot;Main Portal page for the Jackson project. Contribute to FasterXML/jackson development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/FasterXML/jackson&quot; data-og-url=&quot;https://github.com/FasterXML/jackson&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/A0wPa/hyZqOYRkxw/KynAmhk4bcSn0wafh9xF7K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/tFjxt/hyZnhIpYmd/WNJCokcBGO9UzaEiIRHQt1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/FasterXML/jackson&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/FasterXML/jackson&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/A0wPa/hyZqOYRkxw/KynAmhk4bcSn0wafh9xF7K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/tFjxt/hyZnhIpYmd/WNJCokcBGO9UzaEiIRHQt1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - FasterXML/jackson: Main Portal page for the Jackson project&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Main Portal page for the Jackson project. Contribute to FasterXML/jackson development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/Java</category>
      <category>mapstruct</category>
      <category>MapStruct ObjectMapper</category>
      <category>MapStruct ObjectMapper 성능 비교</category>
      <category>MapStruct와 ObjectMapper</category>
      <category>objectmapper</category>
      <author>누구세연</author>
      <guid isPermaLink="true">https://sy-develop-note.tistory.com/167</guid>
      <comments>https://sy-develop-note.tistory.com/167#entry167comment</comments>
      <pubDate>Wed, 23 Jul 2025 22:29:02 +0900</pubDate>
    </item>
  </channel>
</rss>