<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>develop</title>
    <link>https://developerhun.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 3 Aug 2026 13:15:20 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>KJihun</managingEditor>
    <item>
      <title>AI Agent란</title>
      <link>https://developerhun.tistory.com/284</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기존 AI가 &quot;질문하면 답하는&quot; 방식이었다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트는 목표를 주면 스스로 계획을 세우고, 도구를 사용하고, 여러 단계를 거쳐 작업을 완수하는 AI이다.&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;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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;에이전트 내부 사고 루프 (Cognitive Architecture)&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Planning (계획): 사용자 요청을 분석하여 이를 달성하기 위한 작은 단위의 단계(Step)들로 나눈다.&lt;/li&gt;
&lt;li&gt;Tool Use (도구 사용): 웹 검색, 코드 실행, 데이터베이스 조회 등 &lt;b&gt;외부 API를 호출&lt;/b&gt;하여 필요한 정보나 결과를 얻는다.&lt;/li&gt;
&lt;li&gt;Action &amp;amp; Observation (실행 및 관찰): 결과를 확인하고, 확인한 내용을 바탕으로&lt;b&gt; ReAct 방식&lt;/b&gt;으로 &lt;b&gt;계획을 수정하거나 다음 단계로 넘어갈지 결정&lt;/b&gt;한다.&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Memory (메모리): 이전 대화 맥락이나 중간 결과물들을 기억하고 참조한다&lt;/li&gt;
&lt;/ol&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;ReAct&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 에이전트는 단일 작업을 처리할 때&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;ReAct(Reason + Act)&amp;nbsp;패턴이 가장 널리 쓰인다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ReAct 란 Reason + Act 의 합성어로, 문제를 해결하는 과정에서 추론과 행동을 결합하는 새로운 패러다임이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존의 언어 모델들이 주로 추론이나 행동 중 하나에 집중했던 반면, ReAct는 이 두 가지 기능을 통합하여 시너지 효과를 창출한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행(Act)&lt;/b&gt;과 그 결과에 대한&amp;nbsp;&lt;b data-path-to-node=&quot;0&quot; data-index-in-node=&quot;64&quot;&gt;관찰(Observation)&lt;/b&gt;, 그리고 이를 바탕으로 한 새로운&amp;nbsp;&lt;b&gt;사고(Thought)&lt;/b&gt;를 반복하여 결과를 도출한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&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;style1&quot; /&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;AI 에이전트에게 &quot;게시판 만들어줘&quot; 라고 요구할 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Planning (계획 단계)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;사용자의 &quot;게시판 만들어줘&quot;라는 추상적인 요청을 &lt;b&gt;실행 가능한 단위&lt;/b&gt;로 쪼갠다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&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-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;사고(Thought):&lt;/b&gt; &quot;게시판을 만들려면 데이터 스키마 정의, API 엔드포인트 구현(CRUD), 프런트엔드 UI 구성이 필요하겠군. 우선 기술 스택을 결정하고 단계별로 진행하자.&quot;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;수립된 계획&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기술 스택 선정 (예: Spring Boot + JPA)&lt;/li&gt;
&lt;li&gt;게시글(Post) 엔티티 및 Repository 생성&lt;/li&gt;
&lt;li&gt;CRUD 기능이 담긴 Controller 및 Service 구현&lt;/li&gt;
&lt;li&gt;동작 확인을 위한 테스트&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Tool Use (도구 사용 및 실행)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;에이전트가 실제로 코드를 작성하거나 파일을 생성하는 도구를 호출한다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;Action (Act):&lt;/b&gt; create_file(&quot;Post.java&quot;, &quot;public class Post { ... }&quot;)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;Action (Act):&lt;/b&gt; create_file(&quot;PostController.java&quot;, &quot;...&quot;)&lt;/li&gt;
&lt;li&gt;에이전트는 이 과정에서 개발 환경에 접근하거나 코드 생성 라이브러리를 사용하게 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Action &amp;amp; Observation (관찰 및 계획 수정)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;도구 사용 후 돌아온 결과를 확인하고 다음 행동을 결정한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;Observation (관찰):&lt;/b&gt; 코드 생성은 완료되었으나, 빌드 도구 실행 중 에러가 발생함을 확인&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;Thought (재판단):&lt;/b&gt; &quot;아, 빌드 설정 파일(pom.xml 또는 build.gradle)에 의존성을 추가하는 단계를 빠뜨렸구나. 계획에 '의존성 체크' 단계를 추가하고 수정해야겠다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;Revised Action (수정된 실행)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Memory (메모리 참조)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;작업 중간중간 이전 단계에서 결정한 사항을 잊지 않고 기억해 불필요하게 비대해지는 설계를 방지한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; 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-path-to-node=&quot;16&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;[Thought]&lt;/b&gt; &quot;게시판 제작 시작. 우선 DB 구조부터 잡자.&quot;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;[Action]&lt;/b&gt; 엔티티 코드 작성 및 파일 저장.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,2,0&quot;&gt;[Observation]&lt;/b&gt; 저장 완료. 문법 오류 없음.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,3,0&quot;&gt;[Thought]&lt;/b&gt; &quot;성공적이야. 이제 이 데이터를 다룰 컨트롤러를 만들 차례군.&quot;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,4,0&quot;&gt;[Action]&lt;/b&gt; 컨트롤러 코드 작성.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,5,0&quot;&gt;[Observation]&lt;/b&gt; 작성 중 특정 라이브러리 참조 에러 발생.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,6,0&quot;&gt;[Thought]&lt;/b&gt; &quot;오타가 있었네. 다시 수정해서 저장하자.&quot; (ReAct에 의한 계획 수정)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,7,0&quot;&gt;[Final Output]&lt;/b&gt; &quot;게시판 기본 코드 구현이 완료되었습니다. Post, Service, Controller 파일을 확인해 보세요.&quot;&lt;/li&gt;
&lt;/ol&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;자료를 찾다보니 어떤 식으로 요청을 해야 토큰 소모량을 효율적으로 사용하고, 정확한 결괏값을 뽑아낼 수 있을지 감이 잡혔다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;AI 에이전트의 특성을 고려하여 &lt;b&gt;하이브리드 방식&lt;/b&gt;이 가장 토큰 효율이 좋다고 판단했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;구조&lt;/b&gt;: 디테일 하게 쪼개지 않고 한번의 요청으로 처리한다&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 구조와 API 명세서 같은 &lt;b data-index-in-node=&quot;39&quot; data-path-to-node=&quot;13,0,0&quot;&gt;설계도&lt;/b&gt;는 한 번의 요청으로 크게 뽑는다&lt;/li&gt;
&lt;li&gt;설계도가 이미 존재한다면 설계도를 분석을 요청하여 컨텍스트를 주입(Context Injection) 시킨다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;구현:&lt;/b&gt; &quot;방금 만든 설계를 바탕으로 PostController의 등록 기능만 구현해줘&quot; 식으로 실제 로직은 쪼개서 요청한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 AI에게 구현하게 시킬 경우, AI가 전체 맥락을 파악한 상태이기 때문에 세부 작업을 수행할 때 일관성도 훨씬 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>KJihun</author>
      <guid isPermaLink="true">https://developerhun.tistory.com/284</guid>
      <comments>https://developerhun.tistory.com/284#entry284comment</comments>
      <pubDate>Wed, 15 Apr 2026 14:19:09 +0900</pubDate>
    </item>
    <item>
      <title>PDF 내보내기 첫 페이지 공백 및 잘림</title>
      <link>https://developerhun.tistory.com/282</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PDF 인쇄 및 출력 시 첫 페이지가 공백으로 출력되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 기준(송장번호)으로 페이지 나누기(page-break-after: always)가 동작하지 않음 &lt;br /&gt;&lt;br /&gt;1. #printDiv에 position: absolute 추가 : 첫 페이지 공백 해결, 페이지 나누기 안 됨&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. #printDiv에 position: fixed 추가첫 페이지 공백 해결, 페이지 나누기 안 됨&lt;br /&gt;3. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;#printDiv에&lt;/span&gt; position 제거: 페이지 나누기 정상, 첫 페이지 공백 재발&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. console.log로 부모 체인 디버깅: 근본 원인 발견&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;br /&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AdminLTE의 CSS가 .content-wrapper에 overflow: hidden을 적용하고 있었음&lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;overflow:&amp;nbsp;hidden&amp;nbsp;&amp;rarr;&amp;nbsp;Block&amp;nbsp;Formatting&amp;nbsp;Context(BFC)&amp;nbsp;생성 &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;BFC&amp;nbsp;내부에서는&amp;nbsp;브라우저&amp;nbsp;인쇄&amp;nbsp;엔진이&amp;nbsp;부모&amp;nbsp;경계를&amp;nbsp;넘는&amp;nbsp;page-break를&amp;nbsp;삽입하지&amp;nbsp;않음 &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;#printDiv가&amp;nbsp;.content-wrapper&amp;nbsp;안에&amp;nbsp;있으므로&amp;nbsp;page-break-after:&amp;nbsp;always가&amp;nbsp;무시됨 &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;그리고&amp;nbsp;visibility:&amp;nbsp;hidden은&amp;nbsp;요소를&amp;nbsp;숨기되&amp;nbsp;공간은&amp;nbsp;유지하므로,&amp;nbsp;position&amp;nbsp;없이는&amp;nbsp;숨겨진&amp;nbsp;요소들의&amp;nbsp;공간이&amp;nbsp;#printDiv를&amp;nbsp;밀어내어&amp;nbsp;첫 &lt;br /&gt;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1775030958657&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @media print {
      .content-wrapper { overflow: visible !important; }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;overflow:&amp;nbsp;visible로&amp;nbsp;BFC를&amp;nbsp;해제하여&amp;nbsp;page-break&amp;nbsp;정상&amp;nbsp;동작 &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;position:&amp;nbsp;absolute로&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;알게 된 것&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;overflow:&amp;nbsp;hidden은&amp;nbsp;자손의&amp;nbsp;page-break를&amp;nbsp;차단한다&amp;nbsp;&amp;mdash;&amp;nbsp;BFC&amp;nbsp;경계를&amp;nbsp;넘는&amp;nbsp;페이지&amp;nbsp;나누기를&amp;nbsp;브라우저가&amp;nbsp;삽입하지&amp;nbsp;않음 &lt;br /&gt;&amp;nbsp;&amp;nbsp;2. position: absolute/fixed는 page-break를 무효화한다 &amp;mdash; 문서 흐름에서 벗어난 요소에 page-break가 적용되지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 단, overflow: visible로 BFC를 해제하면 position: absolute에서도 page-break가 동작함 &lt;br /&gt;&amp;nbsp;&amp;nbsp;3.&amp;nbsp;visibility:&amp;nbsp;hidden은&amp;nbsp;display:&amp;nbsp;none과&amp;nbsp;다르다&amp;nbsp;&amp;mdash;&amp;nbsp;요소를&amp;nbsp;안&amp;nbsp;보이게&amp;nbsp;하지만&amp;nbsp;공간은&amp;nbsp;그대로&amp;nbsp;차지함 &lt;br /&gt;&amp;nbsp;&amp;nbsp;4. 직접 작성한 CSS에 없더라도 프레임워크가 적용한 overflow, transform&amp;nbsp;등이&amp;nbsp;인쇄&amp;nbsp;레이아웃에&amp;nbsp;영향을&amp;nbsp;줌 &lt;br /&gt;&amp;nbsp;&amp;nbsp;5. &lt;b&gt;getComputedStyle&lt;/b&gt;로 부모 체인을 추적하면 원인을 빠르게 찾을 수 있다&lt;/p&gt;</description>
      <author>KJihun</author>
      <guid isPermaLink="true">https://developerhun.tistory.com/282</guid>
      <comments>https://developerhun.tistory.com/282#entry282comment</comments>
      <pubDate>Wed, 1 Apr 2026 16:26:38 +0900</pubDate>
    </item>
    <item>
      <title>[MONITORING] 예외발생 시 Slack 으로 알림 보내기</title>
      <link>https://developerhun.tistory.com/281</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. 코드작성&lt;/p&gt;
