<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Hello, Hannah!</title>
    <link>https://prohannah.tistory.com/</link>
    <description>개발 블로그</description>
    <language>ko</language>
    <pubDate>Wed, 20 May 2026 08:11:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>iia_hannah</managingEditor>
    <image>
      <title>Hello, Hannah!</title>
      <url>https://tistory1.daumcdn.net/tistory/2570674/attach/47a72c4960c04c178f5d6845040057e7</url>
      <link>https://prohannah.tistory.com</link>
    </image>
    <item>
      <title>2025.12.10 사내 발표 - 들쭉날쭉한 AI 품질, 팀 SSOT 문서로 해결하기</title>
      <link>https://prohannah.tistory.com/279</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 '팀 차원에서 시스템적으로 AI 품질을 어떻게 올릴 수 있을까?'에 대한 고민과 해결 방법에 대해 사내 발표를 했다. 되도록 개인의 역량에 의존하지 않고, 팀 차원에서 AI를 활용하여 시스템적으로 안정적으로 서비스를 운영할 수 있는 방법론에 대해 소개한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;개발팀의 고민: AI 품질 문제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 활용한 개발에서 가장 중요한 것은 '속도'가 아니라 '정확한 방향'입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AI의 구조적 한계&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프롬프트 의존성&lt;br /&gt;개발자는 작업을 시작하기 전, 스스로 과거의 히스토리를 파악하고 도메인 정책을 검토합니다.&lt;br /&gt;반면, AI는 사용자가 입력한 프롬프트와 연관된 코드만 읽습니다. 스스로 과거 작업이나 도메인 지식을 찾지 못하고, 오로지 입력된 프롬프트에만 의존합니다.&lt;/li&gt;
&lt;li&gt;불확실한 요구사항(Gray Zone)의 임의 추론&lt;br /&gt;&lt;b&gt;명확하지 않은 요구사항은 가장 그럴듯한 로직으로 채워 넣기 때문에, 의도와 다른 구현이 반영될 가능성이 높습니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 충분한 맥락을 제공한 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&quot;구좌 신청 임시저장 API를 만들어줘. 임시저장은 제출하기 전에 사용자가 데이터를 보관하는 용도로 사용하는 것이야. 그래서 입력한 값에 대해서만 최소한의 Validation을 통과하면 DRAFT 상태로 저장해. 검증이 필요한 항목은 다음과 같이...&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 의도한 구현&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 컨텍스트가 부족한 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&quot;구좌 신청 임시저장 API 만들어줘.&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; AI가 Gray 영역을 스스로 추론하여 개발하므로, 일관성이 없거나 도메인 정책 위반됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 아래와 같은 문제를 야기합니다. 이것은 개인이 아닌 팀 전체의 비용입니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;개인이 아닌 팀 전체의 비용&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GHpli/dJMcabQkPuV/CsX3djKfXy93UrseuTK8HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GHpli/dJMcabQkPuV/CsX3djKfXy93UrseuTK8HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GHpli/dJMcabQkPuV/CsX3djKfXy93UrseuTK8HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGHpli%2FdJMcabQkPuV%2FCsX3djKfXy93UrseuTK8HK%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;1366&quot; height=&quot;425&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AI와 일을 잘 하려면?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 비용을 개인의 문제가 아니라 팀의 부채로 보고, 팀 차원에서 어떻게 하면 의도한 대로 AI를 일 시킬 수 있을까 고민하게 되었습니다.&lt;br /&gt;이 글에서는 두 가지 측면에서 해결 방법을 각각 제안합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;프롬프트 엔지니어링&lt;/b&gt; : AI에게 어떻게 물어볼 것인가&lt;/li&gt;
&lt;li&gt;&lt;b&gt;충분한 컨텍스트&lt;/b&gt; : AI에게 무엇을 알려줄 것인가&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Step 1. 명세를 통한 정확한 구현 (SDD)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SDD 란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SDD(Spec-Driven Development)는 코드를 작성하기 전에 먼저 명세(Specification)를 작성하고, 이를 기반으로 개발을 진행하는 방식입니다. AI에게 &quot;무엇을 만들어야 하는지&quot;를 명확하게 전달한다면 의도한 결과물을 얻을 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SDD 개발 방법론으로 구현하는 과정을 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MzKbD/dJMcacBIt7z/NSnmlFZ3bE5N5RLjVDkPlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MzKbD/dJMcacBIt7z/NSnmlFZ3bE5N5RLjVDkPlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MzKbD/dJMcacBIt7z/NSnmlFZ3bE5N5RLjVDkPlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMzKbD%2FdJMcacBIt7z%2FNSnmlFZ3bE5N5RLjVDkPlk%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;1590&quot; height=&quot;115&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프롬프트 엔지니어링은 SDD 도구가 책임진다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SDD 도구(spec-kit, task-master-ai 등)가 개발자가 구현을 위해 수행하던 프로세스들을 추상화하여 구조화된 명령어 로 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 더보기를 통해 spec-kit 기준으로 명령어별 어떤 개발 프로세스를 추상화했는지 간략히 소개합니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;spec-kit에서 제공하는 /specify, /plan, /tasks 명령어 통해 명세와 Task 단위의 TODO List가 작성됩니다.&lt;br /&gt;명령어는 주기적으로 추가되고 내용이 조금씩 변경되고 있어서 간략한 설명 위주로 안내드립니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;/speckit.specify - 명세 작성&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브랜치 자동 생성 (5-feature-name)&lt;/li&gt;
&lt;li&gt;WHAT(무엇) + WHY(왜) 집중 - 기술 용어 금지&lt;/li&gt;
&lt;li&gt;최대 3개 [NEEDS CLARIFICATION] 마커만 허용&lt;/li&gt;
&lt;li&gt;품질 체크리스트 자동 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;/speckit.clarify - 명확화 (Optional)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최대 5개 질문만 (우선순위: 범위 &amp;gt; 보안 &amp;gt; UX &amp;gt; 기술)&lt;/li&gt;
&lt;li&gt;각 질문에 추천 옵션 제공&lt;/li&gt;
&lt;li&gt;답변 즉시 spec.md에 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;/speckit.plan - 구현 계획&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;constitution.md 원칙 준수 검증&lt;/li&gt;
&lt;li&gt;research.md - 기술 결정 및 근거&lt;/li&gt;
&lt;li&gt;data-model.md - 엔티티 설계&lt;/li&gt;
&lt;li&gt;contracts/ - API 스펙&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;/speckit.tasks - 작업 분해&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;User Story 기반 Phase 구성&lt;/li&gt;
&lt;li&gt;의존성 순서 정렬&lt;/li&gt;
&lt;li&gt;병렬 실행 가능 태스크 [P] 표시&lt;/li&gt;
&lt;li&gt;체크리스트 형식: - [ ] T001 [P] [US1] 설명 + 파일경로&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;/speckit.analyze - 일관성 검증 (Optional)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;읽기 전용 (파일 수정 안 함)&lt;/li&gt;
&lt;li&gt;누락된 요구사항 감지&lt;/li&gt;
&lt;li&gt;constitution 위반 = CRITICAL&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;/speckit.implement - 자동 구현&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;체크리스트 완료 여부 확인&lt;/li&gt;
&lt;li&gt;TDD: 테스트 먼저 &amp;rarr; 구현 &amp;rarr; 커밋&lt;/li&gt;
&lt;li&gt;작업 완료 시 [X]로 자동 체크&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 명령어에는 이러한 복잡한 작업들을 수행하는 정교한 프롬프트가 내장되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 복잡한 프롬프트 작성 대신, 무엇을 만들지 순수한 의도 전달에만 집중하면 됩니다.&lt;br /&gt;개인의 프롬프트 작성 능력과 무관하게, 맥락을 충분히 제공하는 것만으로도 의도한 대로 정확한 구현이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추천 도구 : spec-kit, task-master-ai&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SDD 방법론을 적용한 API 구현 예시 (spec-kit 활용)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SDD 도구 중 하나인 spec-kit 을 이용해서 개발한 API PR을 살펴보겠습니다.&lt;br /&gt;명세 기반 개발을 위한 AI 워크플로우 도구로 Specification &amp;rarr; Plan &amp;rarr; Tasks &amp;rarr; Implementation 단계로 구조화 되어있다는 특징이 있습니다. (spec-kit 외에도 task-master 등 다양한 도구들이 있습니다.)&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 펼치기를 통해 확인하실 수 있습니다.&lt;br /&gt;spec-kit 사용하지 않으신 분들은 실제 사례를 통해, &amp;lsquo;나에게 정말 도움이 될까?&amp;rsquo; 살펴보실 수 있습니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;(1) 명세서 작성하기&lt;/h3&gt;
&lt;pre id=&quot;code_1766557403741&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; ▐▛███▜▌   Claude Code v2.0.25
▝▜█████▛▘  Opus 4.1 &amp;middot; Claude Max
  ▘▘ ▝▝    /Users/hannah/workspaces/my-project

────────────────────────────────────────────────────────────────────────────────────
# 명세 작성 - 무엇을, 왜
&amp;gt;&amp;nbsp;/specify ExampleEntity을 읽어서 {uuid}/pre-application API를 완성해보자.
  ...

# 구현 계획
&amp;gt; /plan
  ...

# 작업 세분화
&amp;gt; /tasks
  ...