&lt;pre id=&quot;code_1775031439344&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum ErrorLevel {
    LOW(Level.INFO, false),
    MEDIUM(Level.WARN, false),
    HIGH(Level.ERROR, true),
    CRITICAL(Level.ERROR, true);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 에러레벨 ENUM으로 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775032354426&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (level.isSlackNotify()) slackNotifier.send(e, request);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- true일 경우 심각한 예외로 지정, slack에 메세지 전달&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;pre id=&quot;code_1775032473520&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SlackNotifier {

    @Value(&quot;${slack.webhook.url:}&quot;)
    private String webhookUrl;

    private final RestClient restClient = RestClient.create();

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd HH:mm:ss&quot;);

    @Async
    public void send(Exception e, HttpServletRequest request) {
        if (webhookUrl == null || webhookUrl.isBlank()) {
            log.warn(&quot;Slack webhook URL이 설정되지 않아 알림을 보내지 않습니다.&quot;);
            return;
        }

        try {
            ErrorLevel level = (e instanceof BaseException be) ? be.getErrorCode().getLevel() : ErrorLevel.CRITICAL;
            String emoji = (level == ErrorLevel.CRITICAL) ? &quot;:rotating_light:&quot; : &quot;:warning:&quot;;
            String mention = (level == ErrorLevel.CRITICAL) ? &quot;&amp;lt;!channel&amp;gt; &quot; : &quot;&quot;;
            String errorCode = (e instanceof BaseException be) ? be.getErrorCode().name() : &quot;INTERNAL_ERROR&quot;;

            String stackTrace = getStackTrace(e, 10);

            String payload = &quot;&quot;&quot;
                    {
                      &quot;blocks&quot;: [
                        {
                          &quot;type&quot;: &quot;header&quot;,
                          &quot;text&quot;: {
                            &quot;type&quot;: &quot;plain_text&quot;,
                            &quot;text&quot;: &quot;%s [%s] 예외 발생&quot;
                          }
                        },
                        {
                          &quot;type&quot;: &quot;section&quot;,
                          &quot;fields&quot;: [
                            { &quot;type&quot;: &quot;mrkdwn&quot;, &quot;text&quot;: &quot;*Error Code:*\\n`%s`&quot; },
                            { &quot;type&quot;: &quot;mrkdwn&quot;, &quot;text&quot;: &quot;*Level:*\\n`%s`&quot; },
                            { &quot;type&quot;: &quot;mrkdwn&quot;, &quot;text&quot;: &quot;*URI:*\\n`%s %s`&quot; },
                            { &quot;type&quot;: &quot;mrkdwn&quot;, &quot;text&quot;: &quot;*Time:*\\n`%s`&quot; }
                          ]
                        },
                        {
                          &quot;type&quot;: &quot;section&quot;,
                          &quot;text&quot;: {
                            &quot;type&quot;: &quot;mrkdwn&quot;,
                            &quot;text&quot;: &quot;*Message:*\\n```%s```&quot;
                          }
                        },
                        {
                          &quot;type&quot;: &quot;section&quot;,
                          &quot;text&quot;: {
                            &quot;type&quot;: &quot;mrkdwn&quot;,
                            &quot;text&quot;: &quot;*StackTrace:*\\n```%s```&quot;
                          }
                        }
                      ],
                      &quot;text&quot;: &quot;%s%s [%s] %s&quot;
                    }
                    &quot;&quot;&quot;.formatted(
                    emoji, level,
                    errorCode,
                    level,
                    request.getMethod(), request.getRequestURI(),
                    LocalDateTime.now().format(FORMATTER),
                    escapeJson(e.getMessage()),
                    escapeJson(stackTrace),
                    mention, emoji, level, escapeJson(e.getMessage())
            );

            restClient.post()
                    .uri(webhookUrl)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(payload)
                    .retrieve()
                    .toBodilessEntity();

            log.info(&quot;Slack 알림 발송 완료 [{}]&quot;, level);
        } catch (Exception ex) {
            log.error(&quot;Slack 알림 발송 실패&quot;, ex);
        }
    }

    private String getStackTrace(Exception e, int maxLines) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        String[] lines = sw.toString().split(&quot;\n&quot;);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i &amp;lt; Math.min(lines.length, maxLines); i++) {
            sb.append(lines[i]).append(&quot;\n&quot;);
        }
        if (lines.length &amp;gt; maxLines) {
            sb.append(&quot;... &quot;).append(lines.length - maxLines).append(&quot; more lines&quot;);
        }
        return sb.toString().trim();
    }

    private String escapeJson(String text) {
        if (text == null) return &quot;null&quot;;
        return text
                .replace(&quot;\\&quot;, &quot;\\\\&quot;)
                .replace(&quot;\&quot;&quot;, &quot;\\\&quot;&quot;)
                .replace(&quot;\n&quot;, &quot;\\n&quot;)
                .replace(&quot;\r&quot;, &quot;\\r&quot;)
                .replace(&quot;\t&quot;, &quot;\\t&quot;);
    }&lt;/code&gt;&lt;/pre&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;&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;2. Slack 워크스페이스 생성 &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;&lt;a href=&quot;https://slack.com/create&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://slack.com/create&lt;/a&gt;&amp;nbsp;접속 &lt;br /&gt;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;이메일&amp;nbsp;입력&amp;nbsp;&amp;rarr;&amp;nbsp;인증코드&amp;nbsp;확인 &lt;br /&gt;&amp;nbsp;&amp;nbsp;3.&amp;nbsp;워크스페이스&amp;nbsp;이름&amp;nbsp;입력&amp;nbsp;(예:&amp;nbsp;dkdk-team) &lt;br /&gt;&amp;nbsp;&amp;nbsp;4.&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;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;&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;br /&gt;1. Slack App 생성 + Webhook 발급 &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;&lt;a href=&quot;https://api.slack.com/apps&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.slack.com/apps&lt;/a&gt;&amp;nbsp;&amp;rarr;&amp;nbsp;Create&amp;nbsp;New&amp;nbsp;App&amp;nbsp;&amp;rarr;&amp;nbsp;From&amp;nbsp;scratch &lt;br /&gt;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;App&amp;nbsp;이름&amp;nbsp;입력&amp;nbsp;(예:&amp;nbsp;dkdk-alert),&amp;nbsp;위에서&amp;nbsp;만든&amp;nbsp;워크스페이스&amp;nbsp;선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-01 133729.png&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;669&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6XZD9/dJMcaflM3wx/zO0plImki9AOjwklHvkGok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6XZD9/dJMcaflM3wx/zO0plImki9AOjwklHvkGok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6XZD9/dJMcaflM3wx/zO0plImki9AOjwklHvkGok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6XZD9%2FdJMcaflM3wx%2FzO0plImki9AOjwklHvkGok%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;645&quot; height=&quot;669&quot; data-filename=&quot;스크린샷 2026-04-01 133729.png&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;669&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;3. Incoming Webhooks &amp;rarr; 토글 On&amp;nbsp; &amp;rarr; Add&amp;nbsp;New&amp;nbsp;Webhook&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;1270&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8FbJq/dJMcah4Y0YD/GLl2wWBEIrBFNm6mbmYjS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8FbJq/dJMcah4Y0YD/GLl2wWBEIrBFNm6mbmYjS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8FbJq/dJMcah4Y0YD/GLl2wWBEIrBFNm6mbmYjS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8FbJq%2FdJMcah4Y0YD%2FGLl2wWBEIrBFNm6mbmYjS0%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;1270&quot; height=&quot;683&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-01 134104.png&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xD4ge/dJMcagE1meO/40NaSKWGk3Mfm2ApOYJlH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xD4ge/dJMcagE1meO/40NaSKWGk3Mfm2ApOYJlH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xD4ge/dJMcagE1meO/40NaSKWGk3Mfm2ApOYJlH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxD4ge%2FdJMcagE1meO%2F40NaSKWGk3Mfm2ApOYJlH1%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;945&quot; height=&quot;860&quot; data-filename=&quot;스크린샷 2026-04-01 134104.png&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;860&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-01 134228.png&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;775&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/phY4u/dJMcab4KwKh/1TJQRGyJ8i8ttk3ip5tOnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/phY4u/dJMcab4KwKh/1TJQRGyJ8i8ttk3ip5tOnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/phY4u/dJMcab4KwKh/1TJQRGyJ8i8ttk3ip5tOnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FphY4u%2FdJMcab4KwKh%2F1TJQRGyJ8i8ttk3ip5tOnK%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;1137&quot; height=&quot;775&quot; data-filename=&quot;스크린샷 2026-04-01 134228.png&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;775&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;5.&amp;nbsp;생성된&amp;nbsp;Webhook&amp;nbsp;URL&amp;nbsp;복사 &lt;br /&gt;&lt;br /&gt;&amp;nbsp; 6. 설정에 붙여넣기&lt;br /&gt;&amp;nbsp;&amp;nbsp;slack.webhook.url=&lt;a href=&quot;https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX&lt;/a&gt;&lt;/p&gt;</description>
      <category>Monitoring</category>
      <author>KJihun</author>
      <guid isPermaLink="true">https://developerhun.tistory.com/281</guid>
      <comments>https://developerhun.tistory.com/281#entry281comment</comments>
      <pubDate>Wed, 1 Apr 2026 16:26:12 +0900</pubDate>
    </item>
    <item>
      <title>[MONITORING] Prometheus + Grafana + docker</title>
      <link>https://developerhun.tistory.com/280</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp;Spring Boot에 메트릭 엔드포인트 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;build.gradle에 Actuator + Micrometer 의존성 추가&lt;/p&gt;
&lt;pre id=&quot;code_1774932421877&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // Monitoring
  implementation 'org.springframework.boot:spring-boot-starter-actuator'
  implementation 'io.micrometer:micrometer-registry-prometheus'&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;application.yml(또는 .properties)에 엔드포인트 노출&lt;/p&gt;
&lt;pre id=&quot;code_1774932476756&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  management:
	endpoints:
      web:
        exposure:
          include: health, info, prometheus
    metrics:
      tags:
        application: dkdk-backend&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Prometheus&amp;nbsp;+&amp;nbsp;Grafana를&amp;nbsp;Docker&amp;nbsp;Compose에&amp;nbsp;추가&lt;/h3&gt;
&lt;pre id=&quot;code_1774932529925&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  prometheus:
    image: prom/prometheus:latest
    container_name: dkdk_prometheus
    restart: always
    ports:
      - &quot;9090:9090&quot;
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    extra_hosts:
      - &quot;host.docker.internal:host-gateway&quot;

  grafana:
    image: grafana/grafana:latest
    container_name: dkdk_grafana
    restart: always
    ports:
      - &quot;3001:3000&quot;
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      GF_SECURITY_ADMIN_USER: admin
      GF_SECURITY_ADMIN_PASSWORD: admin
    depends_on:
      - prometheus&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Docker&amp;nbsp;Compose&amp;nbsp;실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접속 URL &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Prometheus:&amp;nbsp;http://localhost:9090 &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Grafana:&amp;nbsp;http://localhost:3001&amp;nbsp;(ID:&amp;nbsp;admin&amp;nbsp;/&amp;nbsp;PW:&amp;nbsp;admin) &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;메트릭&amp;nbsp;원본:&amp;nbsp;http://localhost:8080/actuator/prometheus &lt;br /&gt;&lt;br /&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;4. Grafana에서 Prometheus 데이터소스 연결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;Grafana&amp;nbsp;로그인&amp;nbsp;&amp;rarr;&amp;nbsp;좌측&amp;nbsp;메뉴&amp;nbsp;&amp;rarr;&amp;nbsp;Connections&amp;nbsp;&amp;rarr;&amp;nbsp;Data&amp;nbsp;Sources&amp;nbsp;&amp;rarr;&amp;nbsp;Add&amp;nbsp;data&amp;nbsp;source &lt;br /&gt;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;Prometheus&amp;nbsp;선택 &lt;br /&gt;&amp;nbsp;&amp;nbsp;3.&amp;nbsp;URL에&amp;nbsp;http://prometheus:9090&amp;nbsp;입력&amp;nbsp;&amp;rarr;&amp;nbsp;Save&amp;nbsp;&amp;amp;&amp;nbsp;Test &lt;br /&gt;&lt;br /&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;5. 대시보드 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Grafana&amp;nbsp;좌측&amp;nbsp;&amp;rarr;&amp;nbsp;Dashboards&amp;nbsp;&amp;rarr;&amp;nbsp;Import &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Dashboard&amp;nbsp;ID&amp;nbsp;4701&amp;nbsp;입력&amp;nbsp;(JVM&amp;nbsp;Micrometer&amp;nbsp;대시보드)&amp;nbsp;&amp;rarr;&amp;nbsp;Load&amp;nbsp;&amp;rarr;&amp;nbsp;Prometheus&amp;nbsp;데이터소스&amp;nbsp;선택&amp;nbsp;&amp;rarr;&amp;nbsp;Import&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;이후 JVM 메모리, GC, HTTP 요청 수/응답시간 등을 실시간으로 모니터링할 수 있다&lt;/p&gt;</description>
      <category>Monitoring</category>
      <author>KJihun</author>
      <guid isPermaLink="true">https://developerhun.tistory.com/280</guid>
      <comments>https://developerhun.tistory.com/280#entry280comment</comments>
      <pubDate>Tue, 31 Mar 2026 13:51:08 +0900</pubDate>
    </item>
    <item>
      <title>시스템 설계(모놀리식 vs 마이크로서비스 아키텍처)</title>
      <link>https://developerhun.tistory.com/276</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 Spring Batch와 RabbitMQ를 활용하며 대규모 트래픽 처리와 대용량 데이터 처리에 관심이 생겼다. 그러다&lt;/p&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;/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 data-ke-style=&quot;style1&quot; /&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;b&gt;시스템 설계란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 설계는 웹 서비스가 트래픽이 많아지거나 데이터량이 커지더라도&lt;/p&gt;
&lt;p 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-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스가 커질수록 단순한 CRUD 구조만으로는 한계가 생긴다.&lt;/li&gt;
&lt;li&gt;성능, 확장성, 신뢰성, 유지보수성을 고려하여 구조를 잡아야 한다.&lt;/li&gt;
&lt;li&gt;실제로는 하나의 서버/DB로는 부족해지며 여러 컴포넌트를 나누고 연결해야 한다.&lt;/li&gt;
&lt;/ul&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;모놀리식 아키텍처와 마이크로서비스 아키텍처(MSA)&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;&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 data-ke-style=&quot;style1&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모놀리식 아키텍처 (Monolithic Architecture)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 기능(UI, 비즈니스 로직, 데이터 처리 등)을 &lt;b&gt;하나의 애플리케이션 코드베이스&lt;/b&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-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 단일 DB와 단일 서버 구조를 사용한다&lt;/li&gt;
&lt;li&gt;동일 애플리케이션을 여러 서버에 배포하면 로드밸런싱은 가능하지만, 구조는 여전히 일체형이다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;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;/li&gt;
&lt;li&gt;API 통신이 필요 없어서 성능 측면에서 단순하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&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;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;/li&gt;
&lt;li&gt;한 서비스의 장애가 전체 시스템에 영향을 줄 수 있다 (단일 장애점)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&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;b&gt;마이크로서비스 아키텍처 (Microservices Architecture, MSA)&lt;/b&gt;&lt;/h3&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;각 서비스는 하나의 도메인(예: 인증, 주문 등)을 책임지고 독립적으로 개발&amp;middot;배포된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 서비스는 자신의 데이터베이스를 갖는다 (Database per Service)&lt;/li&gt;
&lt;li&gt;공통 인프라: &lt;b&gt;API Gateway, Service Discovery, Message Queue&lt;/b&gt; 등과 함께 구성된다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;API Gateway&lt;/b&gt;: 모든 요청의 진입점, 라우팅 처리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Service Discovery&lt;/b&gt;: 각 서비스의 위치(IP/포트) 동적 관리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Message Queue&lt;/b&gt;: 비동기 이벤트 처리 (예: Kafka, RabbitMQ)&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;p 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-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기능 단위로 &lt;b&gt;독립 배포&lt;/b&gt; 가능하여 빠른 릴리즈와 롤백이 가능하다&lt;/li&gt;
&lt;li&gt;특정 서비스만 선택적으로 확장(수평 확장)할 수 있다&lt;/li&gt;
&lt;li&gt;서비스마다 &lt;b&gt;적절한 기술 스택&lt;/b&gt;을 선택할 수 있다&lt;/li&gt;
&lt;li&gt;한 서비스 장애가 전체에 영향을 주지 않도록 격리 가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 간 통신(API)으로 인한 지연 및 장애 전파 가능성&lt;/li&gt;
&lt;li&gt;인프라/운영 복잡도 증가 (서비스 디스커버리, 모니터링, 로깅 등 필요)&lt;/li&gt;
&lt;li&gt;설계 미숙 시 데이터 정합성과 트랜잭션 문제가 발생할 수 있음&lt;/li&gt;
&lt;/ul&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 data-ke-style=&quot;style1&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;로드 밸런싱 (Load Balancing)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수평 확장을 통해 특정 서비스의 서버 수를 늘렸다고 해도,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽이 고르게 분산되지 않으면 확장의 효과를 제대로 누릴 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위한 대표적인 방법이 바로 로드밸런싱(Load Balancing)이다. 로드밸런싱은&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;&amp;nbsp;&lt;/p&gt;
&lt;p 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-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버의 부하 분산으로 처리 성능 향상&lt;/li&gt;
&lt;li&gt;하나의 서버가 죽더라도 나머지 서버로 트래픽을 자동 전환 (고가용성)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;L4 (IP 기반), L7 (경로/헤더 기반)&lt;/li&gt;
&lt;li&gt;라운드로빈, 최소 연결 수, 가중치 기반 분산&lt;/li&gt;
&lt;li&gt;Nginx, HAProxy, AWS ELB 등으로 구현&lt;/li&gt;
&lt;/ul&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 data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&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;b&gt;데이터베이스 설계 (SQL vs NoSQL)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드밸런싱을 통해 애플리케이션 레벨의 트래픽을 효과적으로 분산시켰다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 중요한 설계 요소는 데이터베이스(DB)다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB는 대표적으로 SQL과 NoSQL로 나뉘며, 데이터의 특성과 목적에 따라 유연하게 선택하는 것이 중요하다.&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;SQL (관계형 DB)&lt;/b&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;/li&gt;
&lt;li&gt;복잡한 쿼리, 트랜잭션 처리에 적합&lt;/li&gt;
&lt;li&gt;대표: MySQL, PostgreSQL, Oracle&lt;/li&gt;
&lt;/ul&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;NoSQL (비관계형 DB)&lt;/b&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;/li&gt;
&lt;li&gt;키-값, 문서, 그래프 등 다양한 형태&lt;/li&gt;
&lt;li&gt;대표: MongoDB, Redis, Cassandra&lt;/li&gt;
&lt;/ul&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;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;rarr; SQL&lt;/li&gt;
&lt;li&gt;캐시, 로그, 추천 등 &lt;b&gt;대량 처리/성능&lt;/b&gt; 중심 데이터 &amp;rarr; NoSQL&lt;/li&gt;
&lt;li&gt;실제 시스템에서는 두 가지 DB를 &lt;b&gt;혼합하여 사용&lt;/b&gt;하는 경우가 많다&lt;/li&gt;
&lt;/ul&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 data-ke-style=&quot;style1&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;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;내결함성 (Fault Tolerance)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 잘 설계된 시스템이라도 서버나 데이터베이스에 장애가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 시스템 일부에 문제가 생기더라도 전체 서비스는 문제 없이 동작할 수 있도록 하는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내결함성(Fault Tolerance) 역시 중요한 설계 요소다.&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;구현하기&amp;nbsp;위한&amp;nbsp;대표적인&amp;nbsp;방법으로는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버&amp;nbsp;및&amp;nbsp;DB의&amp;nbsp;이중화,&amp;nbsp;헬스&amp;nbsp;체크,&amp;nbsp;장애&amp;nbsp;발생&amp;nbsp;시&amp;nbsp;자동&amp;nbsp;전환(Failover)&lt;/b&gt;&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;55&quot; data-start=&quot;47&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-end=&quot;55&quot; data-start=&quot;47&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마무리하며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;최근 Spring Batch와 RabbitMQ를 활용한 프로젝트를 진행하며&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 계기로 단순한 코드 작성보다 서비스를 안정적으로 운영할 수 있는 설계가 중요하다는 걸 느꼈다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 프로젝트라도 직접 구조를 설계해보며 감각을 익히고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 설계가 서비스에 끼치는 영향을 몸소 체험해보고 싶다.&lt;/p&gt;</description>
      <category>웹 개발 종합</category>
      <author>KJihun</author>
      <guid isPermaLink="true">https://developerhun.tistory.com/276</guid>
      <comments>https://developerhun.tistory.com/276#entry276comment</comments>
      <pubDate>Tue, 9 Dec 2025 12:19:41 +0900</pubDate>
    </item>
    <item>
      <title>[OS] 세마포어(Semaphore)와 뮤텍스(Mutex)</title>
      <link>https://developerhun.tistory.com/273</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;br /&gt;세마포어(Semaphore)와 뮤텍스(Mutex)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;280&quot; data-start=&quot;116&quot; data-ke-size=&quot;size16&quot;&gt;다중 스레드 환경에서 웹 애플리케이션을 개발할 때, 공유 자원(Shared Resource) 에 대한 접근 제어는 피할 수 없는 문제다&lt;br /&gt;여러 스레드가 동시에 동일한 데이터를 수정하려 하면, 데이터 무결성이 깨지거나 경쟁 상태(Race Condition) 가 발생할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;460&quot; data-start=&quot;282&quot; data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위한 대표적인 동기화 기법이 세마포어(Semaphore) 와 뮤텍스(Mutex) 이다.&lt;br /&gt;이들은 모두 &lt;b&gt;운영체제(OS) 수준에서 &lt;/b&gt;공유 자원에 대한 접근을 제어하기 위해 사용되는 동기화 메커니즘으로,&lt;br /&gt;자원 접근을 제어하는 &lt;b&gt;&amp;lsquo;락(lock)&amp;rsquo;을 관리하는 기술&lt;/b&gt;이라 할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;460&quot; data-start=&quot;282&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;622&quot; data-start=&quot;462&quot; data-ke-size=&quot;size16&quot;&gt;즉, 낙관적 락(Optimistic Lock), 비관적 락(Pessimistic Lock), 분산 락(Distributed Lock) 등이&lt;br /&gt;서버나 데이터베이스 수준에서 웹 개발자가 구현하는 락이라면,&lt;br /&gt;세마포어와 뮤텍스는 운영체제 차원에서 제공되는 락 관리 기법이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;세마포어 (Semaphore)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;b&gt;동시 접근 가능한 개수를 제어&lt;/b&gt;하는 역할이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적으로 1 이상의 값을 가지며&lt;/b&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;p style=&quot;color: #333333; text-align: start;&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-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;소유권이 없다 (누가 잠갔는가를 추적하지 않는다) &amp;rarr; 세마포어를 잠근 스레드가 아니더라도 해제할 수 있다&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;상대적으로 많은 오버헤드를 가진다(정수 카운터 관리 등)&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;작동 방식&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;`P()` (Wait)와 `V()` (Signal) 두 가지 연산을 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`P()`: &lt;b&gt;세마포어 값을 감소&lt;/b&gt;시키고, 이 값이 &lt;b&gt;0보다 작아질 경우 대기 상태로 전환&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;`V()`: &lt;b&gt;세마포어 값을 증가&lt;/b&gt;시키고, &lt;b&gt;대기 중인 프로세스나 스레드를 실행 상태로 전환&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;뮤텍스 (Mutex)&lt;/b&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터 무결성과 경쟁 상태(Race Condition)를 방지하기 위한 &lt;b&gt;상호 배제(Mutual Exclusion) 기반의 동기화 기법이다.&lt;/b&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;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이진 상태를 가지며(Lock, Unlock)&lt;/b&gt; 임계&amp;nbsp;영역이&amp;nbsp;&lt;b&gt;잠겨있는지&amp;nbsp;여부를&amp;nbsp;나타낸다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;소유권을 가진다. 뮤텍스의 상태변화는 한 스레드 또는 프로세스만 가능하다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;하나의 공유 자원에 여러 프로세스나 스레드가 접근하려고 할 때 임계구역 보호를 위해 사용된다.&lt;/li&gt;