────────────────────────────────────────────────────────────────────────────────────&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 구현 시에는 API 인터페이스를 맥락으로 제공하는 것이 효과적이므로, Mock API와 참조해야하는 핵심 Entity 1개에 대한 정보만 제공하였습니다.&lt;br /&gt;Mock API 대신 json이나 간단한 문서 형태를 전달해도 잘 이해합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1766557469088&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/inventories&quot;)
class InventoryUiSchemaController {
    @GetMapping(&quot;{uuid}/ui-schema/pre-application&quot;)
    fun getPreApplicationSchema(): MarketingSlotApiSimpleResponse {
        // 사전신청 없으면 NotFound
        return MarketingSlotApiSimpleResponse(
            data =
                PreApplicationCreateUiSchemaResponse(
                    eventName = PreApplicationCreateUiSchemaResponse.CommonAttributeSchema(),
                    adAccountIds = PreApplicationCreateUiSchemaResponse.CommonAttributeSchema(),
                    requestedStartDateTime = PreApplicationCreateUiSchemaResponse.CommonAttributeSchema(),
                    requestedEndDateTime = PreApplicationCreateUiSchemaResponse.CommonAttributeSchema(),
                    specialNotes = PreApplicationCreateUiSchemaResponse.CommonAttributeSchema(),
                    targetRevenue = PreApplicationCreateUiSchemaResponse.CommonAttributeSchema(),
                    planningScale = PreApplicationCreateUiSchemaResponse.CommonAttributeSchema(),
                    additionalAttributes = null,
                ),
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot;&gt;명령어 수행 결과로 아래와 같은 파일들이 생성됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;207&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vlIVq/dJMcabQkPix/f3cs9RIVAvzd5UfvyZILL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vlIVq/dJMcabQkPix/f3cs9RIVAvzd5UfvyZILL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vlIVq/dJMcabQkPix/f3cs9RIVAvzd5UfvyZILL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvlIVq%2FdJMcabQkPix%2Ff3cs9RIVAvzd5UfvyZILL1%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;207&quot; height=&quot;255&quot; data-origin-width=&quot;207&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;(2) 구현하기&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구현 시 spec-kit의 /implement 명령어을 이용한다면, 기존에 작성한 명세 문서들을 자동으로 활용하여 구현합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전 코드에서는 API Request, Response만 존재할뿐, 서비스도 없었습니다.&lt;br /&gt;1번, 2번 명령어를 통해 명세(spec)을 구체화한 뒤 작업을 요청하였기에 기대한 구현 결과를 얻을 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;(3) Jira 티켓에 갱신&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;야무지게 만들어진 명세서, 이대로 휘발성으로 날리기에는 아쉽지 않나요?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Spec-kit이 생성한 spec.md와 tasks.md는 구현을 위한 명세서이자 최종 작업 결과에 대한 설명입니다. 이 문서를 요약해 Jira 티켓에 최종 반영함으로써 다음과 같은 이점을 얻을 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;지식의 영구 보존:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;코드는 변경되지만, 무엇을 의도했는지에 대한 명확한 명세가 Jira 티켓에 남습니다. 향후 QA, PM, 신규 개발자가 이 기능을 다시 확인할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;PR과 코드 베이스 전체를 탐색할 필요 없이&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Jira 티켓 하나로 모든 맥락을 파악할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;협업의 효율화:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Spec-kit에 포함된 /summarize 등 요약 기능을 활용하거나, 직접&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;핵심 비즈니스 규칙&lt;/b&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;최종 구현 결정 사항&lt;/b&gt;을 발췌하여 Jira 댓글 혹은 본문에 갱신해달라고 AI에게 요청합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;  적용 예시:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;/summarize spec.md and tasks.md and update jira ticket #1234&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검증의 편의성:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;PM/PO가 Jira 티켓만 보고도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;최종 구현 결과가 요구사항을 충족했는지&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명세를 통해 빠르고 쉽게 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Atlassian MCP 추가하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766557565671&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;claude mcp add --transport sse atlassian https://mcp.atlassian.com/v1/sse&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;여전히 남아있는 컨텍스트 제공 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트 엔지니어링 문제는 Step1에서 SDD 도구를 통해 해결했습니다.&lt;br /&gt;하지만 여전히 컨텍스트 제공에 대한 문제는 남아있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KQ3Tg/dJMcahwfsaa/8qdjvyIatD0dUo4CUcgvC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KQ3Tg/dJMcahwfsaa/8qdjvyIatD0dUo4CUcgvC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KQ3Tg/dJMcahwfsaa/8qdjvyIatD0dUo4CUcgvC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKQ3Tg%2FdJMcahwfsaa%2F8qdjvyIatD0dUo4CUcgvC0%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;1180&quot; height=&quot;251&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 일을 잘 하기 위해서는 충분한 컨텍스트 제공에 대한 방법도 고민해야합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그렇다면 명세서를 컨텍스트로 재활용할 수 없을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명세서는 특정 작업을 위한 명세서이자 구현을 위한 최고의 프롬프트입니다.&lt;br /&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; : 일회성 명세 문서는 시간이 지날수록 쌓이기만 하고 최신화되지 않습니다. Outdated되어 최신 정책을 반영하지 못해 재활용이 불가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지식의 파편화&lt;/b&gt; : 도메인 지식이 N개의 명세 문서에 흩어져 있습니다. 어떤 문서가 최신이고 정확한지 판단하기 어렵습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 일회성 작업 명세서(spec.md, tasks.md 등)들은 .gitignore에 추가하여 폐기합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심은 팀 SSOT 구축&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일회성 작업 명세서에서 끝나는 것이 아니라,&lt;br /&gt;그 안에 담긴 &lt;b&gt;도메인 컨텍스트를 팀의 영구적인 지식 자산으로 전환&lt;/b&gt;해야 합니다.&lt;br /&gt;이 과정을 통틀어 &lt;b&gt;CI-SDD (Continuously Integrated Spec-Driven Development)&lt;/b&gt;라고 표현합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Step 2. 팀 SSOT의 지속적 통합 (CI-SDD)  &lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CI-SDD란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Step 2에서는 컨텍스트 제공 문제를 해결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CI/CD가 &quot;코드의 지속적 통합&quot;을 의미하듯이,&lt;br /&gt;이 글에서 CI-SDD는 SDD를 넘어 팀 SSOT(Single Source of Truth)로의 지속적 통합을 의미합니다. (  개발자에게 개념을 직관적으로 이해시키기 위해 가상의 단어입니다)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;선순환 구조의 지속적인 지식 통합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명세를 통해 정확한 구현을 하는 것(Step 1)을 넘어,&lt;br /&gt;그 지식을 통합하고 축적하여 팀의 단일 진실 원천 자산으로 만들어내는 것(Step 2)까지 포함한 흐름을 설명합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1593&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJWeWp/dJMcagxk1TR/gPVpW7KYCBZKDYVWt95mxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJWeWp/dJMcagxk1TR/gPVpW7KYCBZKDYVWt95mxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJWeWp/dJMcagxk1TR/gPVpW7KYCBZKDYVWt95mxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJWeWp%2FdJMcagxk1TR%2FgPVpW7KYCBZKDYVWt95mxK%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;1593&quot; height=&quot;175&quot; data-origin-width=&quot;1593&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;흐름 설명&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;작업에 필요한 풍부한 맥락을 제공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SDD 도구 덕분에 프롬프트 엔지니어링에 대한 고민 불필요&lt;/li&gt;
&lt;li&gt;무엇을 만들지에 대한 맥락 제공에 집중&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AI는 사용자가 입력한 프롬프트와 팀의 SSOT 문서를 활용&lt;/li&gt;
&lt;li&gt;명세를 기반으로 구현 완료&lt;/li&gt;
&lt;li&gt;팀 SSOT 문서 최신화
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;새롭게 발견/변경된 도메인 지식을 팀 SSOT 문서에 반영&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;다음 작업 시 AI가 더 완벽한 컨텍스트를 가지고 작업 시작!&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  시간이 지날수록&amp;hellip;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀의 SSOT의 품질 &amp;uarr;&lt;/li&gt;
&lt;li&gt;개발자가 제공해야할 맥락과 노력 &amp;darr; (이미 SSOT 문서에 있는 내용)&lt;/li&gt;
&lt;li&gt;AI의 품질 &amp;uarr; (SSOT를 기반으로 일관된 출력)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문서 관리 예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 구조&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;647&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJVrmn/dJMcaaw783Z/oKzOCfDCqARpxDXfkLcZm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJVrmn/dJMcaaw783Z/oKzOCfDCqARpxDXfkLcZm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJVrmn/dJMcaaw783Z/oKzOCfDCqARpxDXfkLcZm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJVrmn%2FdJMcaaw783Z%2FoKzOCfDCqARpxDXfkLcZm1%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;1307&quot; height=&quot;647&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;647&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CLAUDE.md&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 전 .team/prd 문서를 읽고, 작업 후 문서 최신화할 것을 명시하세요. Claude Code의 경우 CLAUDE.md 를 시스템 프롬프트로서 항상 작업전에 읽고 시작합니다. AI Agent별로 약속된 시스템 프롬프트에 작성하시면 됩니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;PR 리뷰 시, AI에게 팀 SSOT 문서 검토 요청&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI PR 코드 리뷰 시에, 작업한 내용이 팀 SSOT 문서에 정책 반영이 되어있는지 혹은 문서와 다른 코드 반영이 있는지 등을 검토 요청합니다. 이 덕분에 코드가 변경되는 시점마다 문서가 최신화되었는지 작업자와 동료가 인지할 수 있도록 합니다.&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;563&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHVMKE/dJMcaiWbnQn/KRhSqKhDsamGyg0Pg9PkC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHVMKE/dJMcaiWbnQn/KRhSqKhDsamGyg0Pg9PkC1/img.png&quot; data-alt=&quot;AI PR 피드백 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHVMKE/dJMcaiWbnQn/KRhSqKhDsamGyg0Pg9PkC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHVMKE%2FdJMcaiWbnQn%2FKRhSqKhDsamGyg0Pg9PkC1%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;430&quot; height=&quot;599&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AI PR 피드백 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;활용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀 SSOT 문서를 활용한다면 개발자는 간결한 지시로도 AI에게 풍부한 컨텍스트를 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Before : 개인의 지식에 의존&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;PreApplicationController에 제출 API를 구현해줘.  사전신청서는 이벤트명, 광고계정ID, 요청 시작일시, 종료일시가 필요하고, 제출 시 Validation 로직은 CommonAttribute와 AdditionalAttribute에 있는... (30줄 설명)&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 개발자가 인지하고 있는 범위의 맥락만 제공&lt;br /&gt;&amp;rarr; 팀원마다 차이 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;After : 팀 SSOT 문서 활용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.team/prd/사전신청서 생성 문서.md를 참조해서 PreApplicationController에 제출 API를 구현해&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 팀 SSOT 문서에 정리된 완벽한 컨텍스트를 AI가 활용&lt;br /&gt;&amp;rarr; 모든 팀원이 상향 평준화된 AI 품질 획득&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀 SSOT 문서가 쌓였기 때문에 짧은 프롬프트 요청만으로 원하던 요구사항을 충족한 API를 뚝딱 만들 수 있었습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;맺음말: 프롬프트 + 팀 SSOT = 완벽한 협업  &lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 잘 일하려면 두 가지가 필요합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;프롬프트 엔지니어링&lt;/b&gt; (How to Ask) &amp;rarr; Step 1 (SDD): 도구를 통한 구조화된 프롬프트 활용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;충분한 컨텍스트&lt;/b&gt; (What to Give) &amp;rarr; Step 2 (CI-SDD): 선순환 구조의 팀 SSOT 문서 관리 체계 구축&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서 제안하는 CI-SDD(Step 1 + Step 2)는 단순한 문서 작업이 아니라, AI 시대에 소프트웨어 개발팀이 가질 수 있는 가장 강력한 경쟁력입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫째, 팀의 일관된 품질을 보장합니다.&lt;/b&gt;&lt;br /&gt;개개인이 알고 있는 컨텍스트가 아닌, 팀 전체가 공유하는 단일 진실의 원천(Single Source of Truth)을 AI에게 제공함으로써, 개인 역량 차이에 관계없이 일관되고 높은 품질의 AI Output을 확보하게 됩니다. 이는 디버깅과 재작업 비용을 획기적으로 줄여주는 미래를 위한 투자입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;둘째, 지식의 복리 효과를 만듭니다.&lt;/b&gt;&lt;br /&gt;일회성 명세가 아닌, 살아있는 도메인 지식 베이스를 구축하여 프로젝트의 핵심 자산을 영구적으로 보존합니다. 이는 시간이 지날수록 지식의 선순환 구조를 만들어 도메인 문서 품질과 AI의 개발 효율이 동시에 상승하는 복리 효과를 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;셋째, 지속 가능한 개발을 가능하게 합니다.&lt;/b&gt;&lt;br /&gt;지속 가능한 도메인 관리를 통해, 티켓 단위의 빠른 성과를 넘어 프로젝트 전체의 안정성과 지속 가능성을 확보할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  AI와 더 정확하고 빠르게 일해봅시다  &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제안하는 액션&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;SDD 도구 설치하기 (spec-kit, task-master-ai를 추천합니다)&lt;/li&gt;
&lt;li&gt;작업마다 SDD 적용하기&lt;/li&gt;
&lt;li&gt;팀 SSOT 문서 작성하기&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>업무일지</category>
      <category>CI-SDD</category>
      <category>SDD</category>
      <category>spec kit</category>
      <category>Spec-Driven Development</category>
      <category>specification</category>
      <category>명세주도개발</category>
      <author>iia_hannah</author>
      <guid isPermaLink="true">https://prohannah.tistory.com/279</guid>
      <comments>https://prohannah.tistory.com/279#entry279comment</comments>
      <pubDate>Sat, 27 Dec 2025 20:18:00 +0900</pubDate>
    </item>
    <item>
      <title>10배 빠른 분산 시스템 디버깅: Micrometer Tracing으로 Trace ID 통합하기</title>
      <link>https://prohannah.tistory.com/276</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 추적으로 마이크로서비스의 복잡한 로그를 단 하나의 Trace ID로 추적하세요&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;개요: 분산 추적의 필요성&lt;/li&gt;
&lt;li&gt;Spring Micrometer Tracing 핵심 이해&lt;/li&gt;
&lt;li&gt;주요 환경별 Trace ID 활용&lt;/li&gt;
&lt;li&gt;로그 통합 및 실전 활용&lt;/li&gt;
&lt;li&gt;실무 활용 꿀팁&lt;/li&gt;
&lt;li&gt;최종 구현 및 트러블슈팅&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요: 분산 추적의 필요성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이런 경험 있으신가요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시나리오 1: Slack 장애 알림&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;#server-error 채널
  [결제 API 처리 실패]
Error: Payment gateway timeout
Time: 2025-11-19 10:30:45

개발자: (OpenSearch/Kibana 접속)
개발자: (시간대 검색)
개발자: (에러 키워드 검색)
개발자: (수 많은 로그 중 관련 로그 찾기...  )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시나리오 2: 동시 실행 Batch Job 혼돈&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[02:00:00] INFO - dailySettlementJob started
[02:00:01] INFO - monthlyReportJob started
[02:00:02] INFO - Processing settlement for 2025-11
[02:00:02] INFO - Generating report for 2025-11
[02:00:03] INFO - Settlement completed
[02:00:03] INFO - Report generation failed  &amp;larr; 어느 Job의 로그?
[02:00:04] ERROR - Database connection timeout

개발자: &quot;어느 Job에서 에러가 났지?&quot;
개발자: &quot;Settlement? Report? 둘 다 02시 처리 중이네...&quot;
개발자: (로그 타임스탬프 하나하나 맞춰가며 추적...  )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시나리오 3: Kafka Consumer 메시지 추적 불가&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[10:30:45.123] INFO - Received product event: productId=12345
[10:30:45.234] INFO - Updating inventory
[10:30:45.456] INFO - Received product event: productId=67890
[10:30:45.567] INFO - Publishing notification
[10:30:45.678] ERROR - Inventory update failed  &amp;larr; 어떤 productId?

개발자: &quot;12345 업데이트 실패? 67890 실패?&quot;
개발자: &quot;타임스탬프로 추론하면... 아니 동시 처리라 헷갈려  &quot;
개발자: (동시 처리된 메시지들의 로그가 뒤섞여서 추적 불가능...)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이제는 이렇게 바뀌었습니다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Trace ID 하나로 모든 로그 추적&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 시나리오 1 해결: API/Slack 에러 추적&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;erlang-repl&quot;&gt;&lt;code&gt;API 에러 응답:
{
  &quot;message&quot;: &quot;결제 처리 실패&quot;,
  &quot;traceId&quot;: &quot;690dae8f00000000de283d7148306199&quot;  &amp;larr; 이거 하나만 있으면!
}

Slack 알림:
  [결제 처리 실패] (log - 690dae8f00000000de283d7148306199)
                              &amp;uarr; 클릭 시 OpenSearch 자동 검색!

OpenSearch:
검색어: &quot;690dae8f00000000de283d7148306199&quot;
결과: 해당 요청의 모든 로그 (15개) - 1초 만에 조회 완료! ✨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 시나리오 2 해결: Batch Job 로그 구분&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[02:00:00] [trace=abc123...] INFO - dailySettlementJob started
[02:00:01] [trace=def456...] INFO - monthlyReportJob started
[02:00:02] [trace=abc123...] INFO - Processing settlement for 2025-11
[02:00:02] [trace=def456...] INFO - Generating report for 2025-11
[02:00:03] [trace=abc123...] INFO - Settlement completed
[02:00:03] [trace=def456...] INFO - Report generation failed  &amp;larr; def456으로 즉시 식별!
[02:00:04] [trace=def456...] ERROR - Database connection timeout

OpenSearch 검색: &quot;def456&quot;
&amp;rarr; monthlyReportJob의 모든 로그만 필터링! (5초 완료) ✨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 시나리오 3 해결: Kafka 메시지별 로그 추적&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[10:30:45.123] [trace=aaa111...] INFO - Received product event: productId=12345
[10:30:45.234] [trace=aaa111...] INFO - Updating inventory
[10:30:45.456] [trace=bbb222...] INFO - Received product event: productId=67890
[10:30:45.567] [trace=bbb222...] INFO - Publishing notification
[10:30:45.678] [trace=aaa111...] ERROR - Inventory update failed  &amp;larr; aaa111 = productId 12345!

OpenSearch 검색: &quot;aaa111&quot;
&amp;rarr; productId=12345 처리 과정의 모든 로그만 추출! ✨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Trace ID 활용 목적&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;API 요청 추적&lt;/b&gt;: 클라이언트 요청부터 응답까지의 전체 흐름&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로그 통합&lt;/b&gt;: 동일 요청에서 발생한 모든 로그를 하나의 ID로 필터링&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장애 대응&lt;/b&gt;: 에러 발생 시 관련 로그를 신속하게 검색&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 모니터링&lt;/b&gt;: Zipkin 등에서 성능 병목 지점 식별&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Spring Micrometer Tracing 핵심 이해&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1. Spring Micrometer Tracing 소개&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring Micrometer Tracing&lt;/b&gt;을 사용하여 분산 추적 시스템을 구축할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Micrometer Tracing이란?&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;Spring Boot의 &lt;b&gt;공식 분산 추적 추상화 레이어&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;APM 도구에 종속되지 않는 &lt;b&gt;벤더 중립적 설계&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Zipkin, Jaeger 등 &lt;b&gt;다양한 백엔드 지원&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시스템 구성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;애플리케이션 코드
      &amp;darr;
Micrometer Tracing API (벤더 중립)
      &amp;darr;
Brave 구현체 (Zipkin 기반)
      &amp;darr;
┌─────────────┬──────────────┬──────────────┐
│   Zipkin    │    Jaeger    │ Elastic APM  │  &amp;larr; 선택 가능!
└─────────────┴──────────────┴──────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 Zipkin을 예시로 사용하지만 다른 도구로 교체 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 가치&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;Micrometer 추상화 &amp;rarr; APM 도구 독립성 &amp;rarr; 요청/Job/메시지별 고유 ID
&amp;rarr; 로그 추적 간소화 &amp;rarr; 장애 대응 시간 단축 &amp;rarr; 개발 생산성 향상&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;통합 메커니즘&lt;/b&gt;: &lt;b&gt;W3C TraceContext 표준&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;APM agent가 HTTP 요청에 &lt;code&gt;traceparent&lt;/code&gt; 헤더 주입&lt;/li&gt;
&lt;li&gt;Brave (Micrometer 구현체)가 이 헤더를 읽어서 동일한 trace ID 사용&lt;/li&gt;
&lt;li&gt;코드 변경 없이 표준 헤더만으로 APM 통합 완성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;APM 도구 교체&lt;/b&gt; 코드 변경 없이 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2. Trace ID vs Span ID&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Trace ID&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: 전체 요청 흐름을 식별하는 고유 ID&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;128-bit hex 형식: &lt;code&gt;690dae8f00000000de283d7148306199&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 시나리오&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;HTTP Request &amp;rarr; Service A &amp;rarr; Service B &amp;rarr; Service C
     └─────────── 동일 Trace ID ─────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Span ID&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: 요청 내 개별 작업 단위를 식별하는 ID&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; ID&lt;/li&gt;
&lt;li&gt;Thread가 바뀌면 &lt;b&gt;새로운 Span&lt;/b&gt; 생성&lt;/li&gt;
&lt;li&gt;64-bit hex 형식: &lt;code&gt;0123456789abcdef&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 시나리오&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;HTTP Request (span: a1b2c3d4)
  ├─ DB Query (span: e5f6g7h8)
  ├─ External API Call (span: i9j0k1l2)
  └─ Cache Access (span: m3n4o5p6)

&amp;rarr; Trace ID: 동일
&amp;rarr; Span ID: 각각 다름&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코드로 확인하기&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
class ExampleController(
    private val tracer: Tracer,  // Micrometer Tracer
) {
    @GetMapping(&quot;/api/example&quot;)
    fun example(): Map&amp;lt;String, String&amp;gt; {
        val currentSpan = tracer.currentSpan()

        return mapOf(
            &quot;traceId&quot; to currentSpan.context().traceId(),     // 690dae8f00000000de283d7148306199
            &quot;spanId&quot; to currentSpan.context().spanId(),       // 0123456789abcdef
            &quot;parentSpanId&quot; to (currentSpan.context().parentId() ?: &quot;none&quot;),
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;traceId&quot;: &quot;690dae8f00000000de283d7148306199&quot;,
  &quot;spanId&quot;: &quot;0123456789abcdef&quot;,
  &quot;parentSpanId&quot;: &quot;fedcba9876543210&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3. 전체 아키텍처 개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Micrometer Tracing의 핵심은 &lt;b&gt;추상화 레이어&lt;/b&gt;를 통해 APM 도구에 종속되지 않는 구조입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;레이어 아키텍처&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────┐
│  Layer 1: Application Code                              │
│  ┌──────────────────────────────────────────────────┐   │
│  │  @RestController, @Service, @Repository          │   │
│  │  - 비즈니스 로직 작성                             │   │
│  │  - Micrometer API만 사용 (Tracer, Span)          │   │
│  └──────────────────────────────────────────────────┘   │
└────────────────────────┬────────────────────────────────┘
                         │ 의존성: Micrometer Tracer 인터페이스
                         ▼
┌─────────────────────────────────────────────────────────┐
│  Layer 2: Micrometer Tracing (추상화 레이어)            │
│  ┌──────────────────────────────────────────────────┐   │
│  │  io.micrometer.tracing.Tracer                    │   │
│  │  io.micrometer.tracing.Span                      │   │
│  │  - 벤더 중립적 API 제공                           │   │
│  │  - W3C TraceContext 표준 지원                    │   │
│  └──────────────────────────────────────────────────┘   │
└────────────────────────┬────────────────────────────────┘
                         │ 구현체 선택 (둘 중 하나)
                         ▼
┌─────────────────────────────────────────────────────────┐
│  Layer 3: Implementation Bridge (구현체)                │
│  ┌────────────────────┐   ┌─────────────────────────┐   │
│  │  Brave Bridge      │   │  OpenTelemetry Bridge   │   │
│  │  (Zipkin 기반)     │ OR│  (OTEL 기반)            │   │
│  │                    │   │                         │   │
│  │  - Trace 생성      │   │  - Trace 생성           │   │
│  │  - Span 관리       │   │  - Span 관리            │   │
│  │  - MDC 자동 설정   │   │  - MDC 자동 설정        │   │
│  │  - W3C 헤더 생성   │   │  - W3C 헤더 생성        │   │
│  └────────────────────┘   └─────────────────────────┘   │
└────────────────────────┬────────────────────────────────┘
                         │ W3C TraceContext 표준 헤더
                         │ (traceparent: 00-&amp;lt;trace-id&amp;gt;-...)
                         ▼
┌─────────────────────────────────────────────────────────┐
│  Layer 4: APM Backend (자유롭게 선택 가능  )           │
│  ┌──────┬──────┬──────────┬────────┬─────────────┐     │
│  │Zipkin│Jaeger│ Datadog  │ Elastic│New Relic 등 │     │
│  │      │      │   APM    │  APM   │             │     │
│  └──────┴──────┴──────────┴────────┴─────────────┘     │
│  - W3C 표준 헤더로 trace 수신                           │
│  - Trace 저장/집계/시각화                               │
│  - 알림 및 분석 기능                                    │
└─────────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 설계 원칙&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1️⃣ APM 도구는 자유롭게 교체 가능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Micrometer Tracing을 사용하면 &lt;b&gt;애플리케이션 코드를 변경하지 않고&lt;/b&gt; APM 도구를 교체할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// ✅ 이 코드는 어떤 APM 백엔드를 사용하든 동일!
@RestController
class UserController(
    private val tracer: Tracer,  // Micrometer 인터페이스
) {
    fun getUser(): UserResponse {
        val traceId = tracer.currentSpan().context().traceId()
        logger.info(&quot;Fetching user, traceId: $traceId&quot;)
        // ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;APM 백엔드 교체 시나리오:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;개발 환경: Zipkin (로컬 Docker)
   &amp;darr;
스테이징: Jaeger (쿠버네티스 클러스터)
   &amp;darr;
운영 환경: Elastic APM (매니지드 서비스)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;b&gt;코드 변경 없이 설정만 변경!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2️⃣ W3C TraceContext 표준 기반&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 APM 백엔드는 W3C 표준 헤더(&lt;code&gt;traceparent&lt;/code&gt;)를 통해 통합됩니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────┐
│  1. HTTP Request 진입                                   │
│     GET /api/users/12345                                │
│     traceparent: 00-690dae8f...-fedcba...-01           │
└────────────────┬────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────┐
│  2. Micrometer Tracing                                  │
│     - traceparent 헤더 파싱 ✅                          │
│     - Trace ID 추출: 690dae8f...                        │
│     - 새로운 Span 생성 (parent: fedcba...)              │
│     - MDC.put(&quot;traceId&quot;, &quot;690dae8f...&quot;)                │
│     - MDC.put(&quot;spanId&quot;, &quot;0123456789...&quot;)               │
└────────────────┬────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────┐
│  3. Application Code                                    │
│     @RestController                                     │
│     fun getUser() {                                     │
│         logger.info(&quot;...&quot;)  &amp;larr; MDC의 trace ID 자동 포함  │
│         // 비즈니스 로직 실행                            │
│     }                                                   │
└────────────────┬────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────┐
│  4. 외부 서비스 호출 (RestTemplate/WebClient)            │
│     GET https://api.external.com/users/12345            │
│     traceparent: 00-690dae8f...-1122334455...-01       │
│                  └─ 동일한 Trace ID 자동 전파! ✅        │
└────────────────┬────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────┐
│  5. APM Backend                                         │
│     - 모든 서비스의 로그를 Trace ID로 그룹화            │
│     - 분산 환경 전체 플로우 시각화                       │
└─────────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;W3C traceparent 헤더 형식:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;traceparent: 00-690dae8f00000000de283d7148306199-fedcba9876543210-01
             │   └─ 32자리 hex (128-bit)        └─ 16자리 hex (64-bit)
             │      Trace ID                       Span ID
             └─ version&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3️⃣ 구현체 선택의 자유&lt;/b&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구현체&lt;/th&gt;
&lt;th&gt;선택 기준&lt;/th&gt;
&lt;th&gt;APM 백엔드 예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Brave&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Spring Boot 중심 프로젝트&lt;/td&gt;
&lt;td&gt;Zipkin, Jaeger, Elastic APM, Tempo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;OpenTelemetry&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;다중 언어/플랫폼 환경&lt;/td&gt;
&lt;td&gt;Jaeger, Tempo, Datadog, Elastic APM, New Relic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;b&gt;핵심&lt;/b&gt;: 어떤 구현체를 선택하든 모든 주요 APM 백엔드와 호환됩니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.4. Micrometer Tracing 핵심 이해&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Micrometer Tracing이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Micrometer Tracing은 &lt;b&gt;분산 추적(Distributed Tracing)&lt;/b&gt;을 위한 Spring Boot 공식 라이브러리입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Spring Cloud Sleuth &amp;rarr; Micrometer Tracing 전환&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 Sleuth가 아니라 Micrometer Tracing인가?&lt;/b&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Spring Cloud Sleuth&lt;/th&gt;
&lt;th&gt;Micrometer Tracing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;지원 상태&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;⚠️ 유지보수 모드 (2021~)&lt;/td&gt;
&lt;td&gt;✅ 활발한 개발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Spring Boot 3.x&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;Micrometer 생태계 통합&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;&lt;b&gt;전환 배경&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Spring Boot 3.0 정책&lt;/b&gt;: Sleuth 공식 지원 중단, Micrometer Tracing 권장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;통합 관측성&lt;/b&gt;: 메트릭(Micrometer Metrics) + 추적(Micrometer Tracing) 단일 프로젝트로 통합&lt;/li&gt;
&lt;li&gt;&lt;b&gt;표준 준수&lt;/b&gt;: W3C TraceContext, OpenTelemetry 등 최신 표준 적극 지원&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장기 지원&lt;/b&gt;: Spring 팀의 직접 관리로 안정적인 유지보수 보장&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론&lt;/b&gt;: 새 프로젝트는 반드시 Micrometer Tracing 사용, 기존 Sleuth 프로젝트는 마이그레이션 권장&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 장점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 벤더 중립성 (Vendor Neutrality)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 Micrometer 에서 제공하는 인터페이스만 사용!&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// ✅ 벤더 중립적 코드 - 어떤 APM 도구든 동작
@RestController
class UserController(
    private val tracer: Tracer,  // Micrometer 인터페이스
) {
    @GetMapping(&quot;/api/users/{userId}&quot;)
    fun getUser(@PathVariable userId: Long): UserResponse {
        val currentSpan = tracer.currentSpan()  // Micrometer API
        val traceId = currentSpan.context().traceId()

        logger.info(&quot;Fetching user: $userId, traceId: $traceId&quot;)
        // ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 자동 계측 (Auto Instrumentation)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring MVC, WebFlux, Kafka 등 자동 지원&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
class OrderController {
    @GetMapping(&quot;/api/orders/{orderId}&quot;)
    fun getOrder(@PathVariable orderId: Long): Order {
        // ✅ 별도 코드 없이도 자동으로 Span 생성!
        return orderService.findById(orderId)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. MDC 통합&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLF4J MDC와 자동 통합&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;logger.info(&quot;Processing order&quot;)
// 출력: [traceId=690dae8f... spanId=0123456789...] Processing order
// ✅ MDC에 자동으로 trace ID 설정됨!&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Trace ID 전파 (Propagation)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Micrometer는 &lt;b&gt;W3C TraceContext 표준&lt;/b&gt;을 지원하여 서비스 간 trace ID를 자동으로 전파합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP Client 호출 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Service
class ExternalApiService(
    private val restTemplate: RestTemplate,
) {
    private val logger = LoggerFactory.getLogger(javaClass)

    fun callExternalApi(userId: Long): ExternalUserResponse {
        logger.info(&quot;Calling external API for user: $userId&quot;)

        // ✅ RestTemplate이 자동으로 W3C traceparent 헤더 추가!
        val response = restTemplate.getForObject(
            &quot;https://api.external.com/users/$userId&quot;,
            ExternalUserResponse::class.java
        )

        logger.info(&quot;External API call completed&quot;)
        return response
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 HTTP 요청&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;GET /users/12345 HTTP/1.1
Host: api.external.com
traceparent: 00-690dae8f00000000de283d7148306199-fedcba9876543210-01
           └─ Micrometer가 자동으로 추가한 trace ID!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;W3C traceparent 헤더 형식&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;traceparent: 00-&amp;lt;trace-id&amp;gt;-&amp;lt;parent-id&amp;gt;-&amp;lt;trace-flags&amp;gt;
             │   │         │          │
             │   │         │          └─ 01 (sampled)
             │   │         └─ 16-digit hex span ID
             │   └─ 32-digit hex trace ID (128-bit)
             └─ version&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.5. Brave vs OpenTelemetry: 구현체 선택&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Micrometer Tracing은 &lt;b&gt;2가지 구현체를 지원&lt;/b&gt;하며, 하나를 선택해야 합니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;Brave&lt;/th&gt;
&lt;th&gt;OpenTelemetry&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;의존성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;micrometer-tracing-bridge-brave&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;micrometer-tracing-bridge-otel&lt;/code&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;Zipkin (기본)&lt;/td&gt;
&lt;td&gt;OTLP Collector, Jaeger 등&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;Zipkin 중심&lt;/td&gt;
&lt;td&gt;OpenTelemetry 표준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;추천&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Spring Boot 프로젝트&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;&lt;b&gt;이 가이드의 선택: Brave&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;✅ Spring 팀이 추천하는 &lt;b&gt;성숙하고 안정적인&lt;/b&gt; 솔루션&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;경량&lt;/b&gt;이며 성능 오버헤드가 적음&lt;/li&gt;
&lt;li&gt;✅ Zipkin과의 &lt;b&gt;완벽한 통합&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;✅ Spring Boot 생태계에서 &lt;b&gt;검증된 track record&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring 공식 블로그 인용:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Spring believes Micrometer and &lt;b&gt;Brave&lt;/b&gt; are essential tools in the Spring Boot observability toolkit as &lt;b&gt;mature, production tested solutions&lt;/b&gt;.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OpenTelemetry를 선택해야 하는 경우:&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;다중 언어 환경 (Java, Python, Go 등)&lt;/li&gt;
&lt;li&gt;OpenTelemetry 표준 준수가 필수인 조직&lt;/li&gt;
&lt;li&gt;Jaeger를 APM 백엔드로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중요&lt;/b&gt;: 두 구현체 모두 &lt;b&gt;동일한 Micrometer API&lt;/b&gt;를 사용하므로, 나중에 교체 가능합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 주요 환경별 Trace ID 활용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 섹션에서는 Spring MVC, Kafka Consumer, Spring Batch 환경에서 Micrometer Tracing을 활용하는 방법을 설명합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1. Spring MVC (HTTP 요청)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;자동 계측&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring MVC에서는 &lt;b&gt;아무것도 하지 않아도&lt;/b&gt; Micrometer가 자동으로 trace를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;흐름&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. HTTP Request 진입
   │
   ▼
2. Micrometer Auto-Configuration
   ├─ W3C traceparent 헤더 읽기 (있는 경우)
   ├─ 또는 새 Trace ID 생성
   ├─ Root Span 생성
   └─ MDC에 traceId/spanId 자동 설정
   │
   ▼
3. Spring MVC Controller
   ├─ @RestController 메서드 실행
   └─ 모든 로그에 trace ID 자동 포함
   │
   ▼
4. Response 반환
   ├─ Span 종료
   └─ APM 백엔드로 전송 (설정된 경우)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실제 코드 예시&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/api/users&quot;)
class UserController(
    private val userService: UserService,
) {
    private val logger = LoggerFactory.getLogger(javaClass)

    @GetMapping(&quot;/{userId}&quot;)
    fun getUser(@PathVariable userId: Long): UserResponse {
        // ✅ 이 로그에는 trace ID가 자동으로 포함됨!
        logger.info(&quot;Fetching user: $userId&quot;)

        val user = userService.findById(userId)

        return UserResponse.from(user)
    }
}

@Service
class UserService(
    private val userRepository: UserRepository,
) {
    private val logger = LoggerFactory.getLogger(javaClass)

    fun findById(userId: Long): User {
        // ✅ Service 내부 로그도 Controller와 동일한 trace ID!
        logger.info(&quot;Querying database for user: $userId&quot;)

        return userRepository.findById(userId)
            .orElseThrow { UserNotFoundException(userId) }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2. Kafka Consumer&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;자동 계측&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka Consumer도 &lt;b&gt;자동으로 trace가 생성&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;흐름&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;1. Kafka Message 수신
   │
   ▼
2. Micrometer Kafka Instrumentation
   ├─ Kafka 헤더에서 trace 정보 읽기 (Producer가 전파한 경우)
   ├─ 또는 새 Trace ID 생성
   ├─ Span 생성
   └─ MDC에 traceId/spanId 자동 설정
   │
   ▼
3. @KafkaListener 메서드 실행
   └─ 모든 로그에 trace ID 자동 포함
   │
   ▼
4. Message 처리 완료
   ├─ Span 종료
   └─ APM 백엔드로 전송 (설정된 경우)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실제 코드 예시&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component
class ProductEventConsumer(
    private val productService: ProductService,
    private val eventPublisher: ApplicationEventPublisher,
) {
    private val logger = LoggerFactory.getLogger(javaClass)

    @Transactional
    @KafkaListener(
        topics = [&quot;\${spring.kafka.topics.product-events}&quot;],
        groupId = &quot;\${spring.kafka.group.product-consumer}&quot;,
    )
    fun consumeProductEvent(@Payload eventJson: String) {
        val event = parseEvent(eventJson)

        // ✅ 메시지마다 고유한 trace ID 자동 생성됨!
        logger.info(
            &quot;Processing product event - productId: ${event.productId}, eventType: ${event.eventType}&quot;
        )

        when (event.eventType) {
            CREATE, UPDATE -&amp;gt; {
                productService.upsertProduct(
                    id = event.productId,
                    name = event.productName,
                    isActive = event.isActive,
                )
                eventPublisher.publishEvent(ProductChangedEvent(event.productId))
            }
            else -&amp;gt; logger.debug(&quot;Ignoring event type: ${event.eventType}&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&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;Kafka 메시지마다&lt;/b&gt; 새로운 trace ID 생성&lt;/li&gt;
&lt;li&gt;동일 메시지 처리 중 발생하는 모든 로그는 &lt;b&gt;동일 trace ID&lt;/b&gt; 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코드 수정 불필요&lt;/b&gt; - Micrometer가 자동 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3. Spring Batch (수동 계측)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 상황&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Batch는 &lt;b&gt;자동 계측이 되지 않습니다&lt;/b&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;Batch Job은 &lt;b&gt;HTTP 진입점이 아님&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Kafka 메시지도 아님&lt;/li&gt;
&lt;li&gt;Scheduler 또는 수동 실행 &amp;rarr; Micrometer가 자동 감지 못함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결: JobTracingListener 구현&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 아이디어&lt;/b&gt;: Job 시작 시 &lt;b&gt;수동으로 Span을 생성&lt;/b&gt;하고 MDC에 trace ID 설정&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구현 코드&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component
class JobTracingListener(
    private val tracer: Tracer,  // ✅ Micrometer Tracer
) : JobExecutionListener {
    private val logger = LoggerFactory.getLogger(javaClass)
    private var currentSpan: Span? = null

    override fun beforeJob(jobExecution: JobExecution) {
        // 1️⃣ Micrometer로 새로운 Span 생성 (trace 시작)
        val span = tracer.nextSpan()
            .name(&quot;batch.job.${jobExecution.jobInstance.jobName}&quot;)
            .tag(&quot;job.name&quot;, jobExecution.jobInstance.jobName)
            .tag(&quot;job.execution.id&quot;, jobExecution.id.toString())
            .tag(&quot;service.name&quot;, &quot;batch-service&quot;)
            .start()  // &amp;larr; 여기서 실제로 trace ID 생성!

        // 2️⃣ Trace ID와 Span ID 추출
        val traceId = span.context().traceId()
        val spanId = span.context().spanId()

        // 3️⃣ MDC에 수동으로 설정
        // ✅ Application Log에 trace ID 포함하기 위해 MDC 설정
        MDC.put(&quot;traceId&quot;, traceId)
        MDC.put(&quot;spanId&quot;, spanId)
        MDC.put(&quot;job.name&quot;, jobExecution.jobInstance.jobName)

        // 4️⃣ JobExecutionContext에 저장 (Step 간 공유용)
        jobExecution.executionContext.put(&quot;traceId&quot;, traceId)
        jobExecution.executionContext.put(&quot;spanId&quot;, spanId)

        this.currentSpan = span

        logger.info(
            &quot;Started batch job trace - job: {}, traceId: {}, spanId: {}&quot;,
            jobExecution.jobInstance.jobName,
            traceId,
            spanId,
        )
    }

    override fun afterJob(jobExecution: JobExecution) {
        // 5️⃣ Job 종료 시 태그 추가 및 Span 종료
        currentSpan?.tag(&quot;job.status&quot;, jobExecution.status.toString())
        currentSpan?.tag(&quot;exit.code&quot;, jobExecution.exitStatus.exitCode)
        currentSpan?.end()  // &amp;larr; APM 백엔드에 전송

        logger.info(
            &quot;Completed batch job trace - job: {}, status: {}&quot;,
            jobExecution.jobInstance.jobName,
            jobExecution.status,
        )

        // 6️⃣ MDC 정리
        MDC.remove(&quot;traceId&quot;)
        MDC.remove(&quot;spanId&quot;)
        MDC.remove(&quot;job.name&quot;)

        currentSpan = null
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step Listener도 추가&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class StepExecutionLoggingListener : StepExecutionListener {
    private val logger = LoggerFactory.getLogger(javaClass)

    override fun beforeStep(stepExecution: StepExecution): Unit? {
        val traceId = MDC.get(&quot;traceId&quot;) ?: &quot;N/A&quot;
        val spanId = MDC.get(&quot;spanId&quot;) ?: &quot;N/A&quot;

        logger.info(
            &quot;Step starting - name: {}, jobExecutionId: {}, traceId: {}, spanId: {}&quot;,
            stepExecution.stepName,
            stepExecution.jobExecutionId,
            traceId,
            spanId,
        )

        return null
    }

    override fun afterStep(stepExecution: StepExecution): Unit? {
        logger.info(
            &quot;Step completed - name: {}, readCount: {}, writeCount: {}, status: {}&quot;,
            stepExecution.stepName,
            stepExecution.readCount,
            stepExecution.writeCount,
            stepExecution.status,
        )

        return null
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Job 설정에 Listener 등록&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration
class DailySettlementJobConfig(
    private val jobTracingListener: JobTracingListener,  // &amp;larr; 주입
) {

    @Bean
    fun dailySettlementJob(
        jobRepository: JobRepository,
        settlementStep: Step,
        notificationStep: Step,
    ): Job {
        return JobBuilder(&quot;dailySettlementJob&quot;, jobRepository)
            .listener(jobTracingListener)  // ✅ 등록
            .start(settlementStep)
            .next(notificationStep)
            .build()
    }

    @Bean
    fun settlementStep(
        jobRepository: JobRepository,
        transactionManager: PlatformTransactionManager,
        settlementTasklet: SettlementTasklet,
    ): Step {
        return StepBuilder(&quot;settlementStep&quot;, jobRepository)
            .tasklet(settlementTasklet, transactionManager)
            .listener(StepExecutionLoggingListener())  // ✅ Step Listener 등록
            .build()
    }
}&lt;/code&gt;&lt;/pre&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;✅ Job 전체가 &lt;b&gt;하나의 trace ID&lt;/b&gt;로 추적됨&lt;/li&gt;
&lt;li&gt;✅ Step별로 &lt;b&gt;다른 span ID&lt;/b&gt; 사용 (세부 작업 추적)&lt;/li&gt;
&lt;li&gt;✅ MDC Key는 &lt;b&gt;traceId/spanId&lt;/b&gt; 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 로그 통합 및 실전 활용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Micrometer Tracing이 생성한 trace ID를 로그에 통합하여 강력한 디버깅 도구로 활용하는 방법을 안내합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1. Logback 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot는 Micrometer Tracing이 활성화되면 자동으로 MDC에 &lt;code&gt;traceId&lt;/code&gt;와 &lt;code&gt;spanId&lt;/code&gt;를 설정합니다.&lt;br /&gt;이를 로그에 포함하려면 Logback 설정을 조정해야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본 Console 로그 패턴&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;application.yml&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;logging:
  pattern:
    console: &quot;%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{traceId},%X{spanId}] %logger{36} - %msg%n&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그 출력 예시&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;2025-11-21 10:30:45 [http-nio-8080-exec-1] INFO  [690dae8f00000000de283d7148306199,0123456789abcdef] c.e.a.UserController - Fetching user: 12345&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JSON 로그 형식 (운영 환경 권장)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;logback-spring.xml&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;springProfile name=&quot;dev | prd&quot;&amp;gt;
    &amp;lt;appender name=&quot;JSON_CONSOLE&quot; class=&quot;ch.qos.logback.core.ConsoleAppender&quot;&amp;gt;
        &amp;lt;encoder class=&quot;net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder&quot;&amp;gt;
            &amp;lt;providers&amp;gt;
                &amp;lt;!-- MDC (✅ Micrometer가 자동 설정한 trace ID) --&amp;gt;
                &amp;lt;mdc&amp;gt;
                    &amp;lt;includeMdcKeyName&amp;gt;traceId&amp;lt;/includeMdcKeyName&amp;gt;
                    &amp;lt;includeMdcKeyName&amp;gt;spanId&amp;lt;/includeMdcKeyName&amp;gt;
                &amp;lt;/mdc&amp;gt;

                &amp;lt;!-- 메시지에 trace ID 포함 --&amp;gt;
                &amp;lt;pattern&amp;gt;
                    &amp;lt;pattern&amp;gt;
                        {
                        &quot;message&quot;: &quot;[traceId=%mdc{traceId:-none} spanId=%mdc{spanId:-none}] %message&quot;
                        }
                    &amp;lt;/pattern&amp;gt;
                &amp;lt;/pattern&amp;gt;

                &amp;lt;!-- 로그 레벨, 타임스탬프, 로거 등 --&amp;gt;
                &amp;lt;timestamp&amp;gt;
                    &amp;lt;timeZone&amp;gt;Asia/Seoul&amp;lt;/timeZone&amp;gt;
                    &amp;lt;fieldName&amp;gt;timestamp&amp;lt;/fieldName&amp;gt;
                &amp;lt;/timestamp&amp;gt;

                &amp;lt;logLevel&amp;gt;
                    &amp;lt;fieldName&amp;gt;level&amp;lt;/fieldName&amp;gt;
                &amp;lt;/logLevel&amp;gt;

                &amp;lt;loggerName&amp;gt;
                    &amp;lt;fieldName&amp;gt;logger&amp;lt;/fieldName&amp;gt;
                &amp;lt;/loggerName&amp;gt;
            &amp;lt;/providers&amp;gt;
        &amp;lt;/encoder&amp;gt;
    &amp;lt;/appender&amp;gt;

    &amp;lt;root level=&quot;INFO&quot;&amp;gt;
        &amp;lt;appender-ref ref=&quot;JSON_CONSOLE&quot;/&amp;gt;
    &amp;lt;/root&amp;gt;
&amp;lt;/springProfile&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JSON 로그 출력 예시&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;timestamp&quot;: &quot;2025-11-21 10:30:45.123&quot;,
  &quot;traceId&quot;: &quot;690dae8f00000000de283d7148306199&quot;,
  &quot;spanId&quot;: &quot;0123456789abcdef&quot;,
  &quot;level&quot;: &quot;INFO&quot;,
  &quot;logger&quot;: &quot;com.example.app.api.UserController&quot;,
  &quot;message&quot;: &quot;[traceId=690dae8f00000000de283d7148306199 spanId=0123456789abcdef] Fetching user: 12345&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2. 로그 기반 트러블슈팅 워크플로우&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;시나리오: 에러 알림 수신&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. Slack/이메일로 에러 알림 수신
   &quot;API 에러 발생: NullPointerException&quot;
   │
   ▼
2. 로그 시스템 (OpenSearch/Kibana/ELK) 접속
   │
   ▼
3. Trace ID로 검색
   검색어: &quot;690dae8f00000000de283d7148306199&quot;
   │
   ▼
4. 해당 요청의 전체 로그 확인
   ├─ HTTP 요청 파라미터
   ├─ 실행된 비즈니스 로직
   ├─ DB 쿼리 및 결과
   ├─ 외부 API 호출
   └─ 에러 스택 트레이스
   │
   ▼
5. 원인 파악 및 수정&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 가치&lt;/h4&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;: Trace ID 하나로 전체 요청 흐름 추적&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;정확한 로그 검색&lt;/b&gt;: 관련 없는 로그에 방해받지 않음&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;동시 요청 구분&lt;/b&gt;: 여러 요청이 동시 처리되어도 각각 추적 가능&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;마이크로서비스 추적&lt;/b&gt;: 서비스 경계를 넘어 전체 플로우 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 실무 활용 꿀팁&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.1. API 에러 응답에 Trace ID 포함&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TraceIdProvider 구현&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Micrometer에서 trace ID를 가져오는 Provider 패턴:&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun interface TraceIdProvider {
    fun provide(): String
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구현체&lt;/b&gt; (Micrometer 기반)&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component
class MicrometerTraceIdProvider(
    private val tracer: Tracer,  // ✅ Micrometer Tracer
) : TraceIdProvider {
    override fun provide(): String {
        return tracer.currentSpan()?.let { span -&amp;gt;
            span.context().traceId()  // Micrometer API
        } ?: run {
            // Batch: MDC fallback (JobTracingListener가 설정)
            // ✅ 운영 환경 기준 traceId 사용
            MDC.get(&quot;traceId&quot;) ?: &quot;&quot;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에러 응답 DTO&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;data class ApiErrorResponse(
    val httpStatus: HttpStatus,
    val errorCode: String?,
    val message: String,
    val timestamp: ZonedDateTime = ZonedDateTime.now(),
    val traceId: String,  // ✅ trace ID 포함!
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에러 핸들러&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestControllerAdvice
class GlobalExceptionHandler(
    private val traceIdProvider: TraceIdProvider,  // ✅ 주입
) {
    private val logger = LoggerFactory.getLogger(javaClass)

    @ExceptionHandler(UserNotFoundException::class)
    fun handleUserNotFound(e: UserNotFoundException): ResponseEntity&amp;lt;ApiErrorResponse&amp;gt; {
        val traceId = traceIdProvider.provide()
        logger.error(&quot;User not found: userId=${e.userId}, traceId=$traceId&quot;, e)

        return ResponseEntity
            .status(HttpStatus.NOT_FOUND)
            .body(
                ApiErrorResponse(
                    httpStatus = HttpStatus.NOT_FOUND,
                    errorCode = &quot;error.user.not-found&quot;,
                    message = &quot;사용자를 찾을 수 없습니다.&quot;,
                    traceId = traceId,
                )
            )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에러 응답 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;httpStatus&quot;: &quot;NOT_FOUND&quot;,
  &quot;errorCode&quot;: &quot;error.user.not-found&quot;,
  &quot;message&quot;: &quot;사용자를 찾을 수 없습니다.&quot;,
  &quot;timestamp&quot;: &quot;2025-11-19T10:30:45.123+09:00&quot;,
  &quot;traceId&quot;: &quot;690dae8f00000000de283d7148306199&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2. Slack 알림에 OpenSearch 바로 랜딩&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Service
class SlackNotificationService(
    private val traceIdProvider: TraceIdProvider,  // ✅ Micrometer 기반
    private val slackWebHookClient: SlackWebHookClient,
    @Value(&quot;\${logging.opensearch-url}&quot;)
    val logUrl: String,
) {
    private val logger = LoggerFactory.getLogger(javaClass)

    fun sendErrorNotification(
        title: String,
        message: String,
    ) {
        val traceId = traceIdProvider.provide()  // ✅ Micrometer에서 trace ID
        val logSearchUrl = buildLogUrl(traceId)

        logger.warn(&quot;Sending error notification: title=$title, traceId=$traceId&quot;)

        slackWebHookClient.sendWebHook(
            text = &quot;*[$title]* (&amp;lt;$logSearchUrl|log - $traceId&amp;gt;)\n$message&quot;,
        )
    }

    private fun buildLogUrl(traceId: String): String =
        if (traceId.isNotEmpty()) {
            logUrl.replace(&quot;{keyword}&quot;, &quot;\&quot;$traceId\&quot;&quot;)
        } else {
            logUrl.replace(&quot;{keyword}&quot;, &quot;*&quot;)
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Slack 메시지 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;api-service (prd)
[결제 처리 실패] (log - 690dae8f00000000de283d7148306199)
Order ID: 123456
Error: Payment gateway timeout&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;log - 690dae8f...&quot; 클릭 시&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;OpenSearch가 자동으로 열림&lt;/li&gt;
&lt;li&gt;검색어: &lt;code&gt;&quot;690dae8f00000000de283d7148306199&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;해당 요청의 모든 로그 자동 필터링!&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 최종 구현 및 트러블슈팅&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 전체 아키텍처&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────┐
│                     클라이언트 요청                           │
│              (iOS, Android, Web, Postman)                     │
└────────────────────────┬─────────────────────────────────────┘
                         │
                         ▼
┌──────────────────────────────────────────────────────────────┐
│  Micrometer Tracing Auto-Configuration                       │
│  - Brave Tracer (Zipkin 기반)                                │
│  - W3C TraceContext 표준 지원                                │
│  - MDC 자동 설정 (traceId/spanId)                   │
└────────────────────────┬─────────────────────────────────────┘
                         │
         ┌───────────────┼───────────────┬──────────────┐
         │               │               │              │
         ▼               ▼               ▼              ▼
    ┌────────┐     ┌────────┐     ┌─────────┐    ┌──────────┐
    │ API    │     │Consumer│     │ Batch   │    │ Logger   │
    │ Error  │     │ Event  │     │ Job     │    │ (MDC)    │
    │Response│     │        │     │ Listener│    │          │
    └────┬───┘     └────┬───┘     └────┬────┘    └────┬─────┘
         │              │              │              │
         └──────────────┴──────────────┴──────────────┘
                         │
                         ▼
         ┌───────────────────────────────────┐
         │   TraceIdProvider                 │
         │   (Micrometer 기반)               │
         │   - provide(): String             │
         └───────────────┬───────────────────┘
                         │
         ┌───────────────┴───────────────┐
         │                               │
         ▼                               ▼
    ┌─────────┐                     ┌────────┐
    │ Slack   │                     │OpenSearch│
    │ Alert   │                     │ Logs   │
    └─────────┘                     └────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  핵심 컴포넌트&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Micrometer Tracing&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의존성&lt;/b&gt; (build.gradle.kts)&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;dependencies {
    // Spring Boot Actuator (Micrometer 포함)
    implementation(&quot;org.springframework.boot:spring-boot-starter-actuator&quot;)

    // Micrometer Tracing
    api(&quot;io.micrometer:micrometer-tracing-bridge-brave&quot;)

    // Zipkin Reporter (Brave)
    implementation(&quot;io.zipkin.reporter2:zipkin-reporter-brave&quot;)

    // JSON 로그
    implementation(&quot;net.logstash.logback:logstash-logback-encoder:7.4&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설정&lt;/b&gt; (application.yml)&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;management:
  tracing:
    sampling:
      probability: 1.0  # 100% 샘플링

logging:
  opensearch-url: &quot;https://opensearch.example.com/app/discover#/?_g=(filters:!(),query:(language:kuery,query:'{keyword}'))&amp;amp;_a=(columns:!(message,level),filters:!(),index:'logs-*')&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. TraceIdProvider&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: Micrometer에서 trace ID 추출&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component
class MicrometerTraceIdProvider(
    private val tracer: Tracer,  // Micrometer Tracer
) : TraceIdProvider {
    override fun provide(): String {
        return tracer.currentSpan()?.context()?.traceId()
            ?: MDC.get(&quot;traceId&quot;) ?: &quot;&quot;  // ✅ traceId 사용
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. JobTracingListener (Batch)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: Micrometer로 Batch Job trace 생성&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component
class JobTracingListener(
    private val tracer: Tracer,  // Micrometer Tracer
) : JobExecutionListener {
    private val logger = LoggerFactory.getLogger(javaClass)

    override fun beforeJob(jobExecution: JobExecution) {
        val span = tracer.nextSpan().start()  // Micrometer API
        val traceId = span.context().traceId()

        // ✅ traceId 사용 (운영 환경 통일)
        MDC.put(&quot;traceId&quot;, traceId)
        MDC.put(&quot;spanId&quot;, span.context().spanId())
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트러블슈팅&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 1: Trace ID가 로그에 없음&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;2025-11-19 10:30:45.123  INFO 12345 --- [nio-8080-exec-1] com.example.app.api.UserController : Fetching user&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;로컬 환경에서 실행 (정상 - Micrometer만 동작)&lt;/li&gt;
&lt;li&gt;logback-spring.xml에서 MDC Key 설정 누락&lt;/li&gt;
&lt;/ol&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;Micrometer 기본 키(traceId/spanId) 또는 설정 안 됨&lt;/li&gt;
&lt;li&gt;필요 시 logback에서 &lt;code&gt;traceId&lt;/code&gt;/&lt;code&gt;spanId&lt;/code&gt; 포함
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;mdc&amp;gt;
  &amp;lt;includeMdcKeyName&amp;gt;traceId&amp;lt;/includeMdcKeyName&amp;gt;
  &amp;lt;includeMdcKeyName&amp;gt;spanId&amp;lt;/includeMdcKeyName&amp;gt;
&amp;lt;/mdc&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 2: Batch Job에서 Trace ID가 없음&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;timestamp&quot;: &quot;2025-11-19 02:00:00.123&quot;,
  &quot;level&quot;: &quot;INFO&quot;,
  &quot;message&quot;: &quot;[traceId=none spanId=none] Starting settlement job&quot;
}&lt;/code&gt;&lt;/pre&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;code&gt;JobTracingListener&lt;/code&gt;가 Job에 등록되지 않음&lt;/li&gt;
&lt;/ul&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;1) JobTracingListener 빈 등록 확인&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Component
class JobTracingListener(
    private val tracer: Tracer,
) : JobExecutionListener {
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) Job에 Listener 등록 확인&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Bean
fun dailySettlementJob(
    jobRepository: JobRepository,
    jobTracingListener: JobTracingListener,  // &amp;larr; 주입
): Job {
    return JobBuilder(&quot;dailySettlementJob&quot;, jobRepository)
        .listener(jobTracingListener)  // &amp;larr; 등록
        .start(settlementStep)
        .build()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) MDC Key 확인&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// ✅ 운영 환경 기준
MDC.put(&quot;traceId&quot;, traceId)
MDC.put(&quot;spanId&quot;, spanId)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 3: APM에 Trace가 안 보임&lt;/h4&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;로그에는 trace ID 있음&lt;/li&gt;
&lt;li&gt;Zipkin UI에는 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;APM agent 없음 (로컬 환경)&lt;/li&gt;
&lt;li&gt;Sampling 설정 0%&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;management:
  tracing:
    sampling:
      probability: 1.0  # 100%&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Micrometer&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://micrometer.io/docs/tracing&quot;&gt;Micrometer Tracing 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.micrometer-tracing&quot;&gt;Spring Boot Actuator - Tracing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openzipkin/brave&quot;&gt;Zipkin Brave GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;W3C TraceContext&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/trace-context/&quot;&gt;W3C Trace Context Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Zipkin&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://zipkin.io/&quot;&gt;Zipkin 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zipkin.io/pages/quickstart&quot;&gt;Zipkin Quick Start&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Programming/SpringBoot</category>
      <category>datadog</category>
      <category>datadog apm</category>
      <category>Micrometer</category>
      <category>spanId</category>
      <category>spring boot</category>
      <category>traceid</category>
      <category>tracing</category>
      <author>iia_hannah</author>
      <guid isPermaLink="true">https://prohannah.tistory.com/276</guid>
      <comments>https://prohannah.tistory.com/276#entry276comment</comments>
      <pubDate>Wed, 19 Nov 2025 21:01:52 +0900</pubDate>
    </item>
    <item>
      <title>@Transactional만으로 DB 읽기/쓰기 분산하기 (Writer/Reader Datasource 자동 라우팅)</title>
      <link>https://prohannah.tistory.com/274</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&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;Writer/Reader Multi-DataSource 라우팅 아키텍처&lt;/b&gt;를 통해 이러한 문제를 해결한 경험을 공유합니다. Spring Boot 환경에서 &lt;code&gt;@Transactional(readOnly=true)&lt;/code&gt; 어노테이션만으로 자동으로 읽기/쓰기 데이터베이스를 분산하는 방법을 소개합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 시스템은 단일 Writer DataSource만 사용하여 모든 조회/변경 쿼리를 처리했습니다&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// 기존 구조: 모든 쿼리가 Writer DB로
@Transactional(readOnly = true)
fun findProducts(): List&amp;lt;Product&amp;gt; {
    return productRepository.findAll()  // Writer DB 사용
}

@Transactional
fun createProduct(dto: CreateDto): Product {
    return productRepository.save(entity)  // Writer DB 사용
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;발생한 문제들&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;읽기 부하 집중&lt;/b&gt;: 모든 조회 쿼리가 Writer DB에 집중되어 쓰기 성능 저하 가능성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성 제약&lt;/b&gt;: DB 부하 분산을 위한 Read Replica 활용 불가&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리소스 비효율&lt;/b&gt;: 읽기 전용 쿼리도 Writer 커넥션 풀을 소비하여 쓰기 트랜잭션 대기 발생 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 방법: Multi-DataSource 라우팅 아키텍처&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아키텍처 개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring의 &lt;code&gt;AbstractRoutingDataSource&lt;/code&gt;와 &lt;code&gt;LazyConnectionDataSourceProxy&lt;/code&gt;를 활용하여 트랜잭션 메타데이터 기반 자동 라우팅을 구현했습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;839&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l5yya/dJMcacuKgSK/PKEdSLmeAzbceWqiHFcZA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l5yya/dJMcacuKgSK/PKEdSLmeAzbceWqiHFcZA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l5yya/dJMcacuKgSK/PKEdSLmeAzbceWqiHFcZA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl5yya%2FdJMcacuKgSK%2FPKEdSLmeAzbceWqiHFcZA1%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;839&quot; height=&quot;658&quot; data-origin-width=&quot;839&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 구현 코드&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. DataSource 설정&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration
class DataSourceConfig {
    @Bean
    @Primary
    fun dataSource(): DataSource =
        LazyConnectionDataSourceProxy(routingDataSource())

    @Bean
    fun routingDataSource(): RoutingDataSource =
        RoutingDataSource(
            writerDataSource = Pair(&quot;WRITER&quot;, writerDataSource()),
            readerDataSource = Pair(&quot;READER&quot;, readerDataSource()),
        )

    @Bean
    @ConfigurationProperties(&quot;spring.datasource.hikari&quot;)
    fun writerDataSource(): HikariDataSource = HikariDataSource()

    @Bean
    @ConfigurationProperties(&quot;spring.datasource.hikari.slaves.first&quot;)
    fun readerDataSource(): HikariDataSource =
        HikariDataSource().apply {
            isReadOnly = true  // PostgreSQL 최적화 활성화
        }
}&lt;/code&gt;&lt;/pre&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;code&gt;LazyConnectionDataSourceProxy&lt;/code&gt;로 Primary DataSource를 래핑하여 트랜잭션 컨텍스트가 완전히 설정된 후 Connection을 획득&lt;/li&gt;
&lt;li&gt;Reader DataSource에 &lt;code&gt;isReadOnly=true&lt;/code&gt; 설정으로 PostgreSQL JDBC 드라이버 최적화 활성화&lt;/li&gt;
&lt;li&gt;잘못된 라우팅으로 Reader에서 쓰기 쿼리 실행 시 DB 레벨에서 즉시 거부 (안전장치)&lt;/li&gt;
&lt;li&gt;이 글에서는 Reader Replica가 1개라고 가정합니다. AWS Aurora DB를 사용하는 경우 Reader Endpoint로 자동으로 여러 Reader 인스턴스에 로드 밸런싱을 제공하므로, 어플리케이션에서는 하나의 Reader DataSource만 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 라우팅 로직&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class RoutingDataSource(
    writerDataSource: Pair&amp;lt;String, DataSource&amp;gt;,
    readerDataSource: Pair&amp;lt;String, DataSource&amp;gt;,
) : AbstractRoutingDataSource() {

    private val writerDataSourceId = writerDataSource.first
    private val readerDataSourceId = readerDataSource.first

    init {
        val writer = writerDataSource.second
        super.setDefaultTargetDataSource(writer)

        val targetDataSources: MutableMap&amp;lt;Any, Any&amp;gt; = mutableMapOf(
            writerDataSourceId to writer,
            readerDataSourceId to readerDataSource.second,
        )
        super.setTargetDataSources(targetDataSources)
    }

    override fun determineCurrentLookupKey(): String {
        val isReadOnlyTransaction =
            TransactionSynchronizationManager.isCurrentTransactionReadOnly()

        val selectedDataSource = if (isReadOnlyTransaction) {
            readerDataSourceId
        } else {
            writerDataSourceId
        }

        logger.debug(&quot;DataSource routing: readOnly=$isReadOnlyTransaction &amp;rarr; $selectedDataSource&quot;)

        return selectedDataSource
    }

    // 런타임 수정 방지 (안정성 보장)
    override fun setDefaultTargetDataSource(defaultTargetDataSource: Any) =
        throw UnsupportedOperationException()

    override fun setTargetDataSources(targetDataSources: MutableMap&amp;lt;Any, Any&amp;gt;) =
        throw UnsupportedOperationException()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 메커니즘&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;TransactionSynchronizationManager.isCurrentTransactionReadOnly()&lt;/code&gt;로 현재 트랜잭션이 읽기 전용인지 판단&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnly=true&lt;/code&gt;면 READER, 그렇지 않으면 WRITER DataSource 선택&lt;/li&gt;
&lt;li&gt;DEBUG 로그로 실시간 라우팅 확인 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. HikariCP 설정 표준화&lt;/h4&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;spring:
  datasource:
    hikari:
      jdbc-url: ${app.db.writer.jdbc-url}
      username: ${app.db.username}
      password: ${app.db.password}
      driver-class-name: org.postgresql.Driver

      # 핵심 설정값 및 근거
      connection-timeout: 30000      # 30초: DB 장애 시 빠른 실패 (Circuit Breaker 효과)
      idle-timeout: 600000           # 10분: 불필요한 커넥션 유지 방지
      max-lifetime: 1800000          # 30분: DB max connection lifetime과 동기화
      maximum-pool-size: ${app.db.writer.max-pool-size}
      minimum-idle: ${app.db.writer.max-pool-size}  # Fixed-Size Pool
      auto-commit: false             # Spring @Transactional이 트랜잭션 제어

      slaves:
        first:
          jdbc-url: ${app.db.reader.jdbc-url}
          # ... 동일한 설정 ...
          maximum-pool-size: ${app.db.reader.max-pool-size}

  jpa:
    properties:
      hibernate:
        connection.provider_disables_autocommit: true  # Hibernate autocommit 확인 쿼리 생략&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설정 값 선정 근거&lt;/b&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;설정&lt;/th&gt;
&lt;th&gt;값&lt;/th&gt;
&lt;th&gt;근거&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connection-timeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30초&lt;/td&gt;
&lt;td&gt;DB 장애 시 빠른 실패로 Circuit Breaker 효과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;minimum-idle = maximum-pool-size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed-Size Pool&lt;/td&gt;
&lt;td&gt;트래픽 급증 시 커넥션 생성 지연 없이 즉시 대응, 성능 예측 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;auto-commit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Spring &lt;code&gt;@Transactional&lt;/code&gt;이 트랜잭션 경계 제어 (true면 각 쿼리마다 즉시 커밋되어 원자성 깨짐)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;provider_disables_autocommit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;Hibernate가 autocommit 상태 확인 쿼리 생략하여 성능 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;계층적 설정 구조 (멀티모듈 패턴)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: Multi-DataSource 라우팅 자체는 모놀리식 애플리케이션에서도 동일하게 구현 가능합니다. 이 글에서는 멀티모듈 MSA 구조를 예시로 설명하지만, 모놀리식의 경우 단일 application.yml에 모든 설정을 통합하여 더 간단하게 구성할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모듈 아키텍처&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴은 MSA(Microservice Architecture) 환경 또는 멀티모듈 구조에서 특히 유용합니다&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;┌─────────────────────────────────────────────┐
│         Application Layer                   │
├──────────────────┬──────────────────────────┤
│ admin-api        │  public-api    │  batch  │ &amp;larr; 각 서비스별 독립 배포
│ (내부 관리용)     │  (외부 API)    │         │
├──────────────────┴──────────────────────────┤
│              Domain Layer                   │ &amp;larr; 공통 비즈니스 로직 + DataSource 설정
│        (JPA Entities, Services)            │
└─────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&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;Domain 모듈&lt;/b&gt;: DataSource 설정을 포함한 공통 인프라 제공&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Application 모듈&lt;/b&gt;: Domain을 의존하며, 각자의 트래픽 특성에 맞는 Pool Size 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설정 파일 계층&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Application 모듈은 공통 설정을 import하고 Pool Size만 재정의:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Domain 모듈 (공통 설정)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# domain/src/main/resources/config-db-common.yml
spring:
  datasource:
    hikari:
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      auto-commit: false
      # ... 공통 HikariCP 설정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Admin API 모듈 (내부 사용자용, 낮은 동시성)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# admin-api/src/main/resources/application.yml
spring:
  config:
    import:
      - classpath:config-db-common.yml  # 공통 설정 import

app:
  db:
    writer:
      max-pool-size: 10
    reader:
      max-pool-size: 15&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Public API 모듈 (외부 트래픽, 높은 동시성)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# public-api/src/main/resources/application.yml
spring:
  config:
    import:
      - classpath:config-db-common.yml

app:
  db:
    writer:
      max-pool-size: 20
    reader:
      max-pool-size: 40  # 조회 위주 트래픽&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Batch 모듈 (순차 처리, 최소 Pool)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# batch/src/main/resources/application.yml
spring:
  config:
    import:
      - classpath:config-db-common.yml

app:
  db:
    writer:
      max-pool-size: 5
    reader:
      max-pool-size: 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;변경 지점 최소화&lt;/b&gt;: HikariCP 타임아웃 변경 시 Domain 설정 파일 1곳만 수정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모듈별 특화&lt;/b&gt;: Pool Size는 각 모듈의 트래픽 특성에 맞게 독립 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설정 누락 방지&lt;/b&gt;: 공통 기본값이 자동 적용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DRY 원칙 준수&lt;/b&gt;: ~100줄의 중복 코드 제거&lt;/li&gt;
&lt;li&gt;&lt;b&gt;독립 배포&lt;/b&gt;: 각 Application 모듈은 독립적으로 배포 및 확장 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발자 입장에서의 사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변경 전&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// 모든 쿼리가 Writer DB로
@Service
class ProductService(
    private val productRepository: ProductRepository
) {
    fun findProducts(): List&amp;lt;Product&amp;gt; {
        return productRepository.findAll()  // Writer DB
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변경 후&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// @Transactional 어노테이션만 추가하면 자동 라우팅
@Service
class ProductService(
    private val productRepository: ProductRepository
) {
    @Transactional(readOnly = true)  // &amp;larr; READER DataSource 자동 선택
    fun findProducts(): List&amp;lt;Product&amp;gt; {
        return productRepository.findAll()  // Reader DB로 자동 라우팅
    }

    @Transactional  // &amp;larr; readOnly 미지정 시 기본값 false &amp;rarr; WRITER
    fun createProduct(dto: CreateDto): Product {
        return productRepository.save(entity)  // Writer DB
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심&lt;/b&gt; 개발자는 &lt;code&gt;@Transactional&lt;/code&gt; 어노테이션만 적절히 선택하면 되고, 뒷단에서 자동으로 라우팅됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주의사항&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. @Transactional 어노테이션 누락 시 라우팅 실패&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Transactional&lt;/code&gt; 어노테이션이 없으면 &lt;code&gt;isCurrentTransactionReadOnly()&lt;/code&gt;가 &lt;code&gt;false&lt;/code&gt;를 반환하여 무조건 WRITER로 라우팅됩니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// ❌ 안티패턴: @Transactional 누락
fun findProducts(): List&amp;lt;Product&amp;gt; {
    return productRepository.findAll()  // Writer DB 사용 (의도와 다름)
}

// ✅ 올바른 사용
@Transactional(readOnly = true)
fun findProducts(): List&amp;lt;Product&amp;gt; {
    return productRepository.findAll()  // Reader DB 사용
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대응 방안&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 Service 메서드에 &lt;code&gt;@Transactional&lt;/code&gt; 명시&lt;/li&gt;
&lt;li&gt;ArchUnit 테스트로 누락 검출 자동화&lt;/li&gt;
&lt;li&gt;DEBUG 로그 활성화로 라우팅 동작 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Reader DB Replication Lag&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reader DB가 Writer DB와 동기화되지 않은 경우 조회 데이터 최신성 문제 발생 가능:&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// 시나리오: 데이터 생성 직후 조회
@Transactional
fun create(dto: CreateDto): Product {
    return productRepository.save(entity)  // Writer DB에 저장
}

@Transactional(readOnly = true)
fun findById(id: Long): Product {
    // Replication Lag로 인해 방금 생성한 데이터가 보이지 않을 수 있음
    return productRepository.findById(id).orElseThrow()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// Critical Path에서는 Writer 직접 사용
@Transactional  // readOnly=false &amp;rarr; Writer 사용
fun createAndGet(dto: CreateDto): Product {
    val created = productRepository.save(entity)
    return productRepository.findById(created.id).orElseThrow()  // Writer에서 조회
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 전략&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;통합 테스트 (MultiDataSourceRoutingTest)&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@SpringBootTest
@DisplayName(&quot;Multi-DataSource 라우팅 테스트&quot;)
class MultiDataSourceRoutingTest : BaseRepositoryTest() {

    @Test
    @DisplayName(&quot;DataSource Bean이 LazyConnectionDataSourceProxy로 래핑되어 있어야 한다&quot;)
    fun `should wrap routing datasource with lazy proxy`() {
        assertThat(dataSource).isInstanceOf(LazyConnectionDataSourceProxy::class.java)
    }

    @Test
    @DisplayName(&quot;Reader DataSource는 readOnly로 설정되어 있어야 한다&quot;)
    fun `should configure reader datasource as readonly`() {
        assertThat(readerDataSource.isReadOnly).isTrue()
    }

    @Test
    @Transactional(readOnly = true)
    @DisplayName(&quot;읽기 전용 트랜잭션에서 JDBC로 테이블 조회가 가능해야 한다&quot;)
    fun `should execute jdbc query in readonly transaction`() {
        val count = jdbcTemplate.queryForObject(
            &quot;SELECT COUNT(*) FROM products&quot;,
            Long::class.java
        ) ?: 0L

        assertThat(count).isGreaterThanOrEqualTo(0L)
    }
}&lt;/code&gt;&lt;/pre&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;LazyConnectionDataSourceProxy 래핑 확인&lt;/li&gt;
&lt;li&gt;Writer/Reader DataSource Bean 등록 확인&lt;/li&gt;
&lt;li&gt;Reader DataSource readOnly 설정 검증&lt;/li&gt;
&lt;li&gt;쓰기/읽기 트랜잭션에서 JDBC 쿼리 실행 성공&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TestContainers 설정 (Optional)&lt;/h3&gt;
&lt;pre class=&quot;roboconf&quot;&gt;&lt;code&gt;object PostgresTestContainer {
    fun configureProperties(registry: DynamicPropertyRegistry) {
        val jdbcUrl = instance.jdbcUrl
        val username = instance.username
        val password = instance.password

        // Writer DataSource
        registry.add(&quot;spring.datasource.hikari.jdbc-url&quot;) { jdbcUrl }
        registry.add(&quot;spring.datasource.hikari.username&quot;) { username }
        registry.add(&quot;spring.datasource.hikari.password&quot;) { password }

        // Reader DataSource (테스트에서는 Writer와 동일)
        registry.add(&quot;spring.datasource.hikari.slaves.first.jdbc-url&quot;) { jdbcUrl }
        registry.add(&quot;spring.datasource.hikari.slaves.first.username&quot;) { username }
        registry.add(&quot;spring.datasource.hikari.slaves.first.password&quot;) { password }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 환경에서도 프로덕션과 동일한 Multi-DataSource 구조를 유지하되, Writer/Reader가 동일한 PostgreSQL 컨테이너를 사용합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot의 &lt;code&gt;AbstractRoutingDataSource&lt;/code&gt;와 &lt;code&gt;LazyConnectionDataSourceProxy&lt;/code&gt;를 활용하면 &lt;code&gt;@Transactional(readOnly=true)&lt;/code&gt; 어노테이션만으로 읽기/쓰기 데이터베이스를 자동 분산할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;code&gt;@Transactional&lt;/code&gt; 어노테이션에 &lt;code&gt;readOnly=true&lt;/code&gt;만 추가&lt;/li&gt;
&lt;li&gt;&lt;b&gt;휴먼 에러 방지&lt;/b&gt;: 개발자가 수동으로 DataSource를 선택할 필요 없음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동화된 라우팅&lt;/b&gt;: 트랜잭션 메타데이터 기반으로 시스템이 자동 판단&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일관성 보장&lt;/b&gt;: 모든 팀원이 동일한 패턴으로 DB 접근&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;잘못된 접근 방식 (수동 관리):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// ❌ 개발자가 매번 DataSource를 명시 (휴먼 에러 가능성)
@Autowired
@Qualifier(&quot;readerDataSource&quot;)
private lateinit var readerDataSource: DataSource

fun findProducts(): List&amp;lt;Product&amp;gt; {
    val connection = readerDataSource.connection  // 실수로 writerDataSource 사용 가능
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올바른 접근 방식 (자동 라우팅):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// ✅ 어노테이션만으로 자동 라우팅 (실수 불가능)
@Transactional(readOnly = true)
fun findProducts(): List&amp;lt;Product&amp;gt; {
    return productRepository.findAll()  // 시스템이 자동으로 Reader DB 선택
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 포인트&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;LazyConnectionDataSourceProxy&lt;/b&gt;: 트랜잭션 컨텍스트 완전 설정 후 Connection 획득&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AbstractRoutingDataSource&lt;/b&gt;: &lt;code&gt;isCurrentTransactionReadOnly()&lt;/code&gt; 기반 자동 라우팅&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HikariCP 표준화&lt;/b&gt;: Fixed-Size Pool, auto-commit=false 등 최적화 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계층적 설정&lt;/b&gt;: 공통 설정 + 모듈별 Pool Size 특화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Zero-Touch&lt;/b&gt;: 개발자는 &lt;code&gt;@Transactional&lt;/code&gt; 어노테이션만 신경 쓰면 됨&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 아키텍처는 실제 프로덕션 환경에서 검증된 패턴이며, &lt;b&gt;개발자의 실수를 시스템적으로 방지&lt;/b&gt;하면서 트래픽 증가에 대비한 안정적인 DB 부하 분산 솔루션입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby&quot;&gt;HikariCP Configuration Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/datasource/lookup/AbstractRoutingDataSource.html&quot;&gt;Spring AbstractRoutingDataSource JavaDoc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/tx-propagation.html#tx-propagation-required&quot;&gt;Spring Transaction Management - readOnly flag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Programming/SpringBoot</category>
      <category>db routing</category>
      <category>leader followers</category>
      <category>master slaves</category>
      <category>Multi DataSource</category>
      <category>reader writer</category>
      <author>iia_hannah</author>
      <guid isPermaLink="true">https://prohannah.tistory.com/274</guid>
      <comments>https://prohannah.tistory.com/274#entry274comment</comments>
      <pubDate>Thu, 31 Oct 2024 07:30:28 +0900</pubDate>
    </item>
    <item>
      <title>[월간회고] 2024년 9월, 생각 정리와 루틴</title>
      <link>https://prohannah.tistory.com/273</link>
      <description>&lt;h1&gt;지난 달 되돌아보기&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지난 달 액션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 저녁 루틴에 '독서' 추가하기 (= 수면을 위한 저녁 루틴 가꾸기)&lt;/b&gt;&lt;br /&gt;저녁 루틴에 독서를 넣은 후로 책 읽는 빈도가 엄청 늘었다. 책 읽기가 익숙해져서 그런지 저녁시간 뿐만 아니라 대중교통 이용할 때에도 많이 읽게 된다.&lt;br /&gt;저녁에 책을 읽고 나면 비교적 잡생각도 줄어들고 머리가 조용해지는 것 같다.&lt;br /&gt;아직 저녁 루틴에 넣은 지 얼마 안되서 빠르게 잠들기에 효과적인지는 좀 더 지켜봐야할 것 같아&lt;br /&gt;효과가 조금 있긴 한데, 침대에 누워서 생각이 시작될 때도 있어서 주의가 필요함!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 주 3회 운동&lt;/b&gt;&lt;br /&gt;제대로 워킹한 것 같다! 일정이 있었던 주만 빼고 주 3회 운동 했다!&lt;br /&gt;되도록 필라테스도 주 3회 신청하려고 노력했는데, 예약이 치열할 때가 있어서 다른 운동도 병행해야할 것 같다.&lt;br /&gt;다음 달에는 클라이밍 주말 강습 시작할 예정이다!&lt;br /&gt;컨디션이 안좋을 때에는 필라테스 대신 가벼운 홈트나 걷기 해야할 것 같다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;이번 달&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 이벤트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스터디 완료&lt;/b&gt;&lt;br /&gt;드디어 책 소프트웨어 아키텍처 101 스터디(4월 17일~9월 3일)가 끝났다.&lt;br /&gt;혼자였으면 못읽었을 책이라서 뿌듯하다.&lt;br /&gt;다들 중간에 회사 일이 바쁘고 일정이 있어서 스킵된 주가 좀 있어서 오래 걸렸는데, 스터디 오래 하니까 좀 괴로웠다.&lt;br /&gt;이번년도는 이제 스터디 그만...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;베란다 페인트칠&lt;/b&gt;&lt;br /&gt;미루고 미루던 베란다 페인트 칠을 추석에 했다.&lt;br /&gt;원래 3월에 한번 했는데 날이 너무 추워서 페인트가 습이 먹고 얼어서 흘러내렸다.&lt;br /&gt;날 풀리면 해야지 하다가 시간 순식간에 지나버려서 이제함..!&lt;br /&gt;추석이 생각보다 시원하지 않아서 베란다칠할때 진짜 덥고 힘들었는데, 결과물 보고 나니까 뿌듯하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 달 모아보기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운동 : 11일&lt;/li&gt;
&lt;li&gt;독서 : 13일&lt;/li&gt;
&lt;li&gt;아침루틴 : 11일 =&amp;gt; 이번달은 1/3밖에 못했네. 다음달에는 비율 높이자!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024년 9월 월간회고 1.jpeg&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;1268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kp996/btsJSEZxULT/hxKKVoNrYDo6l5kAcqEBB1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kp996/btsJSEZxULT/hxKKVoNrYDo6l5kAcqEBB1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kp996/btsJSEZxULT/hxKKVoNrYDo6l5kAcqEBB1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKp996%2FbtsJSEZxULT%2FhxKKVoNrYDo6l5kAcqEBB1%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;1535&quot; height=&quot;1268&quot; data-filename=&quot;2024년 9월 월간회고 1.jpeg&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;1268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;회고 &amp;amp; 액션아이템&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 생각을 비우고 마음을 평온하게 하기&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;&lt;b&gt;2. 늦게 자면 아침 루틴을 실패한다&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;밤 10시에는 집에 있자. (오후 9시에는 집으로 출발하고, 저녁에 과식 금지)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 주말 루틴이 워킹하지 않음&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주말/휴일에도 아침 루틴을 하자!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024년 9월 월간회고 2.jpeg&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;1509&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uxBEQ/btsJSivUA7Z/pqC4DHkkke39OWg8gaJxq0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uxBEQ/btsJSivUA7Z/pqC4DHkkke39OWg8gaJxq0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uxBEQ/btsJSivUA7Z/pqC4DHkkke39OWg8gaJxq0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuxBEQ%2FbtsJSivUA7Z%2FpqC4DHkkke39OWg8gaJxq0%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;430&quot; height=&quot;741&quot; data-filename=&quot;2024년 9월 월간회고 2.jpeg&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;1509&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>인생이란/회고&amp;amp;피드백시스템</category>
      <category>월간회고</category>
      <category>피드백루프</category>
      <category>회고</category>
      <author>iia_hannah</author>
      <guid isPermaLink="true">https://prohannah.tistory.com/273</guid>
      <comments>https://prohannah.tistory.com/273#entry273comment</comments>
      <pubDate>Wed, 2 Oct 2024 07:34:53 +0900</pubDate>
    </item>
    <item>
      <title>[소프트웨어 아키텍처 101] 13. 서비스 기반 아키텍처 스타일</title>
      <link>https://prohannah.tistory.com/270</link>
      <description>&lt;h1&gt;인상깊은 부분&lt;/h1&gt;
&lt;h5&gt;&lt;b&gt;분산 시스템에서의 데이터 무결성&lt;/b&gt;&lt;/h5&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;BASE 분산 트랜잭션&lt;/span&gt;라는 새로운 개념의 존재를 알게 됨&lt;br /&gt;분산도가 높은 아키텍처에서는 최종 일관성 기반의 BASE 분산 트랜잭션 활용. 이 기법은 최종 일관성 기반이므로 ACID 트랜잭션 레벨의 데이터 무결성은 지원하지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;최종 일관성&lt;/span&gt;이란 별도로 분리된 배포 단위에서 처리된 데이터를 미리 알 수 없는 어느 시점에 모두 일관된 상태로 동기화한다.&lt;br /&gt;확장성, 성능, 가용성을 얻는 대가로 데이터 일관성과 무결성을 희생하는 트레이드오프인 셈이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;트랜잭션 사가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;오케스트레이션 및 코레오그래피&lt;/span&gt;&lt;/p&gt;
&lt;h5&gt;&lt;b&gt;DB 분리 시 도메인 서비스 간 상호 통신 방지와 데이터베이스 간 중복 데이터 방지를 고려&lt;/b&gt;&lt;/h5&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 기반 아키텍처에서 단일 모놀리식 데이터베이스를 개별 데이터베이스로 분리할 수 있고, 마이크로서비스와 비슷하게 각 도메인 서비스의 전용 데이터베이스들로도 쪼갤 수도 있음&lt;br /&gt;여기서 포인트는 각 데이터베이스에 있는 도메인 데이터를 다른 도메인의 서비스가 필요로 하지 않도록 설계하는 것임&lt;br /&gt;그래야 도메인 서비스 간 상호 통신을 방지하고 데이터베이스 간의 중복 데이터가 방지됨&lt;/p&gt;
&lt;h5&gt;&lt;b&gt;서비스 단위에 따른 트레이드 오프&lt;/b&gt;&lt;/h5&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 기반 아키텍처는 도메인 서비스의 단위가 크기 때문에 데이터 무결성, 일관성 측면에서 유리하다.&lt;br /&gt;반면 서비 스규모가 작은 MSA와 같은 아키텍처는 작은 서비스 하나만 변경 영향도가 있고, 서비스가 한 가지 역할만 수행하므로 변경을 해도 다른 기능이 망가질 일이 거의 없다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;요약&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 기반 아키텍처(service-based architecture)는 마이크로서비스 아키텍처 스타일의 일종으로, 가장 실용적인 아키텍처 스타일 중 하나&lt;/li&gt;
&lt;li&gt;유연하고 비교적 덜 복잡하고 비용이 많이 들지 않아서 널리 채택됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;13.1 기본 토폴리지&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 구성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각각 따로 배포된 유저 인터페이스, N개의 원격 서비스, 그리고 모놀리스 데이터베이스&lt;/li&gt;
&lt;li&gt;이 아키텍처 스타일에서 '서비스'는 큼지막한 단위로 분리해 별도로 배포하는 어플리케이션의 일부 (보통 도메인 서비스라고 함)&lt;/li&gt;
&lt;li&gt;여러 서비스가 단일 모놀리식 데이터베이스를 공유하며, 서비스는 보통 4개~12개, 평균 7개 정도&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;배포 : 도메인 서비스는 각각 단일 인스턴스로 배포되지만, 확장성/내고장성/처리량 요구사항에 따라 N개의 인스턴스를 둘 수 있음&lt;/li&gt;
&lt;li&gt;통신
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유저 인터페이스 외부에서 원격 액세스 프로토콜로 서비스에 접속 가능함. 프로토콜은 일반적으로 REST를 많이 쓰며, 메시징, 원격 프로시저 호출(RPC), SOAP도 사용 가능함&lt;/li&gt;
&lt;li&gt;유저 인터페이스(또는 외부 요청)은 프록시나 게이트웨이로 구성된 API 레이어를 통해 서비스 접속 가능하지만, 대개는 [[서비스 로케이터 패턴(service locator pattern)]]에 따라 유저 인터페이스, API 게이트웨이, 프록시에 내장된 유저 인터페이스를 직접 액세스함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서비스 기반 아키텍처는 중앙 공유 데이터베이스를 사용한다는 특징이 중요&lt;/b&gt;. 서비스는 기존 모놀리식 레이어드 아키텍처와 동일한 방식으로 쿼리와 조인 기능을 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;13.2 토폴로지 변형 (다양한 구성)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유저 인터페이스 변형 (그림 13-2)&lt;/li&gt;
&lt;li&gt;데이터베이스 변형 (그림 13-3)
&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;b&gt;데이터베이스 분리 시 중요한 점은, 각 데이터베이스에 있는 도메인 데이터를 다른 도메인 서비스가 필요로 하지 않도록 설계하는 것&lt;/b&gt;.그래야 도메인 서비스 간 상호 통신을 방지하고 데이터베이스 간의 중복 데이터를 방지할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;API 레이어 추가 (그림 13-4)
&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;br /&gt;![[소프트웨어 아키텍처 101 13장 다양한 토폴로지.png]]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;13.3 서비스 설계 및 세분도 (최상위 분할)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 기반 아키텍처의 도메인 서비스는 보통 단위가 크기 때문에 레이어드 아키텍처 스타일로 기술 분할하는 방법이 일반적이지만, 도메인 분할도 쓰인다.&lt;/li&gt;
&lt;li&gt;API 액세스 퍼사드(access facade)
&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;어떻게 분할하든 도메인 서비스는 유저 인터페이스에서 비즈니스 기능을 호출하기 위해 접속할 수 있게 API 액세스 퍼사드를 제공해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;세분도 관점에서의 서비스 아키텍처와 MSA의 차이 (API 액세스 퍼사드)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 기반 아키텍처는 내부 클래스 수준의 오케스트레이션&lt;br /&gt;예를 들어, 유저 인터페이스에서 주문이 접수되면 OrderService 도메인 서비스의 API 액세스 퍼사드가 받아 내부 처리 후, 재고 차감 등의 일련의 비즈니스 요청을 오케스트레이트함&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MSA는 외부 서비스의 오케스트레이션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어, 위와 같은 요청을 MSA에서 처리한다면 별도 배포된 다수의 원격 서비스를 오케스트레이션 해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 무결성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 도메인 서비스 - 커밋/롤백이 수반되는 [[ACID 트랜잭션]] 데이터베이스 트랜잭션 활용&lt;br /&gt;예를 들어, 고객이 주문 후 만료된 신용카드로 결제를 했다면, 동일 서비스에서 발생한 원자적 트랜잭션이니 데이터베이스에 추가된 주문 관련 데이터를 롤백해서 모두 삭제하고 해당 고객에게 더 이상 결제 진행이 불가함을 알릴 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분산도가 높은 아키텍처(ex: 마이크로 서비스) - 최종 일관성 기반의 [[BASE 분산 트랜잭션]] 활용. 이 기법은 최종 일관성 기반이므로 ACID 트랜잭션 레벨의 데이터 무결성은 지원하지 않음&lt;br /&gt;서비스를 잘게 나눈 MSA에서 위와 동일하게 만료된 신용카드로 결제를 했다면, 주문 데이터는 데이터베이스에 삽입되었으나 결제는 불가하고 주문 처리를 진행할 수 없어서 비일관된 상태(inconsistent state)가 됨. 그렇다면 주문과 관련된 재고 정보는 주문이 성공한 것으로 보고 재고 차감을 해야할까요? 만약 재고가 없는 상태에서 다른 고객이 동일 제품을 주문한다면 새 고객이 구매할 수 있게 해야할까, 아니면 신용카드가 만료된 고객이 주문을 마칠 수 있도록 재고를 확보해두어야할까?&lt;/li&gt;
&lt;li&gt;서비스가 잘게 나누어진 아키텍처에서 비즈니스 프로세스를 오케스트레이트하려면 신경 써야 할 문제가 많다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;트레이드 오프
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 기반 아키텍처의 도메인 서비스는 단위가 커서 데이터 무결성과 일관성 측면에서는 유리하지만 배포와 변경 영향도 측면에서는 트레이드오프가 존재함&lt;/li&gt;
&lt;li&gt;서비스 기반 아키텍처는 일부 기능이 변경되어도 서비스 전체를 테스트해야함&lt;/li&gt;
&lt;li&gt;반면, 규모가 작은 서비스 단위의 아키텍처는 작은 서비스 하나만 변경 영향도가 있고, 서비스가 한 가지 역할만 수행하므로 변경을 해도 다른 기능이 망가질 일이 거의 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;13.4 데이터베이스 분할&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 모놀리식 데이터베이스 공유 시 문제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 기반 아키텍처는 서비스 수가 적은 편이라 보통 단일 모놀리식 데이터베이스를 공유함&lt;/li&gt;
&lt;li&gt;데이터베이스 커플링은 테이블 스키마 변경 시 문제가 될 수 있음&lt;/li&gt;
&lt;li&gt;테이블 스키마를 올바르게 변경하지 않을 경우 모든 서비스에 악영향을 미침&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공용 스키마 관리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 공유 라이브러리 활용 (그림 13-6)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블 구조를 조금이라도 변경하게 되면, 변경된 테이블의 사용 여부와 상관없이 전체 서비스를 일제히 변경 후 재배포할 수 밖에 었음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;여러 공유 라이브러리 활용 (그림 13-7)
&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;단, 모든 서비스가 참조하는 공통 테이블을 변경하려면 전체 서비스와 미리 조율이 필요함. 테이블 변경 영향도를 낮추는 방법 중 하나는 공통 엔티티 객체를 버전 관리 시스템에서 락킹하고 수정 권한을 DBA팀에만 부여하는 것임. 이렇게 해야 변경을 통제할 수 있고 모든 서비스의 공용 테이블 변경하는 작업의 중요성이 부각됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;13.5 아키텍처 예시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전자제품 재활용 시스템을 예로 서비스 기반 아키텍처의 유연함과 강점을 살펴보자. 중고 전자 제품 재활용 프로세스는 아래와 처리 순서로 이뤄진다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;견적 (quoting) : 고객이 자신의 중고 제품을 얼마에 보상받을 수 있는지 문의&lt;/li&gt;
&lt;li&gt;수취 (receiving) : 고객이 보상가에 만족하면 회사에 제품을 보내고 회사는 실물을 받음&lt;/li&gt;
&lt;li&gt;감정 (assessment) : 고객이 보낸 제품의 작동 상태를 꼼꼼히 평가&lt;/li&gt;
&lt;li&gt;회계 (accounting) : 상태가 좋을 경우 회사는 고객에게 약속한 보상가를 지불&lt;/li&gt;
&lt;li&gt;제품 상태 (item status) : 이 과정 중 고객은 언제든 회사 웹사이트에 접속하여 진행 상황 확인 가능&lt;/li&gt;
&lt;li&gt;재활용 (recycling) : 감정 결과에 따라 제품은 해제 후 재활용되거나 재판매&lt;/li&gt;
&lt;li&gt;리포팅 (reporting) : 재활용 성과에 따른 임시/정기 재무 리포트를 정기적으로 체크&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;전자 제품 재활용 서스템을 서비스 기반 아키텍처로 설계 (그림 13-8)
&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;퀀텀 (2개)
&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;/li&gt;
&lt;li&gt;보안
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 고객은 내부 기능으로 향하는 네트워크에 접속 불가&lt;/li&gt;
&lt;li&gt;내부 데이터와 기능을 외부 작업과 분리된 별도 네트워크 영역에 두어 데이터 보안에 긍정적 요소&lt;/li&gt;
&lt;li&gt;방화벽을 통해 내부 서비스는 단방향으로 외부 서비스에 접근 가능하지만, 반대 케이스는 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터베이스 분리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 고객 처리용 데이터베이스와 내부 처리용 데이터베이스를 물리적으로 분할&lt;/li&gt;
&lt;li&gt;데이터베이스에 따라서 내부 테이블을 미러링하고 테이블을 동기화할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변경이 잦은 서비스를 단일 도메인 서비스로 격리하여 민첩성(얼마나 빨리 변화에 대응할 수 있나), 시험성(테스트하기 얼마나 쉽고 완전한가), 배포성(배포의 난이도, 빈도, 리스크)을 높일 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;13.6 아키텍처 특성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등급별점 5개 받은 특성은 없지만 두루두루 밸런스 좋은 아키텍처이다.&lt;/p&gt;
&lt;table style=&quot;height: 605px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;th style=&quot;width: 79px; height: 20px;&quot;&gt;아키텍처 특성&lt;/th&gt;
&lt;th style=&quot;width: 121px; height: 20px;&quot;&gt;별점&lt;/th&gt;
&lt;th style=&quot;width: 652px; height: 20px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 20px;&quot;&gt;분할 유형&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 20px;&quot;&gt;도메인&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 20px;&quot;&gt;도메인 위주로 구성된 아키텍처&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 60px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 60px;&quot;&gt;퀀텀 수&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 60px;&quot;&gt;하나 또는 여러 개&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 60px;&quot;&gt;동일한 데이터베이스나 유저 인터페이스를 공유할 경우 전체 시스템의 퀀텀은 1이지만, 유저 인터페이스와 데이터베이스는 분리되어 연합될 수 있으므로 전체 시스템 내부에 여러 퀀텀이 생길 수 있음 그림 13-8 전자 제품 재활용 시스템의 퀀텀은 2임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 40px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;배포성&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 40px;&quot;&gt;⭐️⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 40px;&quot;&gt;개별 배포되는 여러 도메인 서비스로 나누기 때문에 덩치 큰 모놀리스보다 덜 위험하게, 더 자주 배포할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 80px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 80px;&quot;&gt;탄력성&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 80px;&quot;&gt;⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 80px;&quot;&gt;서비스를 나누는 단위가 크기 때문에 별점 2점 프로그램 방식으로 탄력성을 추구할 수 있지만 중복되는 기능이 많아 머신 리소스 및 비용 측면에서 효율적이지 않음 그리고 처리량이나 페일오버를 개선해야하는 요건이 따로 없다면 서비스 인스턴스는 1개임. 단일 인스턴스는 인메모리 캐시, 데이터베이스 커넥션 풀링 지원이 더 간편함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 25px;&quot;&gt;진화성&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 25px;&quot;&gt;⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 25px;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 40px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;내고장성&amp;amp;가용성&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 40px;&quot;&gt;⭐️⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 40px;&quot;&gt;자기 완비형이고 데이터베이스와 코드를 공유하는 까닭에 서비스 간 통신이 거의 없고, 어느 도메인 서비스가 잘못돼도 다른 서비스는 옇양을 받지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 25px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;모듈성&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 25px;&quot;&gt;⭐️⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 25px;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 40px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;전체 비용&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 40px;&quot;&gt;⭐️⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 40px;&quot;&gt;가장 구현하기 쉽고 비용면에서도 효율적인 분산 아키텍처로, 단순성과 전체 비용 측면에서 다른 분산 아키텍처와 차별화됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 25px;&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 25px;&quot;&gt;⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 25px;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 60px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 60px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;신뢰성&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 60px;&quot;&gt;⭐️⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 60px;&quot;&gt;도메인 서비스를 굵직하게 나누기 때문에 다른 분산 아키텍처에 비해 신뢰성이 우수함. 서비스 간 네트워크 트래픽이 적고 대역폭을 덜 사용하며, 분산 트랜잭션이 많지 않기 때문에 전반적으로 네트워크 측면에서 신뢰성이 좋음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 80px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 80px;&quot;&gt;확장성&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 80px;&quot;&gt;⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 80px;&quot;&gt;서비스를 나누는 단위가 크기 때문에 별점 3점 프로그램 방식으로 확장성을 추구할 수 있지만 중복되는 기능이 많아 머신 리소스 및 비용 측면에서 효율적이지 않음 그리고 처리량이나 페일오버를 개선해야하는 요건이 따로 없다면 서비스 인스턴스는 1개임. 단일 인스턴스는 인메모리 캐시, 데이터베이스 커넥션 풀링 지원이 더 간편함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 40px;&quot;&gt;단순성&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 40px;&quot;&gt;⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 40px;&quot;&gt;가장 구현하기 쉽고 비용면에서도 효율적인 분산 아키텍처로, 단순성과 전체 비용 측면에서 다른 분산 아키텍처와 차별화됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 25px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;시험성&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 25px;&quot;&gt;⭐️⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 25px;&quot;&gt;개별 배포되는 여러 도메인 서비스로 나누기 때문에 도메인 범위가 한정되므로 테스트 커버리지가 향상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 79px; height: 25px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;민첩성&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 121px; height: 25px;&quot;&gt;⭐️⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 652px; height: 25px;&quot;&gt;개별 배포되는 여러 도메인 서비스로 나누기 때문에 신속한 변경이 가능함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;13.7 언제 서비스 기반 아키텍처를 사용하는가&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순성과 비용 측면에서 가장 실용적인 아키텍처
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이보다 더 강력한 분산 아키텍처 스타일도 있지만, 그 강력함은 곧 가파른 비용 상승을 동반하고 그렇게 까지 강력할 필요가 없음을 알게됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도메인 주도 설계와 궁합이 좋음&lt;/li&gt;
&lt;li&gt;ACID 트랜잭션 사용 비율이 높음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분산 아키텍처에서는 전통적인 ACID 트랜잭션이 아닌 최종 일관성에 의존하는 방식으로 수행되기 때문에 데이터베이스 트랜잭션을 관리하고 조율하는 일이 늘 골칫거리임&lt;/li&gt;
&lt;li&gt;그러나 서비스 기반 아키텍처의 도메인 서비스는 큼지막한 단위로 구성되므로 다른 분산 아키텍처에 비해 ACID 트랜잭션이 더 잘 보존됨&lt;/li&gt;
&lt;li&gt;물론, 유저 인터페이스나 API 게이트웨이가 둘 이상의 도메인 서비스를 오케스트레이션하는 경우도 있는데, 이럴 때에는 트랜잭션이 사가와 BASE 트랜잭션에 의존해야함&lt;/li&gt;
&lt;li&gt;그러나 대부분 트랜잭션은 특정 도메인 서비스에 한정되므로 전통적인 커밋/롤백 트랜잭션 기능을 그대로 사용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;아키텍처 모듈성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡도와 세분도의 함정에 허우적거리지 않고도 모듈성을 괜찮은 수준으로 달성할 수 있음&lt;/li&gt;
&lt;li&gt;서비스를 더 잘게 나눌수록 [[오케스트레이션]] 및 [[코레오그래피]] 관련 이슈가 발생함. 여러 서비스를 조율해서 비즈니스 트랜잭션을 완성하려면 이 둘 다 필요함&lt;/li&gt;
&lt;li&gt;그러나 서비스 기반 아키텍처의 서비스는 더 큰 단위로 나뉘어지는 편이라서 다른 분산 아키텍처 만큼 정교한 조율이 필요하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>엔지니어링/설계</category>
      <category>서비스기반아키텍처</category>
      <category>소프트웨어아키텍처</category>
      <category>소프트웨어아키텍처101</category>
      <category>소프트웨어아키텍처101요약</category>
      <category>소프트웨어아키텍처요약</category>
      <author>iia_hannah</author>
      <guid isPermaLink="true">https://prohannah.tistory.com/270</guid>
      <comments>https://prohannah.tistory.com/270#entry270comment</comments>
      <pubDate>Sat, 14 Sep 2024 11:14:11 +0900</pubDate>
    </item>
    <item>
      <title>[월간회고] 2024년 8월, 삶의 질(식단과 수면)</title>
      <link>https://prohannah.tistory.com/271</link>
      <description>&lt;h1&gt;지난 달 되돌아보기&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지난 달 액션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 실수를 통해 배우자! 체크리스트 만들기!&lt;/b&gt;&lt;br /&gt;여러 개 모아봤음 ㅎㅎ 조만간 팀원들에게 공유하면서 더 취합해봐야겠다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 잠들기 전 일기로 하루에 있었던 일을 적자&lt;/b&gt;&lt;br /&gt;하루 마무리를 통해 기억력을 높이고 잠들기 전 잡생각을 떨쳐내기 위함이었는데, 수면 측면에서는 효과적이지는 않았다. 한 달 중 10일 정도 일기 쓴 것 같은데, 하루를 되짚어보는 건 여러모로 좋은 것 같아서 루틴 유지하려고 한다.&lt;br /&gt;근데 야근이나 약속이 있으면 저녁 루틴을 잘 안하게 되서, 저녁 루틴은 optional 성격이 있는 것들만 넣어놔야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 주말 활용하기&lt;/b&gt; - 주말 아침도 평일과 동일한 루틴을 하되, 블로그 포스팅이나 이력서 갱신 같은 Task를 하는 날로 활용하자!&lt;br /&gt;쉽지 않다. 결국 주말을 야무지게 활용한 날은 적어서 아쉽다.&lt;br /&gt;나태하게 아무 생각 없이 집에만 있는 게 좋기도 하고, 주말에 약속이 있으면 미루다 잘 안하게 되는 것 같아.&lt;br /&gt;되도록 주말도 아침 루틴을 챙겨서, 기술 블로그나 이력서 갱신처럼 계속 미뤄지거나 까먹는 일을 하도록 하면 좋겠는데...&lt;br /&gt;뭔가 평일처럼 출근같은 강제성이 없으니까 어떻게 해야할지 모르겠군!&lt;br /&gt;주말루틴은 다시 생각해보도록하자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 업무 중 답답하더라도 스트레스와 동요를 드러내고 표현하지 말자&lt;/b&gt;&lt;br /&gt;딱히 스트레스 받는 일이 없었어서 회고가 어렵다. 범위를 사람으로 넓혀야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 새로운 팀원들과 친해져보자!&lt;/b&gt;&lt;br /&gt;새로운 사람들과 다양한 대화를 해보자!&lt;br /&gt;ㅎㅎ팀원분들의 좋은 점을 조금씩 발견하고 있어서 친밀감이 많이 생겼음! 하지만 서로를 알아가기에는 대화 시간이 부족하긴 해서 종종 티타임을 제안하면 좋을 것 같다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;이번 달&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 이벤트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수면 습관&lt;/b&gt;&lt;br /&gt;요즘도 잡생각이 많아서 졸리고 피곤한데도 잠에 바로 들지 못해서 힘들었는데, 최근에 적당히 재미없는 책 30분 정도 읽으니까 잡념도 사라지고 잠에도 금방 들 수 있었다. 저녁 루틴에 책 읽기 넣어봐야겠음!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;건강한 식사&lt;/b&gt;&lt;br /&gt;요즘 그릭요거트 만들어 먹는 거에 푹 빠져서 5번째 만들어 먹는 중&lt;br /&gt;유투브에도 저속노화나 거꾸로식단 관련된 추천이 많이 뜬다.&lt;br /&gt;요즘 아침에는 그릭요거트, 블루베리, MCT 오일, 올리브오일 섞어서 먹는 중!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;광고 관련 업무&lt;/b&gt;&lt;br /&gt;경험해보지 못한 도메인이라서 재미있고, 드디어 돈 버는 업무를 하게 되서 기대가 많이 된다.&lt;br /&gt;대규모 이벤트 전에 개발되어야해서 제 시간 내에 개발하는 것이 중요하다.&lt;br /&gt;커머스의 꽃 결제, 포인트 관련 코어 로직을 새로 개발하게 되서 챌린지되고 좋다.&lt;br /&gt;낯선 도메인이다보니 업무 불확실성을 빠르고 명확하게 하는 게 중요한데, 다행히 팀에서 서포트를 많이 해주셔서 도움이 많이 된다.&lt;br /&gt;요즘 일하는거 너무 재밌음 ㅎㅎ&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 달 모아보기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024년 8월 월간회고_01.jpg&quot; data-origin-width=&quot;3009&quot; data-origin-height=&quot;2514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu658M/btsJzgyj5vU/B8laYCJ8KmwC8HFbzaklkk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu658M/btsJzgyj5vU/B8laYCJ8KmwC8HFbzaklkk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu658M/btsJzgyj5vU/B8laYCJ8KmwC8HFbzaklkk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu658M%2FbtsJzgyj5vU%2FB8laYCJ8KmwC8HFbzaklkk%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;3009&quot; height=&quot;2514&quot; data-filename=&quot;2024년 8월 월간회고_01.jpg&quot; data-origin-width=&quot;3009&quot; data-origin-height=&quot;2514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반복되는 생각/다짐/할 일&lt;/h3&gt;
&lt;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;운동 -&amp;gt; 필테 예약 성공하지 못하면 주 1회만 운동하게 되는 경우가 있음. 보충할 방법 필요&lt;/li&gt;
&lt;li&gt;주말 루틴 -&amp;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;h3 data-ke-size=&quot;size23&quot;&gt;회고&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024년 8월 월간회고_02.jpg&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;3027&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doRXSS/btsJx1IOXBf/HQq6yOx4hGv6TnSkKkOwMk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doRXSS/btsJx1IOXBf/HQq6yOx4hGv6TnSkKkOwMk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doRXSS/btsJx1IOXBf/HQq6yOx4hGv6TnSkKkOwMk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoRXSS%2FbtsJx1IOXBf%2FHQq6yOx4hGv6TnSkKkOwMk%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;600&quot; height=&quot;988&quot; data-filename=&quot;2024년 8월 월간회고_02.jpg&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;3027&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다음 달 액션&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저녁 루틴에 책읽기 넣기 (소설류는 흥미진진하니까 제외하고, 경제나 자기계발 혹은 상식 관련된 책을 읽는 게 좋겠음)&lt;/li&gt;
&lt;li&gt;주 3회 운동하기 (필테 수업 못듣는 때면 홈트와 클라이밍을 다녀오자!)&lt;/li&gt;
&lt;li&gt;체크리스트 만들기 세분화 - PR 체크리스트 만들기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다음달 목표 정하기&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;운동 주 3회&lt;/li&gt;
&lt;li&gt;실수를 통해 배우자! PR 체크리스트 만들기&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>인생이란/회고&amp;amp;피드백시스템</category>
      <author>iia_hannah</author>
      <guid isPermaLink="true">https://prohannah.tistory.com/271</guid>
      <comments>https://prohannah.tistory.com/271#entry271comment</comments>
      <pubDate>Thu, 12 Sep 2024 08:02:22 +0900</pubDate>
    </item>
    <item>
      <title>[소프트웨어 아키텍처 101] 12. 마이크로커널 아키텍처 스타일</title>
      <link>https://prohannah.tistory.com/269</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;인상깊은 부분&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보험 청구처럼 비즈니스 케이스가 복잡한 경우에도 사용이 용이하겠다&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이크로커널 아키텍처(microkernel architecture) 혹은 플러그인 아키텍처(plug-in architechture)
&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;비제품(nonproduct) 고객 비즈니스 어플리케이션에서도 많이 사용&lt;/li&gt;
&lt;li&gt;예시) IDE와 같은 텍스트 에디터&lt;/li&gt;
&lt;li&gt;예시) Payment Processing이 코어 시스템을 나타내는 도메인이라면, 결제 수단(신용카드, 페이팔, 선불 카드 등)이 플러그인 컴포넌트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12.1 토폴로지&lt;/h2&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;코어 시스템
&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;/li&gt;
&lt;li&gt;플러그인 컴포넌트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플러그인 컴포넌트란?
&lt;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;span style=&quot;background-color: #f6e199;&quot;&gt;변동성이 매우 큰 코드를 분리하여 어플리케이션 내부의 유지보수성, 시험성을 높임.&lt;/span&gt;클라이언트에 종속된 코드를 각 전자 제품마다 플러그인 컴포넌트를 따로 제작 (코어에 두면 순환복잡도가 높아지기 때문)&lt;/li&gt;
&lt;li&gt;이상적인 플러그인 컴포넌트는 상호독립적이며 의존성이 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;통신 방식 (점대점 통신 or 원격 액세스)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플러그인 컴포넌트와 코어 시스템은 일반적으로 점대점(point-to-point) 통신&lt;/li&gt;
&lt;li&gt;하지만 REST나 메시징 등 다른 방법으로 기능 호출하는 방법도 있음. 플러그인 컴포넌트를 개별 서비스로 구현해서 원격 액세스하는 방법은 전체 컴포넌트의 커플링이 낮아져 성과 처리량이 개선되고, 런타임 변경이 가능하다는 장점이 있음. 비동기 통신도 가능하므로 경우에 따라 전체 유저 반응성을 엄청나게 끌어올릴 수 있음&lt;/li&gt;
&lt;li&gt;하지만 위와 같은 원격 통신은 전체 확장성을 개선하는 좋은 방법 같지만, 이 토폴로지는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;코어 시스템이 모놀리식이므로 여전히 단일 아키텍처 퀀텀임&lt;/span&gt;. 즉, 모든 요청이 무조건 코어 시스템을 거쳐 각 플러그인 서비스로 호출하는 구조&lt;/li&gt;
&lt;li&gt;개별 서비스로 구현 시 마이크로커널 아키텍처를 모놀리식이 아닌 분산 아키텍처로 바꿔야함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 경우, 대부분의 서드파티 온프렘 제품은 그렇게 구현/배포하기가 쉽지 않고 전반적으로 복잡도와 비용이 높아져 전체 배포 포톨리지가 상당히 난해해짐&lt;/li&gt;
&lt;li&gt;플러그인이 무응답이거나 작동하지 않는 경우 (특히 REST 사용 시) 요청은 완료될 수 없음 (모놀리식이었다면 이런 일은 없을 것)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12.2 레지스트리&lt;/h2&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;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;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12.3 계약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코어 시스템과 플러그인 컴포넌트 간의 계약
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 플러그인 컴포넌트의 도메인 단위로 표준화되어 있고, 플러그인 컴포넌트가 수행하는 기능 및 입출력 데이터가 계약에 명시되어 있음&lt;/li&gt;
&lt;li&gt;일반적으로 컹 시스템이 각 플러그인별 코드를 필요로 하지 않도록 플러그인 계약과 우리가 저한 표준 계약 간의 어댑터를 만듬&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12.4 실제 용례&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이클립스 IDE, 인터넷 웹 브라우저&lt;/li&gt;
&lt;li&gt;대규모 비즈니스 어플리케이션에도 적용 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 보험금 청구 처리 어플리케이션
&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12.5 아키텍처 특성 등급&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이크로커널 아키텍처도 레이어드 아키텍처처럼 단순성과 전체 비용이 주요 강점임. 반면, 고질적인 모놀리식 배포 탓에 탄력성, 내고장성, 확장성이 문제가 될 때가 많음. 퀀텀 수는 언제나 1. 레이어드 아키텍처 스타일과의 유사성은 여기까지&lt;/li&gt;
&lt;li&gt;위치나 클라이언트에 따라 설정이 달라지는 문제는 이 아키텍처가 제격&lt;/li&gt;
&lt;li&gt;커스터마이징, 기능 신장성에 중점을 둔 제품도 이 아키텍처가 제격&lt;/li&gt;
&lt;/ul&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 80px;&quot;&gt;아키텍처 특성&lt;/th&gt;
&lt;th style=&quot;width: 62px;&quot;&gt;별점&lt;/th&gt;
&lt;th style=&quot;width: 710px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;분할 유형&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;도메인 및 기술&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;도메인 분할과 기술 분할이 모두 가능한 유일한 아키텍처 스타일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;퀀텀 수&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;모든 요청은 코어시스템을 통해 유입되어 독립적인 플러그인 컴포넌트로 흘러가므로 퀀텀은 언제나 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;배포성&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;기능을 독립적인 플러그인 컴포넌트로 분리 가능하므로 평균(별3)보다 약간 높게 매김. 잘 분할하면 변경분에 대한 테스트 범위와 배포 리스크가 줄어듬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;탄력성&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;⭐️&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;모놀리식 배포탓에 탄력성 낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;진화성&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;내고장성&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;⭐️&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;모놀리식 배포탓에 내고장성 낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;모듈성&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;플러그인 컴포넌트를 통해 기능을 추가, 삭제, 변경할 수 있고, 덕분에 어플리케이션 개선/확장 작업이 비교적 용이해 팀 차원에서 더욱 신속하게 변경에 대응할 수 있다는 점 때문에 평균(별3)보다 조금 높게 측정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;전체 비용&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;⭐️⭐️⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;굿굿!! 주요 강점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;애매하지만 평균(별3)보다 약간 높게 측정&lt;br /&gt;이 아키텍처는 대부분 규모가 작으며, 레이어드 아키텍처처럼 갈수록 커지지 않기 때문임. 또 아키텍처 싱크홀 안티패턴으로 고생할 필요도 없고 불필요한 기능은 해제하여 처리 흐름을 간소화할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;신뢰성&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;기능을 독립적인 플러그인 컴포넌트로 분리 가능하므로 평균(별3)보다 약간 높게 매김. 잘 분할하면 변경분에 대한 테스트 범위와 배포 리스크가 줄어듬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;확장성&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;⭐️&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;모놀리식 배포탓에 확장성 낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;단순성&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;⭐️⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;굿굿!! 주요 강점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot;&gt;시험성&lt;/td&gt;
&lt;td style=&quot;width: 62px;&quot;&gt;⭐️⭐️⭐️&lt;/td&gt;
&lt;td style=&quot;width: 710px;&quot;&gt;기능을 독립적인 플러그인 컴포넌트로 분리 가능하므로 평균(별3)보다 약간 높게 매김. 잘 분할하면 변경분에 대한 테스트 범위와 배포 리스크가 줄어듬&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>엔지니어링/설계</category>
      <category>마이크로커널</category>
      <category>마이크로커널아키텍처</category>
      <category>소프트웨어아키텍처101</category>
      <category>소프트웨어아키텍처12</category>
      <category>책 소프트웨어아키텍처</category>
      <author>iia_hannah</author>
      <guid isPermaLink="true">https://prohannah.tistory.com/269</guid>
      <comments>https://prohannah.tistory.com/269#entry269comment</comments>
      <pubDate>Sat, 7 Sep 2024 13:01:58 +0900</pubDate>
    </item>
    <item>
      <title>[소프트웨어 아키텍처 101] 11. TODO</title>
      <link>https://prohannah.tistory.com/268</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;/p&gt;</description>
      <category>엔지니어링/설계</category>
      <author>iia_hannah</author>
      <guid isPermaLink="true">https://prohannah.tistory.com/268</guid>
      <comments>https://prohannah.tistory.com/268#entry268comment</comments>
      <pubDate>Sat, 31 Aug 2024 12:59:08 +0900</pubDate>
    </item>
    <item>
      <title>[소프트웨어 아키텍처 101] 9. 아키텍처 스타일 기초</title>
      <link>https://prohannah.tistory.com/266</link>
      <description>&lt;h1&gt;인상깊은 부분/내 생각&lt;/h1&gt;
&lt;h5&gt;실무에서 순수하게 모놀리식을 쓰는 경우가 많을까?&lt;/h5&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;s3, serverless, firebase 등을 활용하는 순간 내 시스템 배포는 모놀리식 이어도 전체적인 모습은 분산 아키텍처를 사용하게 되는 거 아닐까?&lt;br /&gt;이 경우 분산아키텍처에서 고려해야하는 요소들을 제3자(네트워크를 이용하는) 연동 파트에서도 고려해야한다.&lt;br /&gt;비중의 문제인걸까? 완전한 모놀리식은 보기 어려운 것 같다.&lt;br /&gt;으음! 아키텍처 차원에서는 모놀리식이라고 보는게 맞으려나? 내가 컨트롤할 수 있는 영역을 어떻게 바라보고 다루느냐도 중요하니까..&lt;/p&gt;
&lt;h5&gt;분산 아키텍처에서의 트랜잭션&lt;/h5&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종일관성을 기반으로 트랜잭셔널 사가와 BASE 기법을 사용한다.&lt;br /&gt;확장성, 성능, 가용성을 얻는 대가로 데이터 일관성과 무결성을 희생하는 트레이드오프인 셈이다&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;요약&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아키텍처 스타일은 아키텍처 패턴이라도 부르며, 다양한 아키텍처 특성을 다루는 컴포넌트의 명명된 관계를 기술함9.1 기초 패턴 (기본적인 패턴)&lt;/li&gt;
&lt;li&gt;소프트웨어 아키텍처의 역사를 통틀어 끊임없이 나타나고 있는 패턴들&lt;/li&gt;
&lt;li&gt;9.1.1 진흙잡탕
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뭐 하나 뚜렷한 아키텍처 구조가 전무한 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;9.1.2 유니터리 아키텍처&lt;/li&gt;
&lt;li&gt;9.1.3 클라이언트/서버
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프론트엔드와 백엔드로 기술적으로 기능을 분리한 2티어 또는 클라이언트/서버 아키텍처&lt;/li&gt;
&lt;li&gt;데스트콥 + 데이터베이스 서버
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;표준 네트워크 프로토콜을 통해 접속가능한 스탠드얼론 데이터베이스 서버와 궁합이 좋음&lt;/li&gt;
&lt;li&gt;프레젠테이션 로직은 데스트콥에 두고 계산량이 많은 액션은 사양이 탄탄한 데이터베이스 서버에서 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;브라우저 + 웹서버&lt;/li&gt;
&lt;li&gt;3티어
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1990년대 후반 인기를 끈 3티어 아키텍처&lt;/li&gt;
&lt;li&gt;고성능 데이터베이스 서버를 사용하는 데이터베이스 티어, 애플리케이션 서버가 관리하는 어플리케이션 티어, 그리고 처음에는 HTML로 시작하여 기능이 점점 많아진 프론트엔드 티어&lt;/li&gt;
&lt;li&gt;3티어 아키텍처는 분산 아키텍처에 적합한 공통 객체 요청 브로커 아키텍처(Common Object Request Broker Architecture), 분산 컴포넌트 객체 모델(Distributed Component Object Model)과 같은 네트워크 수준의 프로토콜과 잘 맞음9.2 모놀리식 대 분산 아키텍처&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;아키텍처 스타일의 두가지 종류&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모놀리식 : 크게 전체 코드를 단일 단위로 배포하는 모놀리식
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레이어드 아키텍처 (10장)&lt;/li&gt;
&lt;li&gt;파이프라인 아키텍처 (11장)&lt;/li&gt;
&lt;li&gt;마이크로커널 아키텍처 (12장)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;분산형 : 원격 액세스 프로토콜을 통해 여러 단위로 배포하는 분산형. 모놀리식 아키텍처 스타일에 비해 성능, 확장성, 가용성 측면에서 훨씬 강력하지만 무시할 수 없는 트레이드오프가 존재함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 기반 아키텍처(13장)&lt;/li&gt;
&lt;li&gt;이벤트 기반 아키텍처 (14장)&lt;/li&gt;
&lt;li&gt;공간 기반 아키텍처 (15장)&lt;/li&gt;
&lt;li&gt;서비스 지향 아키텍처 (16장)&lt;/li&gt;
&lt;li&gt;마이크로서비스 아키텍처 (17장)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;분산 컴퓨팅의 오류&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;9.2.1 분산 컴퓨팅의 오류 #1 : 네트워크는 믿을 수 있다
&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;그래서 타임아웃 장치나 서비스 사이에 [[서킷 브레이커(circuit breaker)]]를 마련하는 것&lt;/li&gt;
&lt;li&gt;MSA처럼 네트워크에 의존할 수록 시스템의 신뢰도는 잠재적으로 떨어질 가능성이 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;9.2.2 분산 컴퓨팅의 오류 #2 : 레이턴시는 0이다
&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;특히 MSA(17장)은 서비스가 잘게 나뉘기 때문에 서비스 간 통신량도 만만치 않음&lt;/li&gt;
&lt;li&gt;평균 레이턴시도 중요하지만 95~99번째 백분위수를 이해하는 것은 더 중요함&lt;/li&gt;
&lt;li&gt;평균 레이턴시가 60밀리초에 불과해도 95번째 백분위수는 400밀리초일 수 있음. 보통 이런 긴 꼬리 레이턴시가 분산 아키텍처의 성능을 저해하는 주범이 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;9.2.3 분산 컴퓨팅의 오류 #3 : 대역폭은 무한하다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MSA에서 시스템이 자잘한 배포 단위(서비스)로 쪼개지면 이 서비스들 간에 주고받는 통신이 대역폭을 상당히 점유하여 네트워크가 느려지고, 결국 레이턴시(오류#2)와 신뢰성(오류#1)에도 영향을 미친다&lt;/li&gt;
&lt;li&gt;[[스탬프 커플링(stamp coupling)]] : 스탬프 커플링은 분산 아키텍처에서 상당히 많은 대역폭을 차지한다. 분산 아키텍처 서비스 또는 시스템 간에 최소한의 데이터만 주고받도록 하는 것이 최선의 해결 방법이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;9.2.4 분산 컴퓨팅의 오류 #4 : 네트워크는 안전하다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가상사설망(VPN), 실뢰할 수 있는 네트워크, 방화벽 이외의 나머지 네트워크는 안전하지 않다.&lt;/li&gt;
&lt;li&gt;알려지지 않은, 또는 악의적인 요청이 해당 서비스로 유입되지 않게 철저한 보안 대책을 강구해야 한다&lt;/li&gt;
&lt;li&gt;모든 엔드포인트에, 서비스 간 통신에도 보안이 적용돼야 하므로 MSA나 서비스 기반 아키텍처처럼 고도로 분산된 동기 아키텍처에서 당연히 성능이 떨어질 수 밖에 없다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;9.2.5 분산 컴퓨팅의 오류 #5 : 토폴로지는 절대 안 바뀐다
&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;/li&gt;
&lt;li&gt;9.2.6 분산 컴퓨팅의 오류 #6 : 관리자는 한 사람뿐이다
&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;/li&gt;
&lt;li&gt;9.2.7 분산 컴퓨팅의 오류 #7 : 네트워크 운송비는 0이다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기에서 운송비란, 단순 레이턴시가 아니라 'REST 호출'하는 데 소요되는 진짜 비용을 의미함&lt;/li&gt;
&lt;li&gt;분산 아키텍처는 하드웨어, 서버, 게이트웨이, 방화벽, 신규 서브넷, 프록시 등 리소스가 더 많이 동원되므로 모놀리식 아키텍처보다 비용이 훨씬 더 든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;9.2.8 분산 컴퓨팅의 오류 #8 : 네트워크는 균일하다
&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;모든 상황과 부하, 환경에서 100% 완벽하게 테스트를 마친 것은 아니므로 실제로 간혹 네트워크 패킷이 유실되는 사고도 심심찮게 일어난다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;9.2.9 다른 분산 아키텍처 고려 사항 : 분산 아키텍처를 설계할 때 맞딱드리게 될 이슈 및 해결해야할 난제들&lt;/span&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;span style=&quot;background-color: #f6e199;&quot;&gt;분산 트랜잭션&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모놀리식 아키텍처에서는 퍼시스턴스 프레임워크가 대신 실행하는 표준 커밋/롤백 기능인 ACID 트랜잭션을 걸어 데이터 일관성과 무결성을 강제한다.&lt;/li&gt;
&lt;li&gt;하지만 분산 아키텍처는 사정이 다르다.&lt;/li&gt;
&lt;li&gt;분산 아키텍처는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;최종 일관성(eventual consistency)&lt;/span&gt; 이라는 개념을 바탕으로 별도로 분리된 배포 단위에서 처리된 데이터를 미리 알 수 없는 어느 시점에 모두 일관된 상태로 동기화한다. &lt;span style=&quot;background-color: #f6e199;&quot;&gt;확장성, 성능, 가용성을 얻는 대가로 데이터 일관성과 무결성을 희생하는 트레이드오프인 셈이다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;분산 트랜잭션을 관리하는 방법으로 트랜잭셔널 사가(transactional saga), BASE 분산 트랜잭션이 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;계약 관리 및 버저닝
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계약은 클라이언트와 서비스 모두 합의한 행위와 데이터이다&lt;/li&gt;
&lt;li&gt;분리된 서비스와 시스템을 제각기 다른 팀과 부서가 소유하기 때문에 계약 유지보수가 특히 어렵다&lt;/li&gt;
&lt;li&gt;버전 구식화(deprecation)에 필요한 통신 모델은 더 더욱 복잡하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>엔지니어링/설계</category>
      <category>소프트웨어 아키텍처 101</category>
      <category>아키텍처 기본</category>
      <category>아키텍처 기초</category>
      <category>아키텍처 스타일</category>
      <category>아키텍처 패턴</category>
      <author>iia_hannah</author>
      <guid isPermaLink="true">https://prohannah.tistory.com/266</guid>
      <comments>https://prohannah.tistory.com/266#entry266comment</comments>
      <pubDate>Sat, 10 Aug 2024 13:29:54 +0900</pubDate>
    </item>
    <item>
      <title>[월간회고] 2024년 7월, 아침 루틴 가꾸기 성공  </title>
      <link>https://prohannah.tistory.com/265</link>
      <description>&lt;h1&gt;지난 달 되돌아보기&lt;/h1&gt;
&lt;h5&gt;지난 달 액션&lt;/h5&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 리듬감 있는 일상 만들기&lt;/b&gt;&lt;br /&gt;일찍자고 일찍 일어나려고 노력중이다!&lt;br /&gt;최대한 규칙적인 생활을 할 수 있도록 재택하는 날짜도 고정하려고 한다.&lt;br /&gt;그리고 8시 기준으로 재택 시에는 바로 출근을 하거나, 사무실 출근하는 날이면 출근 준비를 하는 것으로 기준 삼았다.&lt;br /&gt;수면 패턴이 정리되서 좀 더 일찍 일어나게 되면 아침 시간을 더 늘릴 수 있을 것 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 변수에 영향받지 않는 여유시간 확보하기&lt;/b&gt;&lt;br /&gt;아침 시간 1시간 30분 확보!&lt;br /&gt;요즘 일찍 자고, 알림 2번 이내로 정신이 바로 깬다. 일찍 잠든 날에는 몸도 개운에서 더 잘 일어나게 된다.&lt;br /&gt;종종 잠을 설친 날은 뭔가 피로하고 의욕이 없어서 루틴을 스킵하게 되지만, 잘 자고 일어난 날은 아침 시간이 여유롭다.&lt;br /&gt;아침에는 루티너리 앱으로 이런 루틴을 실행하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;아침루틴-루티너리.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dV35d1/btsIS7WLcVv/CfXUg0bVp6NXObeAJkQ3m0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dV35d1/btsIS7WLcVv/CfXUg0bVp6NXObeAJkQ3m0/img.jpg&quot; data-alt=&quot;루티너리 앱&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dV35d1/btsIS7WLcVv/CfXUg0bVp6NXObeAJkQ3m0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdV35d1%2FbtsIS7WLcVv%2FCfXUg0bVp6NXObeAJkQ3m0%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;282&quot; height=&quot;583&quot; data-filename=&quot;아침루틴-루티너리.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1340&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;루티너리 앱&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 할일 관리 도구 사용&lt;br /&gt;&lt;/b&gt;틱틱이랑 구글 캘린더 조합으로 할 일과 일정을 관리하고 있다.&lt;br /&gt;캘린더에 넣기에 애매했던 일들(ex: 장보기, 오리고기 언제까지 먹기 등)을 가볍게 관리하기 좋다.&lt;br /&gt;까먹지 않게 잔잔한 일들을 챙길 수 있고 머릿속이 복잡하지 않아서 좋다.&lt;br /&gt;Task를 생성할 때 데드라인과 중요도도 지정할 수 있어서 우선순위 관리할 수도 있음 :)&lt;br /&gt;위젯도 다양해서 핸드폰 바탕화면에 띄워놓으면 자주 일감 확인이 가능하고, 오늘 할 일만 따로 볼 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccvMQy/btsIS6Q355K/bxfv1lvsU4qHjZil5OHxok/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccvMQy/btsIS6Q355K/bxfv1lvsU4qHjZil5OHxok/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2340&quot; data-filename=&quot;틱틱-할일관리.jpg&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccvMQy/btsIS6Q355K/bxfv1lvsU4qHjZil5OHxok/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccvMQy%2FbtsIS6Q355K%2Fbxfv1lvsU4qHjZil5OHxok%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;1080&quot; height=&quot;2340&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7LpOu/btsIUKsh524/pVCpyRuGlWbXIrjsNoSND0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7LpOu/btsIUKsh524/pVCpyRuGlWbXIrjsNoSND0/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2340&quot; data-filename=&quot;위젯1.jpg&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7LpOu/btsIUKsh524/pVCpyRuGlWbXIrjsNoSND0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7LpOu%2FbtsIUKsh524%2FpVCpyRuGlWbXIrjsNoSND0%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;1080&quot; height=&quot;2340&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nlA3l/btsITwhoaQz/XTT1IRxLBvFwoKmkipwNGK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nlA3l/btsITwhoaQz/XTT1IRxLBvFwoKmkipwNGK/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2340&quot; data-filename=&quot;위젯2.jpg&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nlA3l/btsITwhoaQz/XTT1IRxLBvFwoKmkipwNGK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnlA3l%2FbtsITwhoaQz%2FXTT1IRxLBvFwoKmkipwNGK%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;1080&quot; height=&quot;2340&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;틱틱 앱 - 할일 관리와 위젯 활용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;틱틱으로 관리하면 추적이나 집계가 용이해서, 습관 만들기랑 위클리 TODO, 먼슬리 TODO 일감을 관리하고 있다.&lt;br /&gt;유료 버전을 활용하게 되면 더 많은 기능이 있다고 하는데, 일단은 무료 버전으로도 충분히 잘 활용하고 있다.&lt;br /&gt;아마 더 잘 활용하고 싶어질 때 쯤 유료 버전을 고민해볼 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6DMNM/btsITUWEAdA/stNG3PPjiKZXJiRAwo1XNk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6DMNM/btsITUWEAdA/stNG3PPjiKZXJiRAwo1XNk/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2340&quot; data-filename=&quot;틱틱-습관1.jpg&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6DMNM/btsITUWEAdA/stNG3PPjiKZXJiRAwo1XNk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6DMNM%2FbtsITUWEAdA%2FstNG3PPjiKZXJiRAwo1XNk%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;1080&quot; height=&quot;2340&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WsYpi/btsISVvfRkg/TKxEr4uQMTQrwckiYlh7w1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WsYpi/btsISVvfRkg/TKxEr4uQMTQrwckiYlh7w1/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2340&quot; data-filename=&quot;틱틱-습관2 집계.jpg&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WsYpi/btsISVvfRkg/TKxEr4uQMTQrwckiYlh7w1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWsYpi%2FbtsISVvfRkg%2FTKxEr4uQMTQrwckiYlh7w1%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;1080&quot; height=&quot;2340&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. one thing! 한 달에 목표 하나!&lt;/b&gt;&lt;br /&gt;한번에 너무 많은 걸 해내고 싶은 욕심으로 이것저것 시작했다가, 머리만 복잡해지고 결국 해내는게 거의 없었다.&lt;br /&gt;7월부터는 제일 중요한 목표 하나를 나에게 와닿는 문구로 만들어서 다이어리 먼슬리에 작성하고 있다.&lt;br /&gt;7월의 문구는 &quot;리듬감 있는 일상 만들기&quot; 였다.&lt;br /&gt;정말 한 달에 목표 하나만 세우지는 않았다. 왜냐면 내가 가용한 시간 유형이 아침과, 출퇴근, 평일 저녁, 주말인데!&lt;br /&gt;아침용 목표와 평일 저녁용 목표 이렇게 구분해서 세우니까 적절한 것 같다!&lt;br /&gt;내가 가용할 수 있는 시간과 그 때 이룰 수 있는 목표 세우기!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;이번 달&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 이벤트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;루틴 재정비&lt;/b&gt;&lt;br /&gt;상반기 회고를 통해 루틴을 재정비했는데 효과가 좋다 :)&lt;br /&gt;나한테 잘 워킹하고 있고, 잡생각이나 혼란스러운 마음도 정리되고 목표도 깨끗하게 정리되서 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내 생일&lt;/b&gt;&lt;br /&gt;생일과 관련된 여러 이벤트들이 있었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;맥북 구매&lt;/b&gt;&lt;br /&gt;고민하다가 M3 맥북프로 램 18GB 로 구매했다!&lt;br /&gt;이제 무겁게 16인치 회사 맥북을 들고 다니지 않아도 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;블로그 체험단 첫 체험&lt;/b&gt;&lt;br /&gt;네이버 블로그를 시작한지 한 달 정도 되었는데, 처음으로 식사권 지원받아서 남편이랑 외식했다 :)&lt;br /&gt;한 달에 1-2번 정도 이렇게 외식 데이트하면 좋을 것 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자동차 주차 접촉 사고&lt;/b&gt;&lt;br /&gt;ㅎㅎ후.. 열받지만 내 잘못이지..&lt;/p&gt;
&lt;h5&gt;한 달 모아보기&lt;/h5&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;먼슬리_24년7월.jpg&quot; data-origin-width=&quot;3270&quot; data-origin-height=&quot;2616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQCEej/btsIVcB5w8M/4PulRk2v8TF1ZNVdIfoUh0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQCEej/btsIVcB5w8M/4PulRk2v8TF1ZNVdIfoUh0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQCEej/btsIVcB5w8M/4PulRk2v8TF1ZNVdIfoUh0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQCEej%2FbtsIVcB5w8M%2F4PulRk2v8TF1ZNVdIfoUh0%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;3270&quot; height=&quot;2616&quot; data-filename=&quot;먼슬리_24년7월.jpg&quot; data-origin-width=&quot;3270&quot; data-origin-height=&quot;2616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h5&gt;반복되는 생각/다짐/할 일&lt;/h5&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;루틴&lt;/b&gt;&lt;br /&gt;아침 루틴은 잘 정착했는데, 평일 저녁과 주말 루틴이 아직 정착이 되지 않았다.&lt;br /&gt;일단 짧은 시간에 아침 루틴이 잘 정착했고, 이게 큰 힘이 되어줘서 좋다!&lt;br /&gt;저녁과 주말도 잘 활용하게 되면 뿌듯할 것 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스터디- 책 소프트웨어 아키텍처 101&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기술 블로그 포스팅&lt;/b&gt;&lt;br /&gt;한동안 기술 블로그를 멈춰왔기 때문에 루틴에 넣어야 할 것 같다.&lt;br /&gt;계속 꾸준히 포스팅하지 못해서 아쉬움이 있어서 모닝페이지에 자주 언급했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수면 패턴&lt;/b&gt;&lt;br /&gt;일찍 자고 일찍 일어날 땐 기쁘다.&lt;br /&gt;그런데 어떨 땐 잡생각으로 머리가 꽉 차서 거의 밤을 셀 정도로 잠을 못잔다.&lt;br /&gt;그런 날에는 아침 루틴을 할 의욕도 없어서 그냥 패스해버리게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이력서 갱신&lt;/b&gt;&lt;br /&gt;해야지, 해야지 하면서 아직까지도 안하고 있는 것ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;운동&lt;/b&gt;&lt;br /&gt;에너지량을 기르기 위해서라도 운동을 시작해야할 필요를 느꼈음&lt;br /&gt;필라테스 결제해서 8월부터는 운동 배우기 시작!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;맥북&lt;/b&gt;&lt;br /&gt;드디어 샀다. 어찌나 많이 언급했던지 ㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인간 관계와 자아성찰&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;회고&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;잠들기 전 일기로 하루에 있었던 일을 적자 =&amp;gt; 하루 마무리를 통해 기억력을 높이고, 잠들기 전 잡생각을 떨쳐내기 위함&lt;/li&gt;
&lt;li&gt;주말 아침도 평일과 동일한 루틴을 하되, 블로그 포스팅이나 이력서 갱신 같은 Task를 하는 날로 활용하자 =&amp;gt; 기술 블로그나 이력서 갱신처럼 계속 미뤄지거나 까먹는 일을 주말에 하기&lt;/li&gt;
&lt;li&gt;업무 중 답답하더라도 스트레스와 동요를 드러내고 표현하지 말자&lt;/li&gt;
&lt;li&gt;새로운 팀원들과 친해져보자! 새로운 사람들과 다양한 대화를 해보자!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_월간회고_7월.jpg&quot; data-origin-width=&quot;1836&quot; data-origin-height=&quot;3000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4yMIy/btsITC23lCd/8eOtXCKHrUNnEogLHTZknk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4yMIy/btsITC23lCd/8eOtXCKHrUNnEogLHTZknk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4yMIy/btsITC23lCd/8eOtXCKHrUNnEogLHTZknk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4yMIy%2FbtsITC23lCd%2F8eOtXCKHrUNnEogLHTZknk%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;1836&quot; height=&quot;3000&quot; data-filename=&quot;edited_월간회고_7월.jpg&quot; data-origin-width=&quot;1836&quot; data-origin-height=&quot;3000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다음 달 액션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 달은 회고와 다음달 액션이 섞여있다 :)&lt;br /&gt;위 회고 내용으로 대체!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다음달 목표 정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실수를 통해 배우자! 체크리스트 만들기!&lt;/p&gt;</description>
      <category>인생이란/회고&amp;amp;피드백시스템</category>
      <category>루틴</category>
      <category>루틴가꾸기</category>
      <category>성장시스템</category>
      <category>성장회고</category>
      <category>월간회고</category>
      <category>피드백루프</category>
      <category>회고</category>
      <author>iia_hannah</author>
      <guid isPermaLink="true">https://prohannah.tistory.com/265</guid>
      <comments>https://prohannah.tistory.com/265#entry265comment</comments>
      <pubDate>Sat, 3 Aug 2024 21:32:24 +0900</pubDate>
    </item>
  </channel>
</rss>