&lt;li&gt;세마포어 보다 오버헤드가 적다(이진 상태 체크)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;작동 방식&lt;/b&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;/li&gt;
&lt;li&gt;스레드가 뮤텍스를 잠그면, 다른 스레드는 해당 뮤텍스를 얻을 때까지 대기한다.&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&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;&amp;nbsp;&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;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;b&gt;세마포어의 값이 1인 경우, 뮤텍스와 같은 용도로 사용될 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그러나&lt;/b&gt; 세마포어가 설계된 목적을 벗어나기 때문에 일반적으로 &lt;b&gt;뮤텍스를 사용하는 것이 더 명확하고 안전&lt;/b&gt;하다.&lt;br /&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;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;style1&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;b&gt;요약&lt;/b&gt;&lt;/h3&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;span style=&quot;text-align: left;&quot;&gt;세마포어는&amp;nbsp;접근&amp;nbsp;가능한&amp;nbsp;자원&amp;nbsp;수량을&amp;nbsp;제어하고,&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;text-align: left;&quot;&gt;뮤텍스는&amp;nbsp;데이터&amp;nbsp;무결성과&amp;nbsp;경쟁&amp;nbsp;상태(Race&amp;nbsp;Condition)&amp;nbsp;를&amp;nbsp;방지하기&amp;nbsp;위해&amp;nbsp;사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 기법 모두&amp;nbsp;&lt;b&gt;단일 시스템 &lt;/b&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;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;figure id=&quot;og_1760943073634&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)&quot; data-og-description=&quot;동시에 여러 트랜잭션이 같은 데이터를 수정하려고 할 때, 데이터의 정합성(Consistency)이 깨질 수 있다.이를 방지하기 위해, 트랜잭션 간의 충돌을 제어하는 동시성 제어(Concurrency Control) 기법이 &quot; data-og-host=&quot;developerhun.tistory.com&quot; data-og-source-url=&quot;https://developerhun.tistory.com/272&quot; data-og-url=&quot;https://developerhun.tistory.com/272&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mnMIh/hyZLsJ7jO4/Wy6WJvczdYqlGyX2y6G5tK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cmCD53/hyZMaOCxtz/cukVIutmLkzt7yYUughCT0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ntkoB/hyZLWCRzHt/xZxFWHBP5p821OscKkKD40/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://developerhun.tistory.com/272&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developerhun.tistory.com/272&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mnMIh/hyZLsJ7jO4/Wy6WJvczdYqlGyX2y6G5tK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cmCD53/hyZMaOCxtz/cukVIutmLkzt7yYUughCT0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ntkoB/hyZLWCRzHt/xZxFWHBP5p821OscKkKD40/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&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;낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;동시에 여러 트랜잭션이 같은 데이터를 수정하려고 할 때, 데이터의 정합성(Consistency)이 깨질 수 있다.이를 방지하기 위해, 트랜잭션 간의 충돌을 제어하는 동시성 제어(Concurrency Control) 기법이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developerhun.tistory.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;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;</description>
      <category>OS</category>
      <author>KJihun</author>
      <guid isPermaLink="true">https://developerhun.tistory.com/273</guid>
      <comments>https://developerhun.tistory.com/273#entry273comment</comments>
      <pubDate>Fri, 17 Oct 2025 16:47:46 +0900</pubDate>
    </item>
    <item>
      <title>낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)</title>
      <link>https://developerhun.tistory.com/272</link>
      <description>&lt;p data-end=&quot;409&quot; data-start=&quot;223&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;409&quot; data-start=&quot;223&quot; data-ke-size=&quot;size16&quot;&gt;동시에 여러 트랜잭션이 같은 데이터를 수정하려고 할 때, 데이터의 정합성(Consistency)이 깨질 수 있다.&lt;br /&gt;이를 방지하기 위해, 트랜잭션 간의 충돌을 제어하는 동시성 제어(Concurrency Control) 기법이 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;362&quot; data-start=&quot;165&quot; data-ke-size=&quot;size16&quot;&gt;세마포어(Semaphore)와 뮤텍스(Mutex)는 운영체제(OS) 수준에서 스레드 간 자원 접근을 제어하기 위해&lt;/p&gt;
&lt;p data-end=&quot;362&quot; data-start=&quot;165&quot; data-ke-size=&quot;size16&quot;&gt;시스템 락을 거는 방법이다.&lt;br /&gt;반면, &lt;b&gt;낙관적 락&lt;/b&gt;과 &lt;b&gt;비관적 락&lt;/b&gt;은 &lt;b&gt;서버 애플리케이션 혹은 데이터베이스(DB)&lt;/b&gt; 레벨에서 적용되는 &lt;b&gt;동시성 제어&lt;/b&gt;방식이다.&lt;/p&gt;
&lt;p data-end=&quot;467&quot; data-start=&quot;364&quot; data-ke-size=&quot;size16&quot;&gt;즉, 단일 프로세스 내의 스레드 동기화가 아니라,&lt;br /&gt;여러 트랜잭션이 같은 데이터를 수정하려 할 때 데이터의 정합성(Consistency)을 유지하기 위한 방법이라 할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;409&quot; data-start=&quot;223&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;409&quot; data-start=&quot;223&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;style1&quot; /&gt;
&lt;p data-end=&quot;409&quot; data-start=&quot;223&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;409&quot; data-start=&quot;223&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;448&quot; data-start=&quot;416&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비관적 락 (Pessimistic Lock)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;560&quot; data-start=&quot;463&quot; data-ke-size=&quot;size16&quot;&gt;비관적 락은 &amp;ldquo;충돌이 자주 일어날 것이다&amp;rdquo;라고 &lt;b&gt;비관적으로 가정&lt;/b&gt;하고,&lt;br /&gt;&lt;b&gt;데이터를 읽거나 수정하기 전에 먼저 락을 걸어 다른 트랜잭션의 접근을 차단&lt;/b&gt;하는 방식이다.&lt;/p&gt;
&lt;p data-end=&quot;620&quot; data-start=&quot;562&quot; data-ke-size=&quot;size16&quot;&gt;즉, 한 트랜잭션이 데이터를 조회하거나 수정하는 동안,&lt;br /&gt;다른 트랜잭션은 그 데이터에 접근할 수 없다.&lt;/p&gt;
&lt;p data-end=&quot;620&quot; data-start=&quot;562&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;620&quot; data-start=&quot;562&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;620&quot; data-start=&quot;562&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;635&quot; data-start=&quot;622&quot; data-ke-size=&quot;size18&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; data-start=&quot;636&quot; data-end=&quot;781&quot;&gt;
&lt;li data-end=&quot;680&quot; data-start=&quot;636&quot;&gt;트랜잭션이 데이터를 &lt;b&gt;읽기 or 수정 전에 lock을 획득&lt;/b&gt;한다.&lt;/li&gt;
&lt;li data-end=&quot;743&quot; data-start=&quot;681&quot;&gt;&lt;b&gt;다른 트랜잭션이&lt;/b&gt; 해당 데이터에 &lt;b&gt;접근하려 하면&lt;/b&gt;, 락이 해제될 때까지 &lt;b&gt;대기(blocking)&lt;/b&gt; 된다.&lt;/li&gt;
&lt;li data-end=&quot;781&quot; data-start=&quot;744&quot;&gt;&lt;b&gt;작업이 완료되면&lt;/b&gt; 트랜잭션이 커밋 또는 롤백되며 &lt;b&gt;락이 해제&lt;/b&gt;된다.&lt;/li&gt;
&lt;/ol&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;&lt;b&gt;장점&lt;/b&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;/li&gt;
&lt;li&gt;구현이 비교적 간단하고 직관적이다.&lt;/li&gt;
&lt;/ul&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;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;락을 너무 오래 유지하면 데드락(Deadlock)이 발생할 위험이 크다.&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; DB 락 적용 예시&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1760683580945&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BEGIN;

SELECT * 
FROM account 
WHERE id = 1 
FOR UPDATE;  -- 베타적 락

UPDATE account 
SET balance = balance - 100 
WHERE id = 1;

COMMIT;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FOR UPDATE를 사용하면 Exclusive Lock(배타적 락)이 걸려 다른 트랜잭션은 이 데이터가 커밋될 때까지 접근할 수 없다.&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버 락 적용 예시&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1760683756385&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private final Object lock = new Object();

public void withdraw(Long accountId, int amount) {
    synchronized (lock) {
        Account account = accountRepository.findById(accountId);
        account.decreaseBalance(amount);
        accountRepository.save(account);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한 스레드가 synchronized 블록에 들어가면&lt;/b&gt; 다른 스레드는 해당 블록이 끝날 때까지 대기한다.&lt;br /&gt;즉, 서버 프로세스 내에서만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;뮤텍스(Mutex) 수준의 락 제어&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;&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;style1&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;416&quot; data-end=&quot;448&quot;&gt;&lt;b&gt; 낙관적 락 (Optimistic Lock) &lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1229&quot; data-start=&quot;1126&quot; data-ke-size=&quot;size16&quot;&gt;낙관적 락은 &quot;충돌이 잘 일어나지 않을 것이다&quot;라고 &lt;b&gt;낙관적으로 가정&lt;/b&gt;하고,&lt;br /&gt;트랜잭션이 데이터를 수정할 때 버전(Version)을 비교하여 충돌 여부를 확인하는 방식이다.&lt;/p&gt;
&lt;p data-end=&quot;1274&quot; data-start=&quot;1231&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;락을 걸지 않고 자유롭게 접근하되, 커밋 시점에 충돌을 검증&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-end=&quot;1274&quot; data-start=&quot;1231&quot; 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-end=&quot;1289&quot; data-start=&quot;1276&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;동작 방식&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1462&quot; data-start=&quot;1290&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;199&quot; data-start=&quot;154&quot;&gt;&lt;b&gt;데이터를 읽을 때&lt;/b&gt;, 현재 &lt;b&gt;버전(version) 정보를 함께 가져온다&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;239&quot; data-start=&quot;200&quot;&gt;&lt;b&gt;수정 시&lt;/b&gt;, &lt;b&gt;WHERE 조건에 버전을 포함&lt;/b&gt;하며, &lt;b&gt;업데이트&lt;/b&gt;&amp;nbsp;&lt;b&gt;성공시 version 값&lt;/b&gt;을 &lt;b&gt;증가&lt;/b&gt;시킨다.&lt;/li&gt;
&lt;li data-end=&quot;310&quot; data-start=&quot;240&quot;&gt;&lt;b&gt;버전이 일치하지 않을 경우 업데이트하지 않으며, UPDATE 결과가 0건이 되어 충돌이 감지된다.&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;335&quot; data-start=&quot;311&quot;&gt;&lt;b&gt;충돌 시&lt;/b&gt;에는 &lt;b&gt;OptimisticLockException 또는 개발자가 정의한 예외를 발생&lt;/b&gt;시켜 재시도 로직을 처리한다.&lt;/li&gt;
&lt;/ol&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;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락을 걸지 않으므로 대기 시간이 없어 높은 처리량(Throughput)을 기대할 수 있다.&lt;/li&gt;
&lt;li&gt;데드락의 위험이 없다.&lt;/li&gt;
&lt;/ul&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;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;/li&gt;
&lt;li&gt;재시도 로직을 개발자가 직접 구현해야 하는 복잡성이 있다.&lt;/li&gt;
&lt;/ul&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;&lt;b&gt;DB 락 적용 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1760684875411&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. 데이터 조회 시 현재 version을 함께 가져온다.
SELECT id, balance, version 
FROM account 
WHERE id = 1;

-- 2. 업데이트 시 version을 비교하며, 일치하면 balance와 version을 동시에 갱신한다.
UPDATE account
SET balance = balance - 100,
    version = version + 1
WHERE id = 1
  AND version = 5;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1760685267011&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 동시성 발생 시 처리 예시(Mybatis)
int updated = jdbcTemplate.update(
    &quot;UPDATE account SET balance = ?, version = version + 1 WHERE id = ? AND version = ?&quot;,
    newBalance, accountId, currentVersion
);

if (updated == 0) {
    throw new OptimisticLockConflictException(&quot;다른 트랜잭션에서 데이터가 변경되었습니다.&quot;);
}&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;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;&lt;b&gt;서버 락 적용 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1760685174156&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Account {
    @Id
    private Long id;

    @Version  // JPA가 자동으로 version 필드 기반 낙관적 락을 관리
    private int version;

    // ....
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1760685125950&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class AccountService {

    @Transactional
    public void withdraw(Long accountId, int amount) {
        Account account = accountRepository.findById(accountId)
                 .orElseThrow(() -&amp;gt; new RuntimeException(&quot;계좌를 찾을 수 없습니다.&quot;));

        account.decreaseBalance(amount);
        // JPA가 flush 시점에 version 비교 &amp;rarr; 불일치 시 OptimisticLockException 발생
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1760685369948&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예외 처리 및 재시도 로직
try {
    accountService.withdraw(1L, 1000);
} catch (OptimisticLockException e) {
    // 재시도 로직 또는 사용자 알림 처리
    throw new CustomRetryException(&quot;다른 사용자가 데이터를 수정했습니다. 다시 시도해주세요.&quot;);
}&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Version 필드가 있으면 JPA는 내부적으로 WHERE id=? AND version=? 조건으로 UPDATE를 수행한다.&lt;/li&gt;
&lt;li&gt;version이 일치하지 않으면 OptimisticLockException이 발생한다.&lt;/li&gt;
&lt;li&gt;프레임워크가 던지는 예외를 잡아 재시도 또는 사용자에게 알림하는 로직을 추가할 수 있다.&lt;/li&gt;
&lt;/ul&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;style1&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비교요약 : 비관적 락 vs. 낙관적 락&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;항목 (Category)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;비관적 락 (Pessimistic Lock)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;낙관적 락 (Optimistic Lock)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&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;락 시점&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;성능&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;데이터 정합성&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;주요 단점&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;구현 복잡도&lt;/b&gt;&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-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;style1&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;어떤 락을 선택해야 할까?&lt;/b&gt;  &lt;/h3&gt;
&lt;p 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-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; 경우&lt;/li&gt;
&lt;li&gt;트랜잭션 처리 시간이 짧아 락 유지 시간이 길지 않은 경우&lt;/li&gt;
&lt;/ul&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;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;하거나 사용자에게 알림으로 충분한 경우&lt;/li&gt;
&lt;li&gt;높은 동시성과 처리량이 중요한 서비스&lt;/li&gt;
&lt;/ul&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;낙관적 락&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;중 어느 것이 절대적으로 우수하다고 말할 수는 없다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;애플리케이션의 특성과 데이터의 성격에 따라 적절한 방식을 선택하는 것이 중요하다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>DB</category>
      <author>KJihun</author>
      <guid isPermaLink="true">https://developerhun.tistory.com/272</guid>
      <comments>https://developerhun.tistory.com/272#entry272comment</comments>
      <pubDate>Fri, 17 Oct 2025 16:34:17 +0900</pubDate>
    </item>
    <item>
      <title>분산 락(distributed lock) 이란?</title>
      <link>https://developerhun.tistory.com/271</link>
      <description>&lt;p data-start=&quot;128&quot; data-end=&quot;262&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;b&gt;뮤텍스(Mutex), 세마포어(Semaphore)&lt;/b&gt;나&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스의 &lt;b&gt;비관적/낙관적 락&lt;/b&gt;을 떠올리게 된다.&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;모놀리식(Monolithic) 아키텍처&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;하지만 MSA 환경에서는 각 서비스가 독립된 서버와 데이터베이스를 사용하는 것이 일반적이다.&lt;/p&gt;
&lt;p data-start=&quot;264&quot; data-end=&quot;356&quot; data-ke-size=&quot;size16&quot;&gt;프로세스 간에는 메모리를 공유하지 않기 때문에 서로의 락 상태를 확인하거나 제어할 수 없다.&lt;br /&gt;즉, 한 서버에서 설정한 락을 다른 서버가 인식할 방법이 없다.&lt;/p&gt;
&lt;p data-start=&quot;358&quot; data-end=&quot;466&quot; data-ke-size=&quot;size16&quot;&gt;따라서 여러 대의 서버가 네트워크를 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;동일한 공유 자원&lt;/b&gt;(예: 재고, 쿠폰, 포인트 등)&lt;b&gt; 에 접근할 수 있는 상황에서&lt;/b&gt;는&lt;br /&gt;&lt;b&gt;분산 락(Distributed Lock) 이 필요&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;분산 락(distributed lock) 이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-start=&quot;506&quot; data-end=&quot;584&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공통 저장소에 락의 상태를 저장&lt;/b&gt;하고 각 프로세스가 이 상태를 읽어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;락을 획득하거나 해제하는 방식&lt;/b&gt;으로 동작한다.&lt;/p&gt;
&lt;p data-start=&quot;586&quot; data-end=&quot;664&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;모든 노드가 공통된 저장소를 통해 락 상태를 공유&lt;/b&gt;하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;서로 다른 서버 간 자원 접근을 제어할 수 있게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-start=&quot;666&quot; data-end=&quot;695&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-start=&quot;666&quot; data-end=&quot;695&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;분산 락의 동작 원리&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot; data-spread=&quot;false&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;공통 저장소에 락의 상태&lt;/b&gt;(소유자 식별자, 만료 시간 등)를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;저장&lt;/b&gt;한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;락을 얻고자 하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;프로세스는 저장소에 락 생성&lt;/b&gt;을 시도한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;성공하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;해당 자원에 대한 접근 권한을 가지며, 작업이 끝나면 락을 해제&lt;/b&gt;한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;네트워크 장애/크래시로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;락이 영구적으로 남는 것을 막기 위해 TTL&lt;/b&gt;(만료시간)&lt;b&gt;을 둔다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;666&quot; data-end=&quot;695&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;666&quot; data-end=&quot;695&quot;&gt;&lt;b&gt;자주 사용되는 공통 저장소&lt;/b&gt;&lt;/h4&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;span&gt;&lt;b&gt;Redis&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인메모리라 빠르고&amp;nbsp;SET NX PX&amp;nbsp;같은 원자적 연산 또는 RedLock 알고리즘을 활용해 분산 락을 구현한다.&lt;/li&gt;
&lt;li&gt;응답성이 중요한 시나리오에 자주 사용된다.&lt;/li&gt;
&lt;/ul&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;span&gt;&lt;b&gt;Zookeeper&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분산 코디네이터로서 노드 간 상태 동기화, ephemeral node 기반의 락, 순서 노드 등을 제공한다.&lt;/li&gt;
&lt;li&gt;일관성(Consistency)을 중시할 때 유리하다.&lt;/li&gt;
&lt;/ul&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;span&gt;&lt;b&gt;Database&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET_LOCK()&amp;nbsp;같은 Named Lock, 혹은 행을 이용한 낙관/비관적 락으로 구현 가능.&lt;/li&gt;
&lt;li&gt;영속성과 ACID 트랜잭션의 장점이 있지만 성능이 떨어질 수 있다.&lt;/li&gt;
&lt;/ul&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;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;구현 방식과 원리&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Redis 기반 분산 락&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Redis에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;SET&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;NX&lt;/span&gt;&lt;span&gt;(존재하지 않을 때만)와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;PX&lt;/span&gt;&lt;span&gt;(만료시간 설정) 옵션을 활용하여 락을 구현한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;락을 요청하는 노드는 고유한 토큰(UUID 등)을 함께 저장해 락의 소유권을 나타낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 방식은 원자적으로 락을 획득하므로 여러 노드가 동시에 요청하더라도 한 노드만 성공하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Redis &amp;mdash; RedLock 알고리즘&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;RedLock은 여러 개의 독립된 Redis 인스턴스에 동일한 키로 락을 요청하고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;과반수 이상에서 성공하면 락을 획득한 것으로 간주하는 알고리즘이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이를 통해 단일 Redis 장애에 대한 내성을 확보할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Zookeeper 기반 락&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Zookeeper는 분산 코디네이터로, ephemeral(임시) znode를 이용해 락을 구현한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;각 클라이언트는 순차 노드를 생성하고 자신의 바로 앞 순서 노드가 삭제될 때까지 감시한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;가장 작은 번호의 노드를 가진 클라이언트가 락을 획득하며, 세션이 끊기면 해당 노드가 자동 삭제되어 락이 해제된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Database 기반 락&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;DB에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;GET_LOCK()&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 Named Lock을 활용하거나 특정 행에 비관적 락(&lt;/span&gt;&lt;span&gt;SELECT ... FOR UPDATE&lt;/span&gt;&lt;span&gt;)을 거는 방식으로 구현할 수 있다. 단순하고 트랜잭션 일관성을 보장하지만, DB 부하가 높아질 수 있으며 대규모 트래픽에는 적합하지 않을 수 있다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;922&quot; data-start=&quot;903&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;핵심 고려사항&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;988&quot; data-start=&quot;924&quot; data-ke-size=&quot;size16&quot;&gt;분산 락이 제대로 동작하려면 다음을 만족해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;988&quot; data-start=&quot;924&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;988&quot; data-start=&quot;924&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;락 획득의 원자성 (Atomicity)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1109&quot; data-start=&quot;1024&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1109&quot; data-start=&quot;1024&quot;&gt;락을 획득하거나 실패하는 과정이 반드시 원자적으로 수행되어야 한다.&lt;br /&gt;(여러 프로세스가 동시에 락을 요청하더라도, 단 하나만 성공해야 함)&lt;/li&gt;
&lt;/ul&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;락 소유권 (Ownership)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1250&quot; data-start=&quot;1141&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1250&quot; data-start=&quot;1141&quot;&gt;어떤 노드가 락을 획득했는지 식별할 수 있어야 하며,&lt;br /&gt;다른 노드가 실수로 해당 락을 해제하지 않도록 해야 한다.&lt;br /&gt;(예: UUID, 스레드 ID, 토큰 값 등으로 식별)&lt;/li&gt;
&lt;/ul&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;락 만료 (Expiration)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1368&quot; data-start=&quot;1282&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1368&quot; data-start=&quot;1282&quot;&gt;네트워크 장애나 서버 다운 등의 이유로 락이 해제되지 않는 상황을 방지하기 위해&lt;br /&gt;일정 시간이 지나면 자동으로 락이 해제되도록 설정한다.&lt;/li&gt;
&lt;/ul&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;락 해제의 안전성 (Safety)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1486&quot; data-start=&quot;1401&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1486&quot; data-start=&quot;1401&quot;&gt;락을 해제할 때, 반드시 &amp;ldquo;내가 획득한 락인지&amp;rdquo; 확인한 뒤 해제해야 한다.&lt;br /&gt;그렇지 않으면 다른 노드의 락을 해제해버리는 위험이 있다.&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>DB</category>
      <author>KJihun</author>
      <guid isPermaLink="true">https://developerhun.tistory.com/271</guid>
      <comments>https://developerhun.tistory.com/271#entry271comment</comments>
      <pubDate>Thu, 16 Oct 2025 14:06:42 +0900</pubDate>
    </item>
    <item>
      <title>Spring Batch를 활용한 쿠폰발급 및 메일전송</title>
      <link>https://developerhun.tistory.com/270</link>
      <description>&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;거래&amp;nbsp;플랫폼을&amp;nbsp;개발하면서,&amp;nbsp;마케팅&amp;nbsp;측면에서&amp;nbsp;유저가&amp;nbsp;이메일&amp;nbsp;수신을&amp;nbsp;허용하도록&amp;nbsp;유도하기&amp;nbsp;위해&amp;nbsp;생일인&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;p data-ke-size=&quot;size16&quot;&gt;이&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;유지보수성을&amp;nbsp;고려하여&amp;nbsp;Spring&amp;nbsp;Batch를&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;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드 (1).jpg&quot; data-origin-width=&quot;180&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H6TwH/btsQ8zR0hkP/yWmclclffLXufkVjf0jky0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H6TwH/btsQ8zR0hkP/yWmclclffLXufkVjf0jky0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H6TwH/btsQ8zR0hkP/yWmclclffLXufkVjf0jky0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH6TwH%2FbtsQ8zR0hkP%2FyWmclclffLXufkVjf0jky0%2Fimg.jpg&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;180&quot; height=&quot;168&quot; data-filename=&quot;다운로드 (1).jpg&quot; data-origin-width=&quot;180&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Spring Batch란?&lt;/b&gt;&lt;/h3&gt;
&lt;p 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;Spring&amp;nbsp;Batch는&amp;nbsp;&lt;b&gt;대용량&amp;nbsp;데이터를&amp;nbsp;효율적으로&amp;nbsp;처리하기&amp;nbsp;위한&amp;nbsp;Java&amp;nbsp;기반&amp;nbsp;배치&amp;nbsp;프레임워크&lt;/b&gt;다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주로&amp;nbsp;&lt;b&gt;청크(Chunk)&amp;nbsp;단위로&amp;nbsp;데이터를&amp;nbsp;나누어&amp;nbsp;처리&lt;/b&gt;함으로써,&amp;nbsp;상황에&amp;nbsp;따라&amp;nbsp;메모리를&amp;nbsp;유연하게&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있다는&amp;nbsp;장점이&amp;nbsp;있다.&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2025-10-15 133102.png&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;607&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tY2WR/btsQ9r6ZOGp/X4tWwfqiD0QVVIzvhJbuD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tY2WR/btsQ9r6ZOGp/X4tWwfqiD0QVVIzvhJbuD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tY2WR/btsQ9r6ZOGp/X4tWwfqiD0QVVIzvhJbuD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtY2WR%2FbtsQ9r6ZOGp%2FX4tWwfqiD0QVVIzvhJbuD0%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;1086&quot; height=&quot;607&quot; data-filename=&quot;화면 캡처 2025-10-15 133102.png&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;607&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Batch의 청크 처리 구조는 다음과 같은 구성요소로 이루어진다. &lt;br /&gt;&lt;br /&gt;1. &lt;b&gt;Scheduler&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;b&gt;JobLauncher&lt;/b&gt; 실행) 한다.&lt;/span&gt;&lt;br /&gt;2. &lt;b&gt;JobLauncher&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;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Job&lt;/b&gt;을 실행한다. &lt;br /&gt;3. &lt;b&gt;Job&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;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Step&lt;/b&gt;으로 구성되며, 각 Step의 실행 순서를 관리한다. &lt;br /&gt;4. &lt;b&gt;Step&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 독립적인 작업 단위로, 각 Step 내부에서 Reader, Processor, Writer를 통해 데이터가 처리된다.&lt;/span&gt; &lt;br /&gt;5. &lt;b&gt;JobRepository&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; Step의 실행 상태와 이력을 관리하는 저장소 역할을 한다.&lt;/span&gt; &lt;br /&gt;6. &lt;b&gt;TransactionManager&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;nbsp;&lt;/span&gt;&lt;/span&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;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;style1&quot; /&gt;
&lt;h3 data-end=&quot;996&quot; data-start=&quot;966&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;996&quot; data-start=&quot;966&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Scheduler와 Batch의 차이&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1074&quot; data-start=&quot;998&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하며 많은 사람들이 &lt;b&gt;Scheduler&lt;/b&gt;와 &lt;b&gt;Batch&lt;/b&gt;를 혼동하는 경우가 있었다.&lt;br /&gt;이를 간단히 비유하자면,&lt;/p&gt;
&lt;p data-end=&quot;1074&quot; data-start=&quot;998&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;1133&quot; data-start=&quot;1076&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1133&quot; data-start=&quot;1078&quot; data-ke-size=&quot;size16&quot;&gt;Scheduler는 &amp;ldquo;알람 시계&amp;rdquo;이고,&lt;br /&gt;Batch는 &amp;ldquo;알람이 울린 후 수행하는 일&amp;rdquo;이다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1201&quot; data-start=&quot;1135&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;Scheduler는 Job을 실행시키는 역할&lt;/b&gt;, &lt;b&gt;Batch는 실제 데이터를 처리하는 역할&lt;/b&gt;을 담당한다.&lt;/p&gt;
&lt;p data-end=&quot;1201&quot; data-start=&quot;1135&quot; 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;style1&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Scheduler의 구현 방식&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scheduler는&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2025-10-15 135518.png&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGIVY0/btsQ9XZhE9q/mAJBNv4vkkB9e8YzzRiUGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGIVY0/btsQ9XZhE9q/mAJBNv4vkkB9e8YzzRiUGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGIVY0/btsQ9XZhE9q/mAJBNv4vkkB9e8YzzRiUGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGIVY0%2FbtsQ9XZhE9q%2FmAJBNv4vkkB9e8YzzRiUGk%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;1086&quot; height=&quot;610&quot; data-filename=&quot;화면 캡처 2025-10-15 135518.png&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;610&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 style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;단일 서버 환경&lt;/span&gt;(Monolithic) 에서는 어떤 방식으로 구현하여도 문제 없지만,&lt;br /&gt;&lt;b&gt;Quartz 방식은 트리거가 발생한 기록을 DB 에 저장&lt;/b&gt;하여 다중서버 환경(MSA)에서 &lt;b&gt;중복실행&lt;/b&gt;을 &lt;b&gt;방지&lt;/b&gt;할 수 있다는 차이점이 있다.&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&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;style1&quot; /&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Batch에서 데이터 읽기 (Reader)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Scheduler를&amp;nbsp;통해&amp;nbsp;Batch가&amp;nbsp;실행되면,&amp;nbsp;데이터&amp;nbsp;처리를&amp;nbsp;위해&amp;nbsp;DB에서&amp;nbsp;데이터를&amp;nbsp;조회해야&amp;nbsp;한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Spring&amp;nbsp;Batch에서는&amp;nbsp;데이터를&amp;nbsp;읽는&amp;nbsp;방식에&amp;nbsp;따라&amp;nbsp;세&amp;nbsp;가지&amp;nbsp;Reader가&amp;nbsp;자주&amp;nbsp;사용된다.&lt;/p&gt;
&lt;p style=&quot;color: #000000;&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-filename=&quot;화면 캡처 2025-10-15 135657.png&quot; data-origin-width=&quot;1087&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFIVEZ/btsQ9xsFWHH/DzR8gWo4PgOmzJE1Qpw2Y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFIVEZ/btsQ9xsFWHH/DzR8gWo4PgOmzJE1Qpw2Y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFIVEZ/btsQ9xsFWHH/DzR8gWo4PgOmzJE1Qpw2Y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFIVEZ%2FbtsQ9xsFWHH%2FDzR8gWo4PgOmzJE1Qpw2Y0%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;1087&quot; height=&quot;608&quot; data-filename=&quot;화면 캡처 2025-10-15 135657.png&quot; data-origin-width=&quot;1087&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. &lt;span style=&quot;color: #000000;&quot;&gt;CursorItemReader&lt;/span&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Cursor 방식은 DB와 연결을 유지하며 하나씩 가져오는 방식&lt;/b&gt;으로,&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;메모리에 하나의 데이터만 적재되기 때문에 메모리 사용량이 적다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;하지만 하나의 DB 커넥션을 오랜 시간 점유하기 때문에, &lt;b&gt;멀티스레드 처리에는 부적합&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. &lt;span style=&quot;color: #000000;&quot;&gt;PagingItemReader&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Paging 방식은 일정한 데이터 개수만큼 조회하여&lt;/b&gt; &lt;b&gt;메모리에 적재&lt;/b&gt;한 뒤,&lt;br /&gt;DB 연결을 끊고 해당 &lt;b&gt;데이터를 모두 처리하면&lt;/b&gt; &lt;b&gt;다음 페이지를 다시 조회&lt;/b&gt;하는 방식이다.&lt;br /&gt;이 방식은 DB 커넥션을 오래 점유하지 않기 때문에 &lt;b&gt;멀티스레드 환경에서도 안전하게 동작&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. &lt;span style=&quot;color: #000000;&quot;&gt;List Reader&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;List 방식은 데이터를 &lt;b&gt;한 번에 모두 메모리에 로드&lt;/b&gt;한 뒤, 리스트 형태로 순차적으로 처리하는 방식이다.&lt;br /&gt;따라서 &lt;b&gt;메모리 사용량이 많아 대용량 데이터 처리에는 부적합&lt;/b&gt;하며, 일반적으로 &lt;b&gt;소량의 데이터 처리나 테스트용으로 사용&lt;/b&gt;된다.&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&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;style1&quot; /&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; Batch에서 데이터 저장 (Writer) &lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 읽는 방식만큼 중요한 것이 &lt;b&gt;데이터 저장 방식&lt;/b&gt;이다.&lt;br /&gt;Spring Batch는 데이터를 &lt;b&gt;청크(Chunk)&lt;/b&gt; 단위로 저장한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1085&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVo9Me/btsRamdb7Ux/zxZWsUBJoF76tYpZYsjVp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVo9Me/btsRamdb7Ux/zxZWsUBJoF76tYpZYsjVp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVo9Me/btsRamdb7Ux/zxZWsUBJoF76tYpZYsjVp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVo9Me%2FbtsRamdb7Ux%2FzxZWsUBJoF76tYpZYsjVp0%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;1085&quot; height=&quot;606&quot; data-origin-width=&quot;1085&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;Chunk는&amp;nbsp;DB에&amp;nbsp;값을&amp;nbsp;저장하기&amp;nbsp;위해&amp;nbsp;사용되는&amp;nbsp;논리적&amp;nbsp;단위&lt;/b&gt;로 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;하나의&amp;nbsp;처리가&amp;nbsp;끝났다면&amp;nbsp;&lt;/b&gt;DB에&amp;nbsp;바로&amp;nbsp;저장되는&amp;nbsp;방식이&amp;nbsp;아닌&amp;nbsp;&lt;b&gt;Chunk에&amp;nbsp;적재&lt;/b&gt;한&amp;nbsp;후,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Chunk가 가득 찼다면 &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;Chunk 단위로 DB에 저장하는 방식&lt;/b&gt;이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Chunk 하나에 하나의 트랜잭션이 적용되기 때문에 DB부하 감소 및 처리성능이 향상된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;단, &lt;b&gt;청크 단위로 커밋 및 롤백&lt;/b&gt;이 이루어지기 때문에&amp;nbsp;에러 발생 시 해당 청크 전체가 롤백된다는 점에 유의해야 한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&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;style1&quot; /&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; 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;figure id=&quot;og_1760505974102&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;TransientDataAccessResourceException&quot; data-og-description=&quot;문제 Spring batch 사용 시 Read 방식을 List로 구현하였을 때에는 정상적으로 동작이 수행되었다이후, read방식을 paging 으로 변경 후 quartz가 실행되었을 때, TransientDataAccessResourceException 발생하였다. org&quot; data-og-host=&quot;developerhun.tistory.com&quot; data-og-source-url=&quot;https://developerhun.tistory.com/263&quot; data-og-url=&quot;https://developerhun.tistory.com/263&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cSw9lW/hyZLo0dV9I/FICuJf7lf7dy59IKRNG87K/img.png?width=800&amp;amp;height=206&amp;amp;face=0_0_800_206,https://scrap.kakaocdn.net/dn/htcs8/hyZLzA6zYg/NKdFFzvkaiRaOrgvwkLup1/img.png?width=800&amp;amp;height=206&amp;amp;face=0_0_800_206,https://scrap.kakaocdn.net/dn/cQA5jp/hyZLiMtIMz/veRY7tbAiXCRBuDYWukorK/img.png?width=1179&amp;amp;height=304&amp;amp;face=0_0_1179_304&quot;&gt;&lt;a href=&quot;https://developerhun.tistory.com/263&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developerhun.tistory.com/263&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cSw9lW/hyZLo0dV9I/FICuJf7lf7dy59IKRNG87K/img.png?width=800&amp;amp;height=206&amp;amp;face=0_0_800_206,https://scrap.kakaocdn.net/dn/htcs8/hyZLzA6zYg/NKdFFzvkaiRaOrgvwkLup1/img.png?width=800&amp;amp;height=206&amp;amp;face=0_0_800_206,https://scrap.kakaocdn.net/dn/cQA5jp/hyZLiMtIMz/veRY7tbAiXCRBuDYWukorK/img.png?width=1179&amp;amp;height=304&amp;amp;face=0_0_1179_304');&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;TransientDataAccessResourceException&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;문제 Spring batch 사용 시 Read 방식을 List로 구현하였을 때에는 정상적으로 동작이 수행되었다이후, read방식을 paging 으로 변경 후 quartz가 실행되었을 때, TransientDataAccessResourceException 발생하였다. org&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developerhun.tistory.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>Java</category>
      <author>KJihun</author>
      <guid isPermaLink="true">https://developerhun.tistory.com/270</guid>
      <comments>https://developerhun.tistory.com/270#entry270comment</comments>
      <pubDate>Wed, 15 Oct 2025 14:24:50 +0900</pubDate>
    </item>
    <item>
      <title>DDD, 헥사고날 아키텍처, 멀티모듈 &amp;mdash; 개념 및 흐름</title>
      <link>https://developerhun.tistory.com/269</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 애플리케이션은 복잡한 비즈니스 규칙과 빠른 기술 교체 요구를 동시에 맞닥뜨린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 잘못 설계하면 코드가 외부 라이브러리&amp;middot;프레임워크에 묶여 변경이 어렵고, 여러 팀이 동시에 작업할 때 충돌이 잦아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; DDD, 헥사고날, 멀티모듈&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;이 글은&amp;nbsp; DDD &amp;rarr; 헥사고날 &amp;rarr; 멀티모듈 순으로 설명할 예정이다.&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-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;도메인 주도 설계 (&lt;/span&gt;&lt;span&gt;DDD;&amp;nbsp;&lt;/span&gt;&lt;span&gt;Domain-Driven Design)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;117&quot; data-start=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;도메인 주도 설계.. 알듯 말듯한 단어이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;117&quot; data-start=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;도메인 중심으로 설계를 한다는 것 같은데..&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-end=&quot;117&quot; data-start=&quot;54&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;과연 DDD에서 말하는 도메인은 무엇일까? &lt;span style=&quot;color: #e6e8f0; text-align: start;&quot;&gt; &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #e6e8f0; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #e6e8f0; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;117&quot; data-start=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;DDD에서 도메인은 &lt;b&gt;현실 세계에서 해결해야 하는 비즈니스 문제 영역&lt;/b&gt;을 정의하는 것이라 볼 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;117&quot; data-start=&quot;54&quot; data-ke-size=&quot;size16&quot;&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;117&quot; data-start=&quot;54&quot;&gt;예매(티켓 발급, 결제, 취소, 환불 등)&lt;/li&gt;
&lt;li data-end=&quot;117&quot; data-start=&quot;54&quot;&gt;사용자(회원가입, 로그인, 프로필 관리 등)&lt;/li&gt;
&lt;li data-end=&quot;117&quot; data-start=&quot;54&quot;&gt;공연(공연 일정, 좌석 배치, 공연 정보 등)&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 객체와 어떤 차이점이 있을까?&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;b&gt;구현하는 구체적인 단위&lt;/b&gt;다. (예: Order 엔티티, PaymentService)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 DDD 의 계층에 대해 간단하게 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1010&quot; data-start=&quot;990&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-end=&quot;1010&quot; data-start=&quot;990&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;DDD의 계층 구조&lt;/b&gt;&lt;/h4&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개의 계층을 가진다.&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1560&quot; data-start=&quot;1040&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1141&quot; data-start=&quot;1040&quot;&gt;&lt;b&gt;Presentation Layer (UI Layer)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1141&quot; data-start=&quot;1082&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1111&quot; data-start=&quot;1082&quot;&gt;&lt;b&gt;사용자의 요청을&lt;/b&gt; 받아 &lt;b&gt;Application Layer 에 전달&lt;/b&gt;하는 계층&lt;/li&gt;
&lt;li data-end=&quot;1111&quot; data-start=&quot;1082&quot;&gt;Client로부터 request를 받고 response를 return 하는 API&lt;/li&gt;
&lt;li data-end=&quot;1141&quot; data-start=&quot;1115&quot;&gt;웹 애플리케이션 기준 Controller가 대표적인 구현체&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1288&quot; data-start=&quot;1143&quot;&gt;&lt;b&gt;Application Layer&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1288&quot; data-start=&quot;1173&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1193&quot; data-start=&quot;1173&quot;&gt;&lt;b&gt;Domain Layer에 구현된 메서드&lt;/b&gt;를 &lt;b&gt;호출&lt;/b&gt;하여 하나의 기능을 수행하는 계층&lt;/li&gt;
&lt;li data-end=&quot;1193&quot; data-start=&quot;1173&quot;&gt;여러 도메인 메서드를 호출하고 순서를 제어&lt;/li&gt;
&lt;li data-end=&quot;343&quot; data-start=&quot;302&quot;&gt;여러 도메인 호출을 하나의 트랜잭션으로 묶음&lt;/li&gt;
&lt;li data-end=&quot;434&quot; data-start=&quot;395&quot;&gt;논리적 검증이나 데이터 기반 접근 제어&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1424&quot; data-start=&quot;1290&quot;&gt;&lt;b&gt;Domain Layer&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1424&quot; data-start=&quot;1320&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1345&quot; data-start=&quot;1320&quot;&gt;&lt;b&gt;기능별로 비즈니스 로직을 작성&lt;/b&gt;하는 계층&lt;/li&gt;
&lt;li data-end=&quot;1424&quot; data-start=&quot;1349&quot;&gt;기능별로 메서드로 작성하여야 하며, Application Layer가 호출하여 사용한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1560&quot; data-start=&quot;1426&quot;&gt;&lt;b&gt;Infrastructure Layer&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1560&quot; data-start=&quot;1459&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1518&quot; data-start=&quot;1478&quot;&gt;DB 접근(JPA, JDBC), 메시지 브로커, 외부 API 연동 등 &lt;b&gt;외부와의 통신을 담당하는 계층&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1560&quot; data-start=&quot;1522&quot;&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;195&quot; data-start=&quot;139&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-end=&quot;195&quot; data-start=&quot;139&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;헥사고날 아키텍처 (Hexagonal Architecture / Ports &amp;amp; Adapters)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;366&quot; data-start=&quot;197&quot; data-ke-size=&quot;size16&quot;&gt;DDD에서 말하는 도메인은 &lt;b&gt;외부 기술&lt;/b&gt;(JPA, DB, 프레임워크 등)&lt;b&gt;에 의존하지 않고 순수한 비즈니스 규칙을 표현해야 한다.&lt;/b&gt;&lt;br /&gt;하지만 일반적인 계층형 아키텍처에서는 도메인 코드가 외부 기술과 강하게 결합되어, DB를 바꾸거나 프레임워크를 교체할 때 도메인까지 수정해야 하는 문제가 발생한다.&lt;/p&gt;
&lt;p data-end=&quot;425&quot; data-start=&quot;368&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위한 패턴이 &lt;b&gt;헥사고날 아키텍처(Hexagonal Architecture)&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;425&quot; data-start=&quot;368&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;425&quot; data-start=&quot;368&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 헥사고날 아키텍처의 핵심&lt;/b&gt;은&lt;/p&gt;
&lt;p data-end=&quot;425&quot; data-start=&quot;368&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도메인은 외부를 직접 알지 못하고, 포트(Port, 추상화 인터페이스)만을 알고 있으며, &lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;425&quot; data-start=&quot;368&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 동작은 어댑터(Adapter, 구현체)가 담당&lt;/b&gt;한다는 것이다. &lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;425&quot; data-start=&quot;368&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;139&quot; data-end=&quot;195&quot;&gt;&lt;b&gt;Hexagonal Architecture&lt;/b&gt;&lt;b&gt;의 계층 구조&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1140&quot; data-start=&quot;712&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;806&quot; data-start=&quot;712&quot;&gt;&lt;b&gt;Domain Core (중앙)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;806&quot; data-start=&quot;741&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;790&quot; data-start=&quot;741&quot;&gt;POJO 도메인 코드(인프라 의존성 없음)&lt;/li&gt;
&lt;li data-end=&quot;790&quot; data-start=&quot;741&quot;&gt;예: Entity, VO, Service&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;909&quot; data-start=&quot;808&quot;&gt;&lt;b&gt;Ports (인터페이스)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;909&quot; data-start=&quot;834&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;865&quot; data-start=&quot;834&quot;&gt;도메인이 외부에 기대는 기능 추상화&lt;/li&gt;
&lt;li data-end=&quot;909&quot; data-start=&quot;869&quot;&gt;예: OrderRepository, PaymentGateway&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1140&quot; data-start=&quot;911&quot;&gt;&lt;b&gt;Adapters (구현체)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1140&quot; data-start=&quot;938&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;955&quot; data-start=&quot;938&quot;&gt;포트를 실제 기술로 구현&lt;/li&gt;
&lt;li data-end=&quot;1017&quot; data-start=&quot;959&quot;&gt;예: JpaOrderRepository, StripePaymentGatewayAdapter&lt;/li&gt;
&lt;li data-end=&quot;1090&quot; data-start=&quot;1021&quot;&gt;Inbound Adapter: 외부 요청 들어오는 곳 (REST Controller, CLI, MQ Consumer)&lt;/li&gt;
&lt;li data-end=&quot;1140&quot; data-start=&quot;1094&quot;&gt;Outbound Adapter: 외부로 나가는 곳 (DB, Kafka, API)&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;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 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;139&quot; data-end=&quot;195&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;멀티모듈 (Multi-module)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;296&quot; data-start=&quot;218&quot; data-ke-size=&quot;size16&quot;&gt;헥사고날 아키텍처를 통해 도메인과 기술 구현체를 명확히 분리하면,&lt;br /&gt;도메인은 외부 변화에 영향을 받지 않고 안정적으로 유지할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;453&quot; data-start=&quot;298&quot; data-ke-size=&quot;size16&quot;&gt;하지만 프로젝트 규모가 커지고 여러 도메인이 함께 존재하면,&lt;br /&gt;코드가 한 프로젝트 안에 섞여 관리되면서 &lt;b&gt;복잡성&lt;/b&gt;이 커지고 &lt;b&gt;유지보수성&lt;/b&gt;이 떨어질 수 있다.&lt;br /&gt;이를 해결하는 방법 중 하나가 &lt;b&gt;멀티모듈 구조(Multi-Module Architecture)&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;453&quot; data-start=&quot;298&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;453&quot; data-start=&quot;298&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;619&quot; data-start=&quot;478&quot; data-ke-size=&quot;size16&quot;&gt;멀티모듈은 하나의 프로젝트를 &lt;b&gt;역할이나 도메인 단위로 나누어&lt;/b&gt; 관리하는 방식이다.&lt;br /&gt;즉, 애플리케이션 전체를 하나의 거대한 모놀리식 코드베이스로 두지 않고,&lt;br /&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;786&quot; data-start=&quot;769&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;멀티모듈의 계층 구조 예시&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;885&quot; data-start=&quot;788&quot; data-ke-size=&quot;size16&quot;&gt;멀티모듈은 헥사고날 아키텍처와 결합하여 사용할 때 가장 큰 효과를 발휘한다.&lt;br /&gt;예를 들어, &lt;b&gt;공연 예매 시스템&lt;/b&gt;을 멀티모듈로 나눈다면 다음과 같은 구조를 가질 수 있다&lt;/p&gt;
&lt;pre id=&quot;code_1759014430364&quot; class=&quot;pgsql&quot; style=&quot;background-color: #f8f8f8; color: #333333; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;project-root/
 ├─ ticket-domain/          ️ 티켓 도메인 (Entity, VO, Domain Service)
 ├─ ticket-application/     ️ 티켓 애플리케이션 서비스 (유스케이스, 트랜잭션 관리)
 ├─ ticket-infra/           ️ 티켓 인프라 (DB, 외부 API, Messaging)
 │
 ├─ user-domain/             사용자 도메인
 ├─ user-application/        사용자 애플리케이션 서비스
 ├─ user-infra/              사용자 인프라
 │
 ├─ common/                 ️ 공통 유틸리티, 공용 DTO, 에러 처리
 └─ app/                     실제 실행 진입점 (API 서버, Batch, CLI)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1632&quot; data-start=&quot;1382&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1430&quot; data-start=&quot;1382&quot;&gt;&lt;b&gt;Domain&lt;/b&gt;: 순수한 비즈니스 로직 (POJO), 외부 의존성 없음&lt;/li&gt;
&lt;li data-end=&quot;1492&quot; data-start=&quot;1431&quot;&gt;&lt;b&gt;Application&lt;/b&gt;: 유스케이스, 트랜잭션 제어, 도메인 서비스 orchestration&lt;/li&gt;
&lt;li data-end=&quot;1544&quot; data-start=&quot;1493&quot;&gt;&lt;b&gt;Infra&lt;/b&gt;: Port의 구현체, DB/JPA, 메시징, 외부 API 연동&lt;/li&gt;
&lt;li data-end=&quot;1632&quot; data-start=&quot;1545&quot;&gt;&lt;b&gt;App&lt;/b&gt;: Controller, Batch Job, MQ Consumer 등), 최종 실행 가능 애플리케이션&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;1480&quot; data-start=&quot;1454&quot; data-ke-size=&quot;size16&quot;&gt;이 계층 구조를 헥사고날 아키텍처와 결합하면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1619&quot; data-start=&quot;1483&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1500&quot; data-start=&quot;1483&quot;&gt;Domain = Core&lt;/li&gt;
&lt;li data-end=&quot;1542&quot; data-start=&quot;1503&quot;&gt;Application = Service Orchestration&lt;/li&gt;
&lt;li data-end=&quot;1573&quot; data-start=&quot;1545&quot;&gt;Infra = Outbound Adapter&lt;/li&gt;
&lt;li data-end=&quot;1619&quot; data-start=&quot;1576&quot;&gt;App = Inbound Adapter&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;154&quot; data-start=&quot;132&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;멀티모듈 분리 시 얻을 수 있는 이점&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;555&quot; data-start=&quot;187&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;278&quot; data-start=&quot;187&quot;&gt;&lt;b&gt;관심사 분리&lt;/b&gt;: 도메인&amp;middot;공통&amp;middot;외부 연동 등을 모듈 단위로 나누어 코드 가독성과 유지보수성을 높임&lt;/li&gt;
&lt;li data-end=&quot;343&quot; data-start=&quot;280&quot;&gt;&lt;b&gt;재사용성 증대&lt;/b&gt;: 공통 모듈을 여러 서비스에서 활용할 수 있어 중복을 줄이고 일관성을 확보&lt;/li&gt;
&lt;li data-end=&quot;412&quot; data-start=&quot;345&quot;&gt;&lt;b&gt;의존성 관리 용이&lt;/b&gt;: 모듈별로 필요한 라이브러리만 의존하도록 설정해 빌드 크기와 충돌 위험을 줄임&lt;/li&gt;
&lt;li data-end=&quot;482&quot; data-start=&quot;414&quot;&gt;&lt;b&gt;병렬 개발 및 협업 효율성&lt;/b&gt;: 팀 단위로 모듈을 분리해 독립적으로 개발&amp;middot;테스트 가능 &amp;rarr; 충돌 최소화&lt;/li&gt;
&lt;li data-end=&quot;555&quot; data-start=&quot;484&quot;&gt;&lt;b&gt;확장성과 안정성&lt;/b&gt;: 새로운 기능 추가 시 기존 모듈에 영향을 덜 주며, 특정 모듈만 교체하거나 개선 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-end=&quot;578&quot; data-start=&quot;562&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-end=&quot;229&quot; data-start=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;멀티모듈 구조는 &lt;b&gt;모듈 단위로 코드를 나누어 각 모듈이 필요한 의존성만 가지도록 설계&lt;/b&gt;함으로써, 빌드 속도를 줄이고 불필요한 라이브러리 충돌을 최소화할 수 있다.&lt;br /&gt;이렇게 명확하게 모듈 경계를 설정하면, 여러 개발자가 동시에 같은 코드베이스에서 작업하더라도 &lt;b&gt;충돌 가능성을 줄이고 독립적인 개발이 가능&lt;/b&gt;해진다.&lt;/p&gt;
&lt;p data-end=&quot;335&quot; data-start=&quot;231&quot; data-ke-size=&quot;size16&quot;&gt;또한, 각 모듈이 독립적인 단위로 관리되기 때문에, 서비스 확장이 필요할 때는 특정 모듈만 교체하거나 개선하는 것이 용이하며, 전체 애플리케이션에 미치는 영향을 최소화할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;433&quot; data-start=&quot;337&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;멀티모듈은 빌드 효율과 의존성 관리의 기술적 장점&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;236&quot; data-start=&quot;138&quot; data-ke-size=&quot;size16&quot;&gt;DDD를 통해 문제 영역을 명확히 정의하고,&lt;br /&gt;헥사고날 아키텍처로 도메인을 외부 기술로부터 철저히 격리하며,&lt;br /&gt;멀티모듈 구조로 코드베이스를 역할 단위로 나누면,&lt;/p&gt;
&lt;p data-end=&quot;308&quot; data-start=&quot;238&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 변화에 유연하고 유지보수가 쉬우며, 팀 단위 협업과 확장성까지 고려한 &lt;b&gt;견고한 아키텍처&lt;/b&gt;를 구현할 수 있다.&lt;/p&gt;</description>
      <category>아키텍처</category>
      <author>KJihun</author>
      <guid isPermaLink="true">https://developerhun.tistory.com/269</guid>
      <comments>https://developerhun.tistory.com/269#entry269comment</comments>
      <pubDate>Sun, 14 Sep 2025 14:40:53 +0900</pubDate>
    </item>
  </channel>
</rss>