<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>uicheon.log</title>
        <link>https://velog.io/</link>
        <description>컨셉입니다~</description>
        <lastBuildDate>Sun, 25 Jan 2026 09:05:08 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>uicheon.log</title>
            <url>https://images.velog.io/images/petit-prince/profile/8b66d63b-5ad1-499a-b2e8-b7d42905cea1/KakaoTalk_20210904_181416071_01.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. uicheon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/petit-prince" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[부동산 시스템 개발 후기 - AI 100배 활용하는 법]]></title>
            <link>https://velog.io/@petit-prince/%EB%B6%80%EB%8F%99%EC%82%B0-%EB%A7%A4%EB%AC%BC-%EA%B4%80%EB%A6%AC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B0%9C%EB%B0%9C-%EC%8B%A4%ED%8C%A8%EA%B8%B0%EB%A1%9D-AI-100%EB%B0%B0-%ED%99%9C%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@petit-prince/%EB%B6%80%EB%8F%99%EC%82%B0-%EB%A7%A4%EB%AC%BC-%EA%B4%80%EB%A6%AC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B0%9C%EB%B0%9C-%EC%8B%A4%ED%8C%A8%EA%B8%B0%EB%A1%9D-AI-100%EB%B0%B0-%ED%99%9C%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Sun, 25 Jan 2026 09:05:08 GMT</pubDate>
            <description><![CDATA[<h1 id="1-소개">1. 소개</h1>
<p>안녕하세요. 저는 4년차 개발자 양준혁입니다.</p>
<p>이 프로젝트를 시작하게 된 계기는 부모님이 부동산을 하시는데, 엑셀말고 &#39;웹 페이지로 매물을 관리하고 싶다 하셔서&#39;입니다.</p>
<p>요구사항은 간단했습니다.</p>
<blockquote>
<ul>
<li>매물 관리<ul>
<li>공개/비공개 매물 관리 기능</li>
<li>지도 검색</li>
<li>매물 정보(가격, 위치 등) 입력</li>
</ul>
</li>
<li>문의 기능</li>
</ul>
</blockquote>
<p>프로젝트는 <strong>3개월</strong> 걸렸습니다. (25년 10월~ing)</p>
<p>아래는 프로젝트 결과입니다.
[부동산 웹 페이지] (<a href="http://eg4scssw4ow0og88o4ocw4gw.134.185.97.61.sslip.io/">http://eg4scssw4ow0og88o4ocw4gw.134.185.97.61.sslip.io/</a>)
<img src="https://velog.velcdn.com/images/petit-prince/post/6f4a5d73-5c0c-4294-8508-cd0bc3e18c15/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/8515e46a-7152-4af0-8c95-f46cbd8a33c2/image.png" alt=""></p>
<p>시작하기전 제 수준을 말씀드리면,</p>
<ul>
<li>(인프라) 토이 프로젝트 중 VM 여러대 설치/관리 경험</li>
<li>(FE) 리액트 문법을 익혀 To Do List 구현 가능 </li>
<li>(BE) 자바, 스프링 부트 활용 SI 프로젝트(CRUD) 개발</li>
</ul>
<p>프로젝트 목표는, 매물 등록할 때 <strong>귀찮지 않게</strong> 매물을 등록하는 것에 주안점을 뒀습니다.
엑셀은 매물 관리, 등록, 수정이 번거롭고, 한눈에 관리하기도 어려웠거든요.</p>
<h1 id="2-어려웠던-점과-개선-과정">2. 어려웠던 점과 개선 과정</h1>
<h2 id="21-프로젝트-초기-설정">2.1 프로젝트 초기 설정</h2>
<p>처음에는 <code>cursor</code> 같은 AI IDE에 <code>부동산 매물 관리 시스템</code> 구현해달라고 요청했습니다. </p>
<p>(기술 스택)</p>
<blockquote>
<ul>
<li>(Infra) vercel, supabase</li>
<li>(FE) React </li>
<li>(BE) Next.js</li>
</ul>
</blockquote>
<p>AI는 <strong>토지/빌딩/호수</strong> 등의 개념을 가지고 약 500줄이 넘는 스키마와 코드를 가져왔습니다.</p>
<p>이때 느꼈던 감정은 <strong>레거시 코드를 보는듯한 기분이었습니다.</strong></p>
<p><strong>무슨 기능인지 모르겠는 몇천줄의 코드에</strong> 당혹감이 어마어마 했습니다.</p>
<p>에라 모르겠다, <strong>끝까지 바이브 코딩으로 완성해보자!</strong> 하고 모든 터미널과 IDE를 켜서 &quot;이 기능 만들어줘&quot;, &quot;이 버그 수정해줘&quot; 프롬프트를 난사하기 시작했습니다.</p>
<p>(아래는 사용한 AI 도구)</p>
<ul>
<li><strong>(IDE)</strong> Cursor, Antigravity, Kiro, Webstorm</li>
<li><strong>(terminal)</strong> Claude code, Codex, Gemini cli</li>
<li><strong>(MCP)</strong> linear, context7, github</li>
</ul>
<p>모든 AI 연동 가능한 도구는 다 켜놓고, 폴더 구조를 아래와 같이 만들었습니다.</p>
<ul>
<li>claude-workspace</li>
<li>cursor-workspace</li>
<li>gemini-workspace</li>
<li>codex-workspace</li>
<li>xxxx-workspace</li>
</ul>
<p>이후, 엄청난 양의 토큰을 불태워가면서 다양한 요구사항과 버그를 수정하도록 지시했습니다.</p>
<p>또, AI에게 &quot;부동산 매물 관리 시스템에 필요한 기능 추천해줘&quot; 해서 Linear에 task를 할당시켰습니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/b979af12-282b-4569-ae70-4d672d4d11f8/image.png" alt=""></p>
<h2 id="2-2-위-방법이-실패한-이유">2-2. 위 방법이 실패한 이유</h2>
<p>11월 말 쯤, 프로젝트는 UI상으로 나쁘지 않아 보였습니다.</p>
<p>매물도 등록되기도 하고, 우리 고객님께 보여드리자 흡족해 하셨습니다.</p>
<p>그리고 실제 사용을 시작하자...
<img src="https://velog.velcdn.com/images/petit-prince/post/2b611a00-34cc-43e0-a2f4-71901b9feafb/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/7fad29dc-81ac-42ca-9385-77ed5ea54664/image.png" alt=""></p>
<p>사진 이외에도 꽤 많은 에러가 발생하기 시작했습니다.</p>
<p>더 이상 코드를 이해하기도 어려웠고, 믿었던 AI는 버그 수정에 실패했습니다.</p>
<p>*<em>프로젝트는 막다른 길에 도달했습니다. 명백히 실패했습니다.
*</em></p>
<p>제 나름 실패 이유를 정리해봤습니다.</p>
<ul>
<li><strong>(익숙하지 않은 언어/프레임워크)</strong><ul>
<li>애초에 FE/Next를 몰라 디버깅 어려움 (렌더링 등 life cycle 모름)</li>
<li>코드 구조, 레이어, 아키텍쳐 등 아는게 하나도 없음</li>
<li><blockquote>
<p>이 컴포넌트가 &quot;어느 디렉토리에 있어야하는지&quot; 모르겠음</p>
</blockquote>
</li>
</ul>
</li>
<li><strong>(도메인 지식 부족)</strong><ul>
<li>건축물 대장(표제부 등)에 어떤 데이터가 있는지 모름</li>
<li>기존 &quot;매물&quot;등록에 필요한 과정을 모름</li>
</ul>
</li>
<li><strong>(미흡한 설계)</strong>  <ul>
<li><strong>(외부 연동 실패)</strong> 
국가전산망 화재사태로 건축물 대장 API 에러 -&gt; 매물 등록 실패  </li>
<li><strong>(느림)</strong> 
vercel, supabase Free tier 이용으로 매 요청 응답에 2초이상 걸림
건축물 대장 API 응답도 느림  </li>
</ul>
</li>
<li><strong>(AI 한계)</strong> <ul>
<li><strong>(낮은 코드 품질)</strong> 
같은 로직의 코드가 <strong>각기 다른 파일에 12개 이상</strong> 구현된 적도 있음
(ex 평-&gt;제곱미터 변환 로직)</li>
<li><strong>(가장 중요)</strong> 더 이상 AI에게 고쳐달라해도 안고쳐짐</li>
<li><blockquote>
<p>AI에게 뭘 모르는지도 모르며 다시 버그 수정 등 위임</p>
</blockquote>
</li>
<li><blockquote>
<p>결과 확인 안함</p>
</blockquote>
</li>
<li><blockquote>
<p>무한 반복</p>
</blockquote>
</li>
<li><blockquote>
<p>토큰 수 부족 (+ linear, Github, MCP 연동)</p>
</blockquote>
</li>
<li>워크스페이스를 한 IDE에 키면 버벅이고, 여러 창에 열면 &#39;이게 어떤 workspace지..?&#39; 헷갈림</li>
</ul>
</li>
</ul>
<p>결론적으로 </p>
<ol>
<li>익숙하지 않은 언어/프레임워크 사용</li>
<li>도메인 지식 부족/ 미흡한 설계 </li>
<li>AI 활용법 미숙</li>
</ol>
<p>하여 실패했습니다.</p>
<p>결국 프로젝트를 거의 다시 제작했습니다.</p>
<h1 id="3-프로젝트가-성공하려면">3. 프로젝트가 성공하려면</h1>
<p>AI를 사용한 프로젝트를 성공시키려면 <strong>위를 정반대로 하면 됩니다.</strong></p>
<blockquote>
<p>어쩌면, &quot;AI를 사용하며&quot; 라는 말은 필요 없을지도 모르겠습니다.</p>
</blockquote>
<h2 id="3-1-익숙한-언어프레임워크-사용">3-1. 익숙한 언어+프레임워크 사용</h2>
<ul>
<li>적어도, 내가 디버깅할 수 있고, 작성한 코드를 이해할 정도의 언어/프레임워크를 사용하세요.</li>
<li>만약 그 마저도 시간이 부족하다면, AI 가 작성한 코드를 AI와 함께 이해하세요.<ul>
<li>ex) 왜 이 컴포넌트는 <code>feature</code> 하위에 있어야해?</li>
<li>ex) 왜 Form을 저장하는건 <code>store</code>면 안돼? </li>
</ul>
</li>
</ul>
<p>저는 가장 먼저 Next 백엔드를 삭제하고, Kotlin+Spring 기반 프로젝트로 변경했습니다. 20~30개 가량의 API와, 스키마 모두 변경하는 대 변경작업이었지만, 막상 시간은 일주일도 걸리지 않았습니다.</p>
<p><strong>사실 개발 시간이 오히려 줄었습니다.</strong></p>
<p>아래는 고민할 것도 없이 AI에게 구현해달라고 했습니다.</p>
<ul>
<li>GlobalExceptionHandler(RestcontrollerAdvice)</li>
<li>로깅 정책</li>
<li>Error/Exception 정책</li>
<li>Spring Security (Google OAuth, JWT, Tenant, Users...)</li>
<li>DDL/DML</li>
<li>Obersvability</li>
</ul>
<p>이미 제가 잘 아는 Best Practices들이 있기에, 구현한 작업을 <strong>확인(검증)</strong> 만 하면 됐습니다. </p>
<p>마음에 안들게 설계했다? 내 입맛대로 다시 바꿔달라 요청했습니다. </p>
<p>오버엔지니어링된 항목들은 과감히 삭제하고 실제 내가 <strong>이용할 기능</strong>에만 집중해서 작고, 집중되게 코드 구현이 가능했습니다.</p>
<blockquote>
<p>단, FE단은 여전히 아는게 없었습니다.
이를 위해 <strong>코드를 먼저 구현하지 않고</strong>  AI와 좋은 FE 설계 방안에 대해 물어본 뒤, 이를 구현해달라고 요청했습니다.
즉, <strong>설계에 대한 방향성</strong>을 제가 갖고 있도록 했습니다.</p>
</blockquote>
<h2 id="3-2-도메인-지식설계">3-2. 도메인 지식/설계</h2>
<h3 id="1-도메인-지식">1. 도메인 지식</h3>
<p>먼저, &#39;총괄 표제부&#39;, &#39;표제부&#39;, &#39;전유부&#39; 등 매물 등록에 사용되는 건축물대장에 어떤 항목이 들어있는지, 이 정보는 어떻게 사용되는지 파악했습니다.</p>
<p><strong>네이버 부동산</strong>의 매물을 각 매물 타입별로(아파트, 빌라, 단독, 사무실 등) 조회해본 뒤, &quot;기본 정보(매물 정보)&quot;, &quot;단지 정보&quot;, &quot;건축물 정보&quot;로 나누어진 것을 확인했습니다.</p>
<p>이를 매핑해보면 건축물 대장에서 어떤 정보를 얻을 수 있는지 알 수 있습니다.</p>
<table>
<thead>
<tr>
<th>정보명</th>
<th>건축물 대장</th>
</tr>
</thead>
<tbody><tr>
<td>기본 정보(매물 정보)</td>
<td>사용자 입력 + 전유부 + 전유면적</td>
</tr>
<tr>
<td>단지 정보</td>
<td>총괄 표제부</td>
</tr>
<tr>
<td>건축물 정보</td>
<td>표제부</td>
</tr>
</tbody></table>
<p>아래는 직접 작성한 도표입니다. 
<img src="https://velog.velcdn.com/images/petit-prince/post/5dac2cd3-7a32-4b7c-8fb7-9f15c5647139/image.png" alt=""></p>
<p>또한, 기존에 사용하던 <code>부동산 PoS</code> 등에서 매물 등록하는 과정등도 확인했습니다.</p>
<p>이후 저만의 사용할 과정도 설계했습니다. (사진에서는 많이 삭제됐습니다.)
<img src="https://velog.velcdn.com/images/petit-prince/post/5f14fb07-879c-403d-bc85-6a430492c752/image.jpeg" alt=""></p>
<h3 id="2-데이터-연동-설계">2. 데이터 연동 설계</h3>
<p>건물과 호(세대)의 정보를 얻으려면 국토교통부의 건축물 대장을 사용해야합니다.</p>
<p>국토교통부의 건축물대장은 2가지 방식을 제공합니다.</p>
<ul>
<li>Open API 형식</li>
<li>대용량 벌크 형식 (csv)</li>
</ul>
<p>처음에는 Open API 형식을 사용했습니다. 
데이터 마이그레이션도 필요없고, API 키로 빠르게 이용 가능했습니다.</p>
<p>그리고 ..
<img src="https://velog.velcdn.com/images/petit-prince/post/5aef948c-ea4c-4428-8d90-2596105d6c45/image.png" alt=""></p>
<p>적어도 내 서비스가 <strong>&quot;아.. 오늘은 매물 등록 안되겠네요. 국가정보원 연동이 안돼서요&quot;</strong> 하는 변명을 하긴 싫었기에, 대용량 벌크 방식으로 건축물 대장 연동 방식을 변경했습니다.</p>
<p>진행 중 가장 큰 문제는 데이터가 꽤 컸다는 것입니다.
10개의 건축물 대장 모두 합쳐 100Gb가 넘었고, 이를 DB에 담아야했습니다. </p>
<p>배치 시스템도 구상했지만, 우선순위를 고려하여 step 별로 중단 가능한 스크립트까지만 (AI가) 개발했습니다.</p>
<h3 id="3-인프라-설계">3. 인프라 설계</h3>
<p>기존 인프라는 vercel, supabase을 사용하고 있었으나 한계가 극명했습니다.
화면 전환마다 2초 이상이 걸렸고, 이제는 100GB 넘는 데이터를 수용하기 까지 해야했습니다.</p>
<p>무료인 OCI의 VM(4vCPU, 24GB Ram)을 이용하기로 했습니다.</p>
<p>그렇지만 Vercel의 GitOps는 너무나도 편했습니다. </p>
<p>GitOps는 꼭 필요하다고 판단해 <strong>Coolify</strong>라는 DevOps 도구를 도입했습니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/3371e48f-d6c1-48dd-9c01-2798ae23c9c9/image.png" alt=""></p>
<p>(Coolify 기능)</p>
<ul>
<li><strong>Github Repository 연동 + GitOps 기능</strong></li>
<li>서비스 (Postgres, clickhouse 등) 배포 기능</li>
<li>네트워크, 단말, 터미널 관리</li>
</ul>
<blockquote>
<p>managed K8s를 사용하고, ArgoCD 구축하는 방향도 생각해봤습니다.
경험상 배보다 배꼽이 더 컸었기에, Coolify GitOps 기능 하나로도 충분하다 생각했습니다.</p>
</blockquote>
<p>이후 건축물대장 데이터를 이관하고, 새로운 FE와 BE를 배포했습니다.
건축물대장 25년 11월 기준 데이터로 운영 가능하고, 반응 속도도 크게 개선됐습니다.
(평균 2<del>3s =&gt; 100</del>200ms)</p>
<h2 id="3-3-ai-활용법">3-3. AI 활용법</h2>
<p>먼저, AI 구독하세요! 그리고 고수의 설정을 베껴오시면 됩니다! </p>
<ul>
<li><a href="https://www.linkedin.com/posts/jyoung105_%EC%96%B4%EC%A0%9C-%EA%B3%B5%EA%B0%9C%EB%90%9C-%ED%81%B4%EB%A1%9C%EB%93%9C-%EC%A0%84%EB%AC%B8%EA%B0%80%EC%9D%98-%ED%81%B4%EB%A1%9C%EB%93%9C-%EC%BD%94%EB%93%9C-%EC%99%84%EC%A0%84%ED%8C%90-%EC%84%B8%ED%8C%85-%EC%A0%80%EB%8F%84-%EB%8B%A4-%EB%B0%94%EA%BF%A8%EC%8A%B5%EB%8B%88%EB%8B%A4-activity-7419142685543882752-KXud/">앤트로픽 해커톤 우승자가 어제 공개한 클로드 코드 세팅 가이드</a></li>
<li><a href="https://github.com/code-yeongyu/oh-my-opencode?tab=readme-ov-file">처음부터 끝까지 다 알아서 해주는 oh-my-opencode</a></li>
</ul>
<p>또한, AI 마다 잘하는게 달라서, 이걸 <strong>몸소 체험하는게 중요합니다.</strong></p>
<ul>
<li>(좁은 범위) 
ex) 한 컴포넌트 수정 =&gt; <strong>Antigravity</strong></li>
<li>(중간 범위/범용) 
ex) CRUD 생성 =&gt; <strong>Claude Code</strong></li>
<li>(복잡한 디버깅, 대형 리팩토링) </li>
<li><em>Codex*</em></li>
</ul>
<p>그 이후로 자신만의 룰을 만들면 좋습니다.</p>
<ul>
<li><strong>AI가 실수를 반복하지 않게 하는게 중요합니다.</strong><ul>
<li>skills, hooks 등을 사용하여 lint/build 자동화 하기</li>
<li>실수한 항목이 있다면 MD 파일에 정리해서 &quot;다시 반복하지 않게 하고&quot; 이를 항상 최신화 하는 hooks 걸어 놓기</li>
</ul>
</li>
<li>큰 범위의 개발/수정은 plan 모드 이용하기.<ul>
<li>(어렵다면) 애초부터 code가 아닌, GPT 등과 chat으로  설계 후 구현하기</li>
</ul>
</li>
<li>필요 없는 MCP는 끄기. 토큰 아까움 (특히 <strong>Github</strong>)</li>
</ul>
<p>또한, git worktree 기능과 함께 <a href="https://www.agentastic.dev/">Agentastic.dev</a> 도 유용합니다.</p>
<blockquote>
<p>git worktree
브랜치 + 다른 디렉토리로 소스를 copy해주는 기능</p>
</blockquote>
<ul>
<li>한 프로젝트에서 다양한 요청을 보낼때, 항상 git worktree + agentastic.dev를 이용</li>
<li>ex) 부동산 매물 등록 버그 수정 worktree, 지도 버그 수정 worktree</li>
</ul>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/76f9e4cb-ce5c-46f4-83fb-8cf905a0014e/image.gif" alt=""></p>
<p>이렇게하면, 위의 workspace directory로 분리한것보다 훨씬 직관적이고 빠르게 수정 가능합니다.</p>
<h1 id="4-느낀점">4. 느낀점</h1>
<ul>
<li>돈 벌기 어렵다.</li>
<li>혼자서 프로젝트의 E2E를 충분히 할 수 있는 시대가 왔다.</li>
<li><strong>쓰레기를 넣으면 쓰레기가 나온다.</strong></li>
</ul>
<p>사실 이 프로젝트를 시작한 것도 에릭 슈미트의 다음 영상 때문이었습니다.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/1d202027-e0e6-4c12-819c-a1929a975d95/image.gif" alt=""></p>
<p>최근 친척이 개발자 진로에 대해 물어봤을 때 대답했던건 
&quot;나도 모른다&quot; 였습니다. </p>
<p>AI덕분에 나의(개발자) 본질이 바뀔 때가 왔습니다.</p>
<p>예전에 나는 구현력이 약해서 불가능했던 일들이 이젠 가능합니다.
(저는 대부분의 코딩테스트에 떨어졌습니다.)</p>
<p>이런 사이드 프로젝트는 몇 개월 씩이나 팀과 함께 만들었어야 합니다.</p>
<p>사실, 너무나도 재밌고 흥분되는 일이기도 합니다.</p>
<p>나는 이제 백엔드 개발자가 아니라, 스팀 게임을 만들 수도 있고, 윈도우 프로그램을 만들 수도 있고, 닷넷 개발자도 될 수 있습니다.</p>
<p>적어도 매일 파도를 탈 수 있음에 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[4년차 개발자의 AI 활용법]]></title>
            <link>https://velog.io/@petit-prince/4%EB%85%84%EC%B0%A8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-AI-%ED%99%9C%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@petit-prince/4%EB%85%84%EC%B0%A8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-AI-%ED%99%9C%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Thu, 09 Oct 2025 14:38:12 GMT</pubDate>
            <description><![CDATA[<h1 id="개발자-필수-ai-활용-습관">개발자 필수 AI 활용 습관</h1>
<p>1년간 실무 AI 활용 노하우를 공유합니다.</p>
<p>저는 이제 AI가 그려주는 다이어그램이 없다면, 업무를 진행하지 못합니다. 😅</p>
<p>AI에게 어떤 요청을 자주하시나요? 🤔</p>
<h2 id="✅-효과적인-활용법">✅ 효과적인 활용법</h2>
<h3 id="1-시퀀스-다이어그램-생성">1. 시퀀스 다이어그램 생성</h3>
<ul>
<li>복잡한 시스템 연동, 레거시 문서 부재 시 필수</li>
<li>AI가 놓친 edge case와 주의사항까지 제안</li>
<li>생성된 다이어그램을 기술 문서에 즉시 활용</li>
</ul>
<h3 id="2-api-변경사항-대조표">2. API 변경사항 대조표</h3>
<ul>
<li>신규/변경/유지 항목을 색상으로 구분</li>
<li>리뷰 시간 대폭 단축</li>
</ul>
<h3 id="3-보일러플레이트-코드">3. 보일러플레이트 코드</h3>
<ul>
<li>단순 CRUD는 ERD + 기존 코드베이스 제공</li>
<li>일관된 패턴 유지 가능 (.cursorrule, GEMINI.md 등)</li>
<li>단, 코드 리뷰 필수</li>
</ul>
<h3 id="4-api-규격서-생성-자동화">4. API 규격서 생성 자동화</h3>
<ul>
<li>.http 파일, Postman Collection 작성</li>
</ul>
<h2 id="❌-피해야-할-활용">❌ 피해야 할 활용</h2>
<h3 id="1-복잡한-통합-로직-일괄-요청">1. 복잡한 통합 로직 일괄 요청</h3>
<ul>
<li>예: &quot;Spring Security OAuth 구현해줘&quot;</li>
<li>원리 이해 없이 생성된 코드로 할루시네이션 무한 반복</li>
<li>→ 직접 이해하여 구현하는게 &quot;더 빠름&quot;</li>
</ul>
<h3 id="2-단순-리팩토링">2. 단순 리팩토링</h3>
<ul>
<li>변수명/메서드명 변경은 IDE 기능이 압도적</li>
<li>AI 사용 시 컴파일 + 테스트 검증 필수</li>
</ul>
<h3 id="3-대규모-마이그레이션">3. 대규모 마이그레이션</h3>
<ul>
<li>예: &quot;JPA → MyBatis 전환&quot;</li>
<li>파일마다 다른 구현 방식 적용</li>
<li>결국 전체 수동 검증 필요 → 투입 대비 효과 낮음</li>
</ul>
<h3 id="4-테스트-자동화-스크립트">4. 테스트 자동화 스크립트</h3>
<ul>
<li>Playwright(mcp), curl 기반</li>
<li>관리가 안되고, 추적도 불가능, 결과도 못미더운 경우 많음</li>
<li>단순 로그인도 실패하는 경우 많음..^^</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[바이브 코딩으로 네이버 페이 부동산 개선하기]]></title>
            <link>https://velog.io/@petit-prince/%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9%EC%9C%BC%EB%A1%9C-%EB%84%A4%EC%9D%B4%EB%B2%84-%ED%8E%98%EC%9D%B4-%EB%B6%80%EB%8F%99%EC%82%B0-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@petit-prince/%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9%EC%9C%BC%EB%A1%9C-%EB%84%A4%EC%9D%B4%EB%B2%84-%ED%8E%98%EC%9D%B4-%EB%B6%80%EB%8F%99%EC%82%B0-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 25 Apr 2025 10:23:47 GMT</pubDate>
            <description><![CDATA[<h1 id="1-네이버-페이-부동산-면적-정렬은-공급-면적-기준이다">1. 네이버 페이 부동산 면적 정렬은 공급 면적 기준이다.</h1>
<p>안녕하세요. 사실상 부동산 솔루션은 <strong>네이버 페이 부동산만</strong> 이용하는 우이천입니다.</p>
<p>저는 부동산 검색을 할 때 핸드폰보다는 화면이 큰 <strong>웹 검색을 선호</strong>합니다. </p>
<p>그리고 UI도 네이버 페이 부동산이 익숙하여 편한데요.</p>
<p>새로 이사갈 집(빌라/원룸/투룸)을 알아보는데, <strong>실평수가 너무 작진 않았으면 좋겠다</strong> 하며 검색 중이었습니다.</p>
<p>안타깝게도 네이버 부동산은 <strong>전용 면적 기준이 아닌, 공급 면적 기준으로 정렬</strong>하고 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/d7b0acc8-734d-4583-a00b-c89c3319fc64/image.png" alt=""></p>
<p>빌라나 원룸 같은 경우 <strong>공급 면적은 터무니 없이 큰 경우가 종종 있습니다.</strong></p>
<p>공급대비 실평수는 굉장히 작은 매물들이 리스트에 많이 보이기에,
&quot;<strong>실평수로 정렬하고 싶다</strong>&quot;라는 생각이 강하게 들었습니다.</p>
<h1 id="2-어떻게-할까">2. 어떻게 할까</h1>
<blockquote>
<p><strong>크롬 확장자</strong>(chrome extension)를 이용해 실평수를 파싱하고, 아이템만 HTML로 다시 로딩해주면 되겠다!</p>
</blockquote>
<p>하지만 저는 <strong>크롬 확장 프로그램을 개발해본 적도 없고</strong> 시간은 부족한 직장인 입니다.😭</p>
<p>어쩔 수 없이, claude님에게 코딩을 요청드렸고, 결과는 아래와 같습니다.</p>
<h1 id="3-실평수-정렬-데모">3. 실평수 정렬 데모</h1>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/ea8a6650-069b-445a-8698-58f778e0048f/image.gif" alt=""></p>
<h1 id="4-느낀점">4. 느낀점</h1>
<p>개발에 무료 claude를 사용하여 약 2시간 걸렸습니다.</p>
<p>날이 가면 갈수록 <strong>개인이 갖는 능력</strong>이 올라가고 있다는 걸 체감합니다.</p>
<p>사내에서도 백엔드보다는 더 넓은 범위에서 기여하고 있다는 점도 내심 뿌듯하기도 합니다.</p>
<p>인상 깊게 읽었던 <a href="https://yozm.wishket.com/magazine/detail/3027/">소프트웨어 개발의 역사</a> 에서는 기술과 환경이 바뀌면서 (<em>현재는 개발자라고 부르는</em>) 개발자들의 역할과 일이 변화하는 것을 알 수 있습니다.</p>
<p>이처럼 AI 시대에는 <strong>내 역할이 어떻게 바뀔까</strong>, 잘 적응하려나하며 살고 있습니다.</p>
<h1 id="5-사용법">5. 사용법</h1>
<p><a href="https://chromewebstore.google.com/detail/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EB%8F%99%EC%82%B0-%EC%8B%A4%EB%A9%B4%EC%A0%81-%EC%A0%95%EB%A0%AC/aflpbdilmmdnkggpbgaokcigfjcdfbpi">크롬 확장자</a>를 이용하시면 됩니다! </p>
<p>코드는 <a href="https://github.com/KYankee6/naver-land-real-pyungso/tree/master">깃허브</a>에 공유되어 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Raycast: 생산성 개선 경험 공유 🚀]]></title>
            <link>https://velog.io/@petit-prince/Raycast-%ED%95%84%EC%88%98-%EC%84%A4%EC%A0%95-%EA%B3%B5%EC%9C%A0</link>
            <guid>https://velog.io/@petit-prince/Raycast-%ED%95%84%EC%88%98-%EC%84%A4%EC%A0%95-%EA%B3%B5%EC%9C%A0</guid>
            <pubDate>Sun, 12 Jan 2025 14:59:56 GMT</pubDate>
            <description><![CDATA[<p>최근 토비 님이 링크드인에 <strong><a href="https://www.linkedin.com/posts/tobyilee_%EC%83%9D%EC%82%B0%EC%84%B1%EC%97%90-%EC%A7%84%EC%8B%AC%EC%9D%B8-%EC%9E%90%EC%9D%98-raycast-%EC%84%B8%ED%8C%85-%EC%97%BF%EB%B3%B4%EA%B8%B0-for-macos-activity-7251737230250053633-iJyw/?utm_source=share&amp;utm_medium=member_desktop">Raycast를 칭찬하는 글</a></strong>을 유의 깊게 읽었는데요!</p>
<p>직접 Raycast를 사용하며 느낀 생산성의 극대화 경험을 공유해보려 합니다!</p>
<p>또한, 제가 사용 중인 단축키 설정 팁도 소개드립니다. 😉</p>
<h1 id="1-raycast란">1. Raycast란?</h1>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/12862390-bce4-4a16-bad4-849d64807259/image.png" alt="">
<a href="https://www.raycast.com/">Raycast 홈페이지</a>에서 소개하듯, <strong>&quot;모든 것의 단축키&quot;</strong>를 제공하는 강력한 도구입니다.</p>
<p>맥 기본 Spotlight도 유용하지만, Raycast는 <strong>한 단계 더 나아간 기능들을</strong> 실행할 수 있습니다.</p>
<h1 id="2-검색-quicklinks">2. 검색 (Quicklinks)</h1>
<h2 id="기존-검색-방식의-문제">기존 검색 방식의 문제</h2>
<p>에러 검색 시 보통 이런 과정을 거쳤습니다</p>
<ol>
<li>에러 메시지 드래그 후 복사 (⌘+C)</li>
<li>브라우저로 전환 (⌘+Tab)</li>
<li>검색창 활성화 (⌘+L)</li>
<li>붙여넣기 후 검색 (⌘+V → Enter)</li>
</ol>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/814c7cac-448f-443b-bef3-5f7e6d1b9718/image.gif" alt=""></p>
<h2 id="raycast로-더-빠르게">Raycast로 더 빠르게</h2>
<p>Raycast를 사용하면 위 과정을 <strong>한 번에 해결할 수 있습니다.</strong>
단축키만 누르면 원하는 검색엔진으로 바로 연결됩니다.</p>
<ul>
<li><p>에러를 바로 구글 검색 (클립보드 검색)
<img src="https://velog.velcdn.com/images/petit-prince/post/b3216dc9-874c-4688-9128-89a4a3724d40/image.gif" alt=""></p>
</li>
<li><p>ChatGPT에서 짧은 답변 바로 얻기 (<code>⌃+G</code>)
<img src="https://velog.velcdn.com/images/petit-prince/post/624930df-9e4e-4b9b-b578-3a6a8ba0620f/image.gif" alt=""></p>
</li>
<li><p>네이버 지도에서 <del>음식점</del> 찾기 (<code>snm</code>)
<img src="https://velog.velcdn.com/images/petit-prince/post/556123e9-9299-43cb-bfb7-ed79d4dca241/image.gif" alt=""></p>
</li>
<li><p>구글 검색과 자주 사용하는 검색은 단축키로도 등록하여 바로 실행 가능합니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/6a44b796-2b88-44b4-a9dd-581cccccb8bf/image.gif" alt=""></p>
</li>
</ul>
<p>저는 검색 관련 기능들은 다음과 같이 단축어를 등록했습니다.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/3357ded1-34b5-4275-8d5a-74fcb92a9880/image.png" alt=""></p>
<h1 id="3-프로그램-실행">3. 프로그램 실행</h1>
<h2 id="기존-방식">기존 방식</h2>
<p>기존에는 Spotlight로 프로그램을 검색해 실행했습니다.
하지만 키 입력이 많아 비효율적이었습니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/72e795e9-90e4-453a-a5f5-24b1779af04a/image.png" alt=""></p>
<p>하지만 이제는 아래와 같이 간단한 단축어로, 원하는 프로그램을 한번에 구동합니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/0cf73ca6-98af-4678-b967-ccb1c7a95f54/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/521309cd-9807-4062-983d-73e38c1425b8/image.gif" alt=""></p>
<p>또한 저는 Security Group 설정을 위해 내 아이피 검색을 정말 자주하는데요!</p>
<p>IP 플러그인으로 한번에 아이피 주소를 알아오기도 합니다! (<code>^+.</code>)
<img src="https://velog.velcdn.com/images/petit-prince/post/64cf71b3-0632-4e86-a854-6d9c6332eb80/image.gif" alt=""></p>
<h1 id="4-애플-스크립트와-자동화">4. 애플 스크립트와 자동화</h1>
<h2 id="문제-vpn-연결">문제: VPN 연결</h2>
<p>사내망 이용을 위해 VPN을 사용하는 경우, 기존에는 마우스를 사용했습니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/6741224b-a7b6-4489-a94b-c23320ec30ca/image.gif" alt=""></p>
<h2 id="해결-raycast--애플-스크립트">해결: Raycast + 애플 스크립트</h2>
<p>Raycast와 애플 스크립트를 결합해 VPN 연결/해제를 자동화할 수 있습니다.
단축키만 누르면 자동으로 VPN이 연결됩니다.</p>
<p>해당 <a href="https://gist.github.com/tmanternach/cbd4c213eab8569e38d6cd021b6255e5">깃허브</a>를 약간 변형해 저만의 스크립트를 만들고, Global Protect VPN을 자동 연결/해제하는 스크립트를 만들어봤습니다.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/1b762643-1074-48eb-9a67-2f468854fd98/image.gif" alt=""></p>
<p>마우스 없이, Raycast를 실행 뒤, <code>vpn</code> 축약어만 입력하면 됩니다!</p>
<p>아래는 제가 위 깃허브로부터 직접 커스텀한 애플 스크립트입니다!</p>
<pre><code class="language-as">(*
Toggle GlobalProtect VPN with AppleScript
Tested using macOS Sequoia 15.1.1 and GlobalProtect version 6.2.3-270
Written by Trevor Manternach, August 2023.
*)

tell application &quot;System Events&quot; to tell process &quot;GlobalProtect&quot;
    click menu bar item 1 of menu bar 2
    delay 0.2
    set statusText to name of static text 1 of window 1
    if statusText is &quot;연결되지 않음&quot; then
        # GlobalProtect is disconnected, so let&#39;s connect
        click button &quot;연결&quot; of window 1
        set entireContents to entire contents of window 1
    else if statusText is &quot;연결됨&quot; then
        delay 0.2
        # GlobalProtect is connected, so let&#39;s disconnect
        set windowText to entire contents of window 1
        repeat with theItem in windowText
            if (name of theItem contains &quot;연결 해제&quot;) then
                click theItem
            end if
        end repeat
    end if
    click menu bar item 1 of menu bar 2
end tell</code></pre>
<h1 id="5-플러그인-추천">5. 플러그인 추천</h1>
<p>Raycast 플러그인을 설치 안할 수 없죠!
제가 소개드린 기능들도 일부는 플러그인인데요!
제가 유용하게 사용하는 플러그인은 다음과 같습니다.</p>
<h2 id="검색-관련">검색 관련</h2>
<ul>
<li><a href="https://github.com/raycast/extensions/blob/5a9f23db0881cd8291df2e8dbb2dc3e9c8489a30/extensions/google-maps-search/README.md">구글 지도 검색</a>, (<code>sgm</code>, <code>⇧+M</code>)</li>
<li><a href="https://github.com/raycast/extensions/blob/330c904473dd718b3c93d0a5acb16885c4f9a201/extensions/google-search/README.md">빠른 구글 검색</a>, (<code>⇧+⌘+L</code>)</li>
<li>QuickLinks, 빠른 검색<ul>
<li>유튜브, (<code>syt</code>)</li>
<li>구글 검색 (<code>sg</code>)</li>
<li>네이버 검색 (<code>sn</code>)</li>
<li>네이버 지도 검색 (<code>snm</code>)</li>
</ul>
</li>
<li><strong><a href="https://github.com/raycast/extensions/blob/0894e9e6bf60897313a7eea0ba42aa7106e57c73/extensions/imessage-2fa/README.md">View 2FA code:</a></strong> (<code>2fa</code>)<ul>
<li>iMessage로 온 2차 인증 비밀번호 저장</li>
</ul>
</li>
</ul>
<h2 id="시스템-관리">시스템 관리</h2>
<ul>
<li><a href="https://www.raycast.com/core-features/clipboard-history">클립보드 히스토리</a>, (<code>⇧+⌘+C</code>)
<img src="https://velog.velcdn.com/images/petit-prince/post/10aa426a-e1e6-4214-89ad-e076fde3ec14/image.png" alt=""><ul>
<li>실행중인 프로그램 종료: <a href="https://www.raycast.com/rolandleth/kill-process">Kill Proccess</a>, (<code>kill</code>)
<img src="https://velog.velcdn.com/images/petit-prince/post/b84bac46-e314-4025-bea7-131b039be56c/image.png" alt=""></li>
<li>포트 검색: <a href="https://www.raycast.com/lucaschultz/port-manager">Port Manager</a>, (<code>port</code>)</li>
</ul>
</li>
<li>바로 종료도 가능
<img src="https://velog.velcdn.com/images/petit-prince/post/58407a75-7f1a-4ec4-9103-7052d187cdf8/image.png" alt=""></li>
</ul>
<ul>
<li><a href="https://www.raycast.com/abielzulio/chatgpt">ChatGPT 빠른 검색</a>, (<code>gpt</code>, <code>^+g</code>)</li>
</ul>
<h1 id="6-설정-꿀팁">6. 설정 꿀팁</h1>
<ul>
<li>Option(⌥): 단순 실행인 경우 (예: ⌥+O → Obsidian 실행)
<img src="https://velog.velcdn.com/images/petit-prince/post/58d5aefc-36fe-4871-84d5-5e557488ab50/image.png" alt=""></li>
<li>Control(⌃): 실행 + 추가 작업 경우 (예: ⌃+G → GPT 검색)</li>
<li>자주 사용하는 기능은 단축키, 그렇지 않은 기능은 <strong>단축어(alias)</strong>로 관리</li>
</ul>
<h1 id="7-마치며-🌃">7. 마치며 🌃</h1>
<p>사실 처음에는 설정하는데 시간도 오래걸리고, 굳이 이렇게 해야하나? 생각도 했습니다. </p>
<p>하지만 나중 가면서 <strong>점점 편리해지고, 쓸모 없는 작업이 없어지더라구요!</strong></p>
<p>어느새 Raycast를 물 흐르듯 활용하는 저를 볼 수 있었습니다!</p>
<p>여러분도 사용 중인 설정이나 플러그인이 있다면 댓글로 공유해주세요! 😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 2024년 회고]]></title>
            <link>https://velog.io/@petit-prince/%ED%9A%8C%EA%B3%A0-2024%EB%85%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@petit-prince/%ED%9A%8C%EA%B3%A0-2024%EB%85%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 07 Jan 2025 17:36:24 GMT</pubDate>
            <description><![CDATA[<h2 id="1-네트워크팀에서-개발팀으로">1. 네트워크팀에서 개발팀으로</h2>
<p>2023년 12월, 네트워크 연구/개발 팀에서 개발 부서로 이동을 결심했습니다.
당시에는 네트워크 부서에서 백엔드 개발자로 성장하기 어렵다고 판단했기 때문입니다.</p>
<blockquote>
<p>기존 부서는 안정적이며, 기술적으로나, <strong>사람으로나</strong> 배울 점이 많은 동료분들이 계셨습니다.
그에 반해 개발 부서로 간다면, 어떤 팀으로 배정될지, 무엇을 개발하게 될지 모르는 상황이었습니다.
새로운 도전을 앞두고 설렘과 두려움이 교차했습니다.
<a href="https://jojoldu.tistory.com/435">이동욱님 블로그</a>에서 본 만화가 큰 도움을 줬습니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/3d616d31-69a6-4f6f-86d5-29c3cb063fe1/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><em>왜 그토록 백엔드 개발자가 되고 싶어 했을까? 불안과 자격지심이었을까?</em></p>
</blockquote>
<p>지금 돌이켜보면, <strong>네트워크/인프라 지식을 더 쌓지 못한 것이 아쉽습니다</strong>.</p>
<p>그때 배운 인프라 지식으로 서버 문제를 파악하거나 관련 업무를 지원할 기회가 많았습니다.</p>
<h2 id="2-개발팀에서의-첫걸음과-깨달음">2. 개발팀에서의 첫걸음과 깨달음</h2>
<p>새로운 팀에서는 모두가 백엔드 개발자였고, 저는 <strong>개발 지식이 전무한 초짜</strong>였습니다. <del>(김영한님의 JPA 강의 들으면 되겠지 생각했습니다.)</del></p>
<p>개발팀에서는 처음으로 <code>Kafka</code>, <code>Redis</code> 등 생소한 기술을 접했습니다.
<code>MySQL</code> 같은 기본 DB도 제대로 알고 있지 않았습니다.</p>
<p>처음엔 이렇게 생각했습니다.</p>
<p><em>&quot;내가 개발 부서에 늦게 와서 이런 기술들을 모르는 거야.&quot;</em></p>
<p>하지만 팀원들을 보며 깨달았습니다.</p>
<p>팀원분들은 <strong>필요한 기술을 스스로 학습하고, 도입을 설득하며 프로젝트를 이끌어 갔습니다.</strong></p>
<p>그제야 제 문제를 직면하게 되었습니다.</p>
<p>그동안 <strong>&#39;좋은 환경에 가기만 하면 성장할 것&#39;</strong> 이라는 착각을 하고 있었습니다.</p>
<blockquote>
<p>뛰어난 동료분들 덕분에 깨달음을 얻었습니다.
<strong>영감을 주는 동료</strong>와 함께 일한다는 것은 정말 가슴 설레는 경험입니다.</p>
</blockquote>
<blockquote>
<p>단순히 좋은 환경을 기다리는 사람이 아니라,
스스로 <strong>환경을 만들어가는 개발자가 되고 싶다</strong>는 목표를 세우게 되었습니다.</p>
</blockquote>
<h2 id="3-光明">3. 光明</h2>
<p>프로젝트 준비를 마치고 판교 사옥을 떠나 개발을 시작했습니다.
그곳의 하늘은 너무 아름다웠지만, <strong>밤을 보는 날이 더 많았습니다.</strong></p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/petit-prince/post/70da29cf-7092-461c-af83-e576581c327d/image.jpeg" alt=""></th>
<th><img src="https://velog.velcdn.com/images/petit-prince/post/6f4bb088-05c6-452f-bc55-5859283deebe/image.jpeg" alt=""></th>
</tr>
</thead>
</table>
<p>저는 출퇴근 시간(왕복 약 3시간)을 줄이고자 단기 월세를 구했습니다.
덕분에 개인 공부, 아침 운동, 사이드 프로젝트에 시간을 더 쓸 수 있었습니다.</p>
<h3 id="프로젝트-수행">프로젝트 수행</h3>
<p>모르는 것이 너무 많았지만, 부족한 대로 부딪혔습니다.
<strong>(아래는 주요 수행 업무입니다)</strong></p>
<ul>
<li>개발 서버 설치 (데스크탑 14대 이상, 스위치, UPS 등)</li>
<li>개발 인프라 구축: helm, k3s, redis, PostgreSQL, Kafka, RabbitMQ, InfluxDB, ArgoCD, OpenVPN 등</li>
<li>알림 전송 모듈 개발 및 설계 (SMS, Email, Push, Web)</li>
<li>Kubernetes 매니페스트 관리(PV, 서비스, 디플로이 등)</li>
<li>CI/CD 파이프라인 관리 (Jenkins)</li>
<li>개발 산출물 작성<ul>
<li>화면 기획서, 시퀀스 다이어그램, ERD, API 문서 등</li>
</ul>
</li>
<li>부하 테스트 및 소스 코드 정적 검증</li>
<li>On-Prem k3s → Azure AKS 기반 이관</li>
</ul>
<p>내가 조금이라도 도움이 되기를 바라며, 퇴근 후와 주말에도 쿠버네티스를 비롯한 기술들을 공부했습니다.</p>
<p>또한, 기술 지식만큼 중요한 것은 <strong>문제를 정의하고 소통하는 능력</strong>임을 깨달았습니다.</p>
<p>기술뿐 아니라 <strong>원활한 소통을 위한 방법도 몰랐던</strong> 저를 돌아보게 됐습니다.</p>
<blockquote>
<p>이후, 다양한 세미나에 참석하며 <strong>원활한 소통 방법</strong>뿐만 아니라 <strong>개발</strong>에 대해 배우고 고민했습니다.
먼저 겪은 선배들의 조언이 많은 도움이 됐습니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/72c65943-c185-4d25-a5d7-e9cdc2f23a6a/image.png" alt=""></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/73776a4d-2bcb-4b2c-b05f-e11fe365a9f3/image.webp" alt=""></p>
<h2 id="4-항해">4. 항해</h2>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/2649eebc-af21-4026-b0b2-d4885c7c9d14/image.webp" alt=""></p>
<p>폭풍처럼 바쁘게 지나간 프로젝트를 끝내고, <a href="https://hanghae99.spartacodingclub.kr/plus/be?utm_source=google&amp;utm_medium=bs&amp;utm_campaign=hhplus&amp;utm_content=brand&amp;utm_term=%ED%95%AD%ED%95%B4%ED%94%8C%EB%9F%AC%EC%8A%A4%EB%B0%B1%EC%97%94%EB%93%9C&amp;gcl_keyword=%ED%95%AD%ED%95%B4%ED%94%8C%EB%9F%AC%EC%8A%A4%EB%B0%B1%EC%97%94%EB%93%9C&amp;gcl_network=g&amp;gad_source=1&amp;gclid=Cj0KCQiAvvO7BhC-ARIsAGFyToV0t_Rw6j0gzNidb4yWDnA-QKDkZRZZppb4il-P4AZZSXidssxJl-EaAsKVEALw_wcB">항해 플러스 백엔드</a> 과정에 등록했습니다.</p>
<p>다른 개발자들은 어떻게 성장하고, 어떻게 문제를 풀어갈까? 그게 궁금했습니다.</p>
<p>결론부터 말하자면, <strong>기술보다 사람이 더 기억에 남습니다.</strong>
물론 제가 배운 기술들도 많습니다.</p>
<h3 id="기억에-남는-기술들">기억에 남는 기술들</h3>
<p>항해에서 익힌 기술을 다음과 같습니다.</p>
<ul>
<li>TDD, 클린 아키텍쳐</li>
<li>동시성 문제: JAVA API, DB 트랜잭션</li>
<li>API FIRST Design</li>
<li>Test Container</li>
<li>Transactional Messaging</li>
<li>장애 대응 (tracing, logging, metric)</li>
</ul>
<p>특히 TDD와 클린 아키텍처는 좋은 코드란 무엇일까?에 대해 스스로 고민하게 했습니다.
<strong>&quot;이런 것들을 왜 이제야 알게 되었을까?&quot;</strong> 하는 아쉬움도 많이 들었습니다.</p>
<p>하지만 결국 기술은 <strong>책이나 강의로도 배울 수 있습니다.</strong> 요즘처럼 AI가 도와주는 시대라면 구현 자체는 더 쉬워질 것입니다.
저에게 더 큰 깨달음을 준 것은, 사람들과 <strong>함께 고민하며 수련했던 경험</strong>이었습니다.</p>
<h3 id="기억에-남는-사람들">기억에 남는 사람들</h3>
<p>항해에서 만난 분들은 참 대단했습니다.</p>
<h4 id="새벽까지-피드백을-주셨던-코치님들">새벽까지 피드백을 주셨던 코치님들</h4>
<p>&quot;이걸 하나하나 다 보시나?&quot; 싶을 정도로 세심한 피드백을 받았습니다.
질문조차 제대로 준비하지 못한 날엔 죄송하고 부끄럽기도 했습니다.</p>
<h4 id="서로의-문제를-함께-고민하며-성장했던-6기-동료들">서로의 문제를 함께 고민하며 성장했던 6기 동료들</h4>
<p>모두들 정말 열심히 노력하고 있었습니다.
그 모습을 보며 <strong>내가 이 사람들에 비해 많이 부족하구나</strong> 하는 생각이 들었습니다.
실력뿐만 아니라 <strong>노력에서도 뒤처지는 것 같아</strong> 마음이 씁쓸했습니다.</p>
<p>물론 저도 나름대로 최선을 다하며 살아가고 있다고 생각했지만,
항해에서 만난 사람들과 비교하면 제 자신이 작아지는 기분이 들 때가 많았습니다.</p>
<p>그렇지만, 이런 사람들과 함께 공부하고 고민했던 시간이 참 고맙습니다.
저 <strong>스스로 어떤 사람이 되고 싶은지 더욱 고민할 수</strong> 있었습니다.</p>
<blockquote>
<p>다른 분들을 보며 <strong>좋은 동료의 태도란 무엇인지</strong>를 조금은 깨달았습니다.</p>
</blockquote>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/petit-prince/post/0f25a843-4c1e-430d-87b4-c13e0c17a1b9/image.png" alt=""> 뻥튀기된 학습 시간</th>
<th><img src="https://velog.velcdn.com/images/petit-prince/post/1e11235c-93f6-4c6f-a1b2-c8b786e9d6b6/image.png" alt=""> 12월부터 차갑게 식어버린 잔디</th>
</tr>
</thead>
</table>
<h2 id="5-저는">5. 저는</h2>
<p>저는 <strong>문제 해결</strong>을 좋아합니다. 
특히, 남들이 어려워하는 문제를 해결하며 효용감을 느낄 때 가장 보람을 느낍니다. </p>
<p>또한, <strong>같이 일하면 편안하고 즐거운 사람</strong>이 되고 싶습니다.
누군가 저에게 피드백을 주거나 반대 의견을 이야기할 때, 상대방이 안전하고 편안한 환경에서 소통하고 있다고 느낄 수 있도록 노력하고 있습니다.</p>
<p>올해는 이러한 목표를 실천하고 더 성장하기 위해 아래와 같은 계획을 세웠습니다:</p>
<ul>
<li>매달 자기계발서 1권 읽기</li>
<li>개발 세미나 참석 및 공유</li>
<li>최소 1회의 사이드 프로젝트 진행</li>
<li>기술 서적 학습/공유<ul>
<li>사내 TDD/MySQL 세미나</li>
<li>도메인 주도 설계</li>
<li>내 코드가 그렇게 이상한가요</li>
<li>... 그리고 더</li>
</ul>
</li>
</ul>
<hr>
<p>글을 어떻게 마쳐야할 지 잘 모르겠습니다.</p>
<p>항상 도전 앞에서 망설인 저에게 큰 도움이 된 글귀와 함께 물러나겠습니다.</p>
<blockquote>
<p><strong>용기란 어떤 일을 두려워하지 않는 것이 아니라,
두려움 속에서도 불구하고 그 일을 하는 것이다.</strong></p>
</blockquote>
<p>이 글 보는 모두 새해 복 많이 받으세요~! 😘</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트랜잭셔널 아웃박스 패턴: Kafka와 함께 데이터 일관성 유지하기]]></title>
            <link>https://velog.io/@petit-prince/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%94%EB%84%90-%EC%95%84%EC%9B%83%EB%B0%95%EC%8A%A4-%ED%8C%A8%ED%84%B4-Kafka%EC%99%80-%ED%95%A8%EA%BB%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9D%BC%EA%B4%80%EC%84%B1-%EC%9C%A0%EC%A7%80%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@petit-prince/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%94%EB%84%90-%EC%95%84%EC%9B%83%EB%B0%95%EC%8A%A4-%ED%8C%A8%ED%84%B4-Kafka%EC%99%80-%ED%95%A8%EA%BB%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9D%BC%EA%B4%80%EC%84%B1-%EC%9C%A0%EC%A7%80%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 22 Nov 2024 16:36:41 GMT</pubDate>
            <description><![CDATA[<h1 id="transactional-outbox-pattern">Transactional Outbox Pattern</h1>
<h2 id="0-들어가기-전">0. 들어가기 전</h2>
<p>콘서트 예약은 <strong>유저</strong>와 <strong>좌석</strong>이 필요하다.  </p>
<p><img src="https://github.com/hpp-backend-15/java-concert-joonhyeokyang/blob/step18/docs/img/10-01.png?raw=true" alt=""></p>
<p>유저는 <strong>&quot;예약되지 않은 좌석&quot;</strong> 을 예약할 수 있다.  </p>
<p><img src="https://github.com/hpp-backend-15/java-concert-joonhyeokyang/blob/step18/docs/img/10-02.png?raw=true" alt="10.01"></p>
<p>이후, 유저는 예약한 좌석에 대해 <strong>결제</strong>할 수 있다.</p>
<p>또한, 결제 요청 정보를 가져가는 <strong>외부 시스템</strong>이 있다. (로그 분석 등)</p>
<h2 id="1-왜-사용해">1. 왜 사용해?</h2>
<p><img src="https://github.com/hpp-backend-15/java-concert-joonhyeokyang/blob/step18/docs/img/10-1.png?raw=true" alt="10.1"></p>
<p><strong>결제</strong> 요청 이후, 발생하는 일은 다음과 같다. </p>
<ol>
<li><p>DB에 상태 변경 요청</p>
<ul>
<li>유저포인트 삭감</li>
<li>좌석 상태 변경</li>
<li>예약 상태 변경</li>
<li>큐 상태 변경</li>
</ul>
</li>
<li><p>DB 커밋 </p>
</li>
<li><p>이벤트 발행</p>
</li>
<li><p>(외부 서버) 결제 정보 전달</p>
</li>
</ol>
<p>만약, <code>2(DB 커밋)</code>  이후 외부 API 서버에 결제 정보 전달 중 <strong>실패하면 어떤 일이 일어날까?</strong></p>
<p>예를 들어, 외부 시스템 요청 중 <code>Connection Timeout</code>이 났다면, 해당 요청이 완료된 것일까?  </p>
<p>직접 외부 시스템을 확인하지 않는 이상, <strong>보내는 입장에서는 알 도리가 없다!</strong>  </p>
<p><img src="https://github.com/hpp-backend-15/java-concert-joonhyeokyang/blob/step18/docs/img/10-2.png?raw=true" alt="10.2"></p>
<blockquote>
<p>결제 정보 다시 보내면 되는거 아닌가?
다시 보낸다면 <strong>중복된 요청</strong>일 수도 있다.<br>다시 보내지 않는다면 해당 정보가 <strong>전달이 안됐을 수도</strong> 있다.  </p>
</blockquote>
<p>이 문제를 어떻게 해결 할까?</p>
<h2 id="2-문제-해결---카프카-이용">2. 문제 해결 - 카프카 이용</h2>
<p><img src="https://github.com/hpp-backend-15/java-concert-joonhyeokyang/blob/step18/docs/img/10-3.png?raw=true" alt="10.3"></p>
<p>외부 메시지 저장소 <strong>(카프카)</strong> 를 이용해보자</p>
<p>우리 서버의 역할은 카프카에 <strong>결제 정보를 전달</strong>만 하면 된다.</p>
<blockquote>
<p><strong>&quot;카프카에 정보 넣어놨으니, 알아서 가져가!&quot;</strong></p>
</blockquote>
<p>즉, 해당 결제 정보를 가져가는 책임을 <strong>외부 시스템에 위임</strong>할 수 있다.</p>
<p>그럼 모든 문제가 없어질까..?</p>
<p><strong>아니다!</strong></p>
<h2 id="3-원자성atomicity">3. 원자성(Atomicity)</h2>
<p>DB에 결제 요청 정보를 적재하는 상황을 되돌아보자.</p>
<p>DB에 요청을 보내는 <code>1</code>의 하위 4가지 작업은 마치 1개의 작업처럼 움직인다.</p>
<p>그 중 하나라도 실패한다면, <code>2(DB Commit)</code>에서 실패하고, 모든 실패한 작업을 되돌린다.</p>
<p>이처럼 모든 작업이 성공할시 성공하고, 하나라도 작업이 실패하면 모두 실패하는 성질을 <strong>원자성</strong>이라 한다</p>
<p>또한 원자성을 갖는 작업을 <code>트랜잭션</code>이라고 한다.</p>
<p>카프카에 정보를 보내는 작업은 어떤가? </p>
<p>DB에 요청 보내는 작업인 <code>1</code>, <code>2</code>를 제외한 <code>3</code>, <code>4</code>는 요청 중 하나가 실패하더라도, <strong>모든 작업이 실패하진 않는다.</strong></p>
<blockquote>
<p>정확하게는 <code>@Async</code>와 <code>@EventListener</code>를 그냥 사용했을 때, 모든 작업이 실패하진 않는다.</p>
</blockquote>
<p><img src="https://github.com/hpp-backend-15/java-concert-joonhyeokyang/blob/step18/docs/img/10-4.png?raw=true" alt="10.4"></p>
<p>그렇다면, <code>@TransactionalEventListener</code>를 이용하여 <code>3</code>, <code>4</code>까지 작업을 원자적으로 만들어보자</p>
<pre><code class="language-java">
@Transactional
public method(){
    // do business logic
    publishEvent();
}

@TransactioanlEventListener(phase = BEFORE_COMMIT)
public listener(){
    // now I am in Transaction!
    sendMessageToKafka();
}</code></pre>
<p>위와 같이 리스너에 <code>BEFORE_COMMIT</code>옵션을 이용한다면 카프카에 메시지를 보내는 요청까지 원자적으로 처리할 수 있다.</p>
<p>그러나 <strong>치명적인 문제가 생긴다.</strong></p>
<p><code>외부 시스템에 정보 전달</code>을 하다가 실패하면, <strong>결제가 실패한다.</strong></p>
<p><strong>결제</strong>는 돈을 벌어다주는 너무나도 중요한 비지니스 로직이다.</p>
<p><strong>(외부 시스템에 정보 전달보다도 훨씬 더!)</strong></p>
<p>그러므로, 외부 시스템에 정보 전달에 실패하여도, 메인 로직인 결제가 실패하지 않아야 한다.</p>
<p>그렇다면, <code>@TransactioanlEventListener의 phase=AFTER_COMMIT</code> 옵션을 이용하여 DB 트랜잭션에서 분리할 수 있다.</p>
<p>그러나, 처음 <code>Connection Timeout</code> 상황으로 돌아갔다.  </p>
<p>카프카에 메시지를 적재하는 순간 서버가 종료되면, 직접 카프카를 확인해야 한다.  (외부 시스템이 카프카로 변경됐을 뿐이다.)</p>
<p>상황을 정리해보자 </p>
<h3 id="1-카프카에-메시지를-보내는-요청이-트랜잭션-내부에-있는-경우">1. 카프카에 메시지를 보내는 요청이 <strong>트랜잭션 내부에 있는 경우</strong></h3>
<ul>
<li>외부 전달 요청이 실패하면, 결제가 실패한다.</li>
<li>그러므로 트랜잭션 내부에 존재해선 안된다.</li>
</ul>
<h3 id="2-카프카에-메시지를-보내는-요청이-트랜잭션-외부에-있는-경우">2. 카프카에 메시지를 보내는 요청이 <strong>트랜잭션 외부에 있는 경우</strong></h3>
<ul>
<li>외부 결제 정보 전달이 성공적으로 수행됐는가에 대한 보장을 할 수 없다.</li>
<li>그러므로 트랜잭션 외부에 존재해선 안된다.</li>
</ul>
<p>외부 결제 정보 전달에 실패하더라도 <strong>결제</strong>는 실패하지 않아야 한다.</p>
<p>하지만 외부 결제 정보 전달이 꼭 결제와 동시에 일어나지 않아도 된다면,</p>
<p>외부 결제 정보 전달 성공/실패 여부를 추적하고, 실패시 다시 전송을 하는 시스템을 생각해 볼 수 있다.</p>
<p>이를 결과적 일관성(Eventual Consistency)이라 한다.</p>
<p>메인 로직이 성공한다면, 부가적인 로직이 <strong>결국엔</strong> 성공하도록 하는 시스템을 말한다.</p>
<p><strong>결과적으론 결제와 외부 결제 정보 전달에 성공하는 시스템</strong>을 고안해보자.  </p>
<h2 id="4-문제-해결---트랜잭션-아웃박스-이용">4. 문제 해결 - 트랜잭션 아웃박스 이용</h2>
<ol>
<li>도메인 로직이 아닌 외부 결제 정보 전송은 원자적으로 처리하지 않는다.</li>
<li>그러나 같은 트랜잭션처럼, <strong>결과적으로는</strong> 외부 결제 정보 전송을 보장하도록 한다.</li>
</ol>
<p><strong>트랜잭셔널 아웃 박스 패턴 (Transactional Outbox Pattern)</strong> 을 이용해보자.</p>
<p>Outbox는 메일함이다.</p>
<p>도메인 로직을 처리할 때 같이 원자적으로 처리되는 <strong>메일함</strong> 을 생각해보자.</p>
<p>이러한 원자성을 이용하기 위해 DB를 이용한다.</p>
<p>원자적으로 처리되는 메일함인 <code>event_outbox</code> 테이블을 생성한다. </p>
<p>또, 카프카에 메시지 전송이 됐는지 여부를 확인하기 위해 카프카의 특징 또한 이용한다.</p>
<blockquote>
<p>카프카 특징
카프카는 넷플릭스와 비슷하다. </p>
<p>넷플릭스는 다수의 이용자가 동시에 시청도 가능하고, 어디까지 봤는지도 알고 있다.</p>
<p>카프카도 <code>consumer_group_id</code>를 이용하여, 여러 서버가 동시에 메시지를 받고, 해당 그룹이 어디까지 읽었는지도 알 수 있다.</p>
</blockquote>
<p><img src="https://github.com/hpp-backend-15/java-concert-joonhyeokyang/blob/step18/docs/img/10-7.png?raw=true" alt="10.7"></p>
<p>이를 이용하여 외부 시스템만 내가 보낸 메시지를 받는게 아니라, <strong>메시지 전송을 하는 나도 카프카로부터 메시지를 읽을 수 있다.</strong></p>
<p><strong>그렇다면 내가 보낸 메시지를 카프카로부터 받아 카프카에 보내졌는지 검증할 수 있다는 뜻이다.</strong></p>
<blockquote>
<p>즉, 방송국이 정상적으로 방송이 송출되는지 확인을 하기 위해, TV를 켜 방송 화면을 보고 &quot;보인다!&quot;하고 확인하는 과정이다.</p>
</blockquote>
<ol>
<li>결제 요청시, <code>event_outbox</code>에 <code>카프카에 메시지를 보낼꺼야</code>라고 적어놓고 카프카에 메시지를 발송한다.</li>
<li>(내 서버의 카프카 리스너) <code>consumer_group_id</code>를 외부 시스템과는 다르게 지정하여, <br/> 메시지를 카프카로부터 직접 수신함으로써 카프카에 보내졌는지 확인한다.</li>
<li>(메시지가 잘 전송된 경우) <code>event_outbox</code>에 <code>카프카에 메시지가 보내졌다.</code> 라고 수정한다.</li>
<li>(메시지가 전송되지 않은 경우) <code>event_outbox</code> 테이블을 순회하며, 보내지지 않은 메시지를 주기적으로 확인하고, 다시 전송한다.</li>
</ol>
<p><img src="https://github.com/hpp-backend-15/java-concert-joonhyeokyang/blob/step18/docs/img/10-6.png?raw=true" alt="10.6"></p>
<p>실제로, 트랜잭션 아웃박스 패턴을 이용하여 얻게되는 효과를 정리해보자</p>
<table>
<thead>
<tr>
<th>1 트랜잭션 수행 성공 여부</th>
<th>2 카프카 메시지 발송 성공 여부</th>
<th>구분</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>O</td>
<td>O</td>
<td>성공</td>
<td>도메인 로직 성공적으로 수행, 카프카도 메시지 정상 발송/수신 한 성공 케이스</td>
</tr>
<tr>
<td>X</td>
<td>O</td>
<td>일어날 수 없는 케이스</td>
<td>트랜잭션이 실패한다면, 트랜잭션 이벤트 리스너의<code>phase=AFTER_COMMIT</code>  조건에 의해 메시지 발송 자체가 불가능</td>
</tr>
<tr>
<td>O</td>
<td>X</td>
<td>관리 가능한 실패</td>
<td>카프카 메시지 발송 시도 중 모종의 이유로 실패한 경우, <code>4</code>의 배치를 이용하여 메시지 재전송</td>
</tr>
<tr>
<td>X</td>
<td>X</td>
<td>실패</td>
<td>비지니스 로직도, 메시지 전송도 아예 실패한 경우. 그러나 정합성은 보장된다</td>
</tr>
</tbody></table>
<p>정리된 표에서 알 수 있듯이, 도메인 로직과 메시지 발송이 성공하거나 실패할 수 있다.      </p>
<p>그러나, 모든 경우에서 <code>결과적 일관성</code>을 모두 보장할 수 있다.</p>
<p><strong>즉, 트랜잭션 아웃박스 패턴은 외부 메시징 시스템을 이용할 때 <code>결과적 일관성</code>을 보장하는 전략이다.</strong></p>
<hr>
<h1 id="항해에-참가하려는-당신">항해에 참가하려는 당신!</h1>
<p>글을 읽다보니 <strong>&#39;아..! 나도 저거 알아야하는데!&#39;</strong> 하진 않으신가요?!</p>
<p>여기까지 시리즈를 읽으셨다면, 아마 <strong>항해 플러스 과정</strong>에 관심있으신 분이라고 생각합니다.</p>
<p>궁금한 점이 있으시다면 저에게 편하게 댓글 남겨주세요!
혹은 <code>highestbright98@naver.com</code>으로 메일을 남겨주셔도 굉장히 빨리 답변한답니다!</p>
<p>항해 플러스 백엔드에 관련해서 궁금하신점이 있다면 언제든 편하게 말씀주세요!</p>
<p>그리고 글이 도움됐다면, 등록하실때 제 추천인 코드 <strong>[9MPLfu]</strong> 입력하면,
<strong>20만원 할인의 혜택</strong>이 있답니다! (물론 저에게도 혜택이 있습니다! 😉)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[실전 APM! 성능과 병목 지점 추적 - 1]]></title>
            <link>https://velog.io/@petit-prince/%EC%8B%A4%EC%A0%84-APM-%EC%84%B1%EB%8A%A5%EA%B3%BC-%EB%B3%91%EB%AA%A9-%EC%A7%80%EC%A0%90-%EC%B6%94%EC%A0%81</link>
            <guid>https://velog.io/@petit-prince/%EC%8B%A4%EC%A0%84-APM-%EC%84%B1%EB%8A%A5%EA%B3%BC-%EB%B3%91%EB%AA%A9-%EC%A7%80%EC%A0%90-%EC%B6%94%EC%A0%81</guid>
            <pubDate>Sat, 09 Nov 2024 14:14:17 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@petit-prince/%EB%8D%94-%EC%A7%84%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-2">전글</a>과 <a href="https://velog.io/@petit-prince/%EB%8D%94-%EC%A7%84%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4">전전글</a>을 보고 오시면, 이해하기 더 수월합니다.</p>
<h1 id="0-어디가-문제지-😵">0. 어디가 문제지..? 😵</h1>
<blockquote>
<p>갑자기 슬랙 알림이 미친듯이 오더니, 서버 메모리는 터져서 <strong>다운 직전</strong>이고
내 API 요청의 응답시간은 <strong>10초</strong>를 넘어간다고 생각해보자.
그리고 모든 사람이 담당자인 <strong>나를 찾는다.</strong></p>
</blockquote>
<blockquote>
<p><strong>당신은 무슨일 부터 시작할 것인가?</strong> </p>
</blockquote>
<p>답을 상상하며, 아래 케이스를 같이 보자.</p>
<h1 id="1-apm-추적-심화편">1. APM 추적 심화편</h1>
<p>현재 상황은 트래픽이 갑자기 솟구치는 상황입니다. 
(엄청나게 많은 유저가 한번에 콘서트 좌석 예약을 시도하는 상황)</p>
<p>비관락은 <a href="https://velog.io/@petit-prince/%EB%8D%94-%EC%A7%84%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-2">전글</a>에서 보았듯이, 성능이 뚜렷하게 낮으므로, <strong>낙관락과 분산락</strong>을 중점적으로 비교해보겠습니다.</p>
<h2 id="실험-제약">실험 제약</h2>
<ul>
<li>1,000 TPS</li>
<li>예약 가능한 좌석 수는 10,000개</li>
<li>4vCPU 8Gb VM 인스턴스 * 5개</li>
<li>2vCPU 8Gb MySQL 1개</li>
<li>2GB Redis 1개
<img src="https://velog.velcdn.com/images/petit-prince/post/88dec34b-c73e-421a-b21a-0ba42b581b18/image.png" alt=""></li>
</ul>
<h2 id="apm-설정">APM 설정</h2>
<p>한참 성능 테스트를 하며 결과를 받을 때 한계에 도달했습니다.
바로 <strong>어디서 병목이 일어나는지 확인이 불가능</strong>했기 때문입니다.</p>
<p>API의 요청 응답 시간만 주구장창 확인하며 실험을 계속 진행하기보다 실제로 각 요청에 얼마나 많은 시간이 소모되는지 확인하고자 하여, APM을 설정했습니다.</p>
<p>세가지 APM이 후보였습니다.</p>
<ul>
<li>Oracle APM</li>
<li>OpenTelemetry + jaeger/zipkin</li>
<li>PinPoint</li>
</ul>
<p>결론적으로는 <strong>OpenTelemetry + jaeger</strong> 조합을 사용했습니다.</p>
<p><code>opentelemetry-spring-boot-starter</code>를 사용했습니다. 
큰 설정없이 굉장히 많은 종류의 인프라를 알아서 트레이싱해주는 기능이 있습니다..만</p>
<p><strong>제가 사용하고 있던 <code>Redisson</code>은 없었습니다.</strong>
그래서.. 해당 라이브러리가 아닌 <code>OpenTelementry Java Agent</code>와 <code>Redisson Instrument</code>를 사용하면 됐지만, 시간이 너무 많이 소모되어 직접 커스텀 <code>span</code>을 추가하는 방식으로 퉁쳤습니다.</p>
<pre><code class="language-java">LockId lockId = new LockId(type + &quot;-&quot; + id);
RLock rLock = redissonClient.getLock(lockId.getValue());
Span span = tracer.spanBuilder(&quot;RedissonLockManager.tryLock&quot;).startSpan();</code></pre>
<blockquote>
<p><strong>_Oracle APM과 PinPoint를 제외한 이유 _</strong></p>
<p>처음엔 <strong>Oracle APM</strong>을 선택했습니다.
이유는 OCI 기반으로 실행하고 있었기에 현재 인프라와 잘 어울린다고 생각했기 때문입니다.</p>
<p>추가적으로 공식 문서만 봐도 짧은 시간안에 APM을 설정하고 값을 확인할 수 있었습니다.</p>
<p>그러나..
<img src="https://velog.velcdn.com/images/petit-prince/post/5560ad00-01f5-4fe9-8374-11240d79d7fc/image.png" alt=""></p>
<p><strong>무료 버전(Always Free)</strong> 임에도 <strong>하루에 약 만원 정도</strong>가 지불되는 상황에 현실적으로 이용하기 어려웠습니다.</p>
<p>APM 리소스를 생성/삭제하는데에도 큰 시간이 들었고,
다시 생성한 APM 리소스 기반으로 Agent도 재설치 해야했습니다.</p>
<p>그래서 모든 Oracle APM 설정을 걷어내고, OpenTelementry 기반으로 바꿨습니다.</p>
</blockquote>
<blockquote>
<p>이외에 PinPoint는 분명 설정해볼만 했지만, 시간관계상 샘플만 구동해보고 포기했습니다.</p>
</blockquote>
<h2 id="실험-결과분산락---data-connection-pool">실험 결과(분산락) - Data Connection Pool</h2>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/01fe720f-fdde-4a63-ab13-df39ceffc62f/image.png" alt=""></p>
<p>먼저 jaeger UI에서 가장 긴 Duration을 갖는 요청의 span을 확인했습니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/2a0c2d57-4412-47fb-a756-47cd9386ca84/image.png" alt=""></p>
<p>좌석 요청까지도 가지 못하고, <strong>대기자</strong> 였는지 확인을 위한 DB 커넥션을 받다가 timeout이 났습니다.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/2bb11402-63f7-4147-ba26-55cb878a7c96/image.png" alt=""></p>
<p>커넥션 풀도 이미 DB에서 미리 설정해놓은 200개를 다 가져가버린 상황이었습니다.</p>
<p>이렇다면, 사실상 분산락/낙관락 비교를 하기 어렵기에 TPS를 낮춰 실험을 해보겠습니다.</p>
<h2 id="실험-결과-분산락">실험 결과 (분산락)</h2>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/d934a294-2dfa-4f68-9ec0-71b0712ad6a9/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/defe5ef7-3fff-4e52-801b-906c423f7b4d/image.png" alt="">
9344건의 요청에서 10건정도가 3초 이상의 요청 시간이 걸렸습니다.</p>
<h2 id="실험-결과-낙관락">실험 결과 (낙관락)</h2>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/d2019c36-c1ea-4fe2-b7f2-253bf3b571af/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/598ac1ab-792e-415a-921d-964fe660e28a/image.png" alt="">
11844건의 요청에서 7건정도가 3초 이상 요청 시간이 걸렸습니다.</p>
<p>알고보니, 현재 실험 제약에서 대기열에서 대기자인지 확인하기 위해 먼저 DB 커넥션을 받아오는데, 여기서부터 정확한 실험을 설계하는 것 자체가 불가능합니다.</p>
<p>즉, 여기서 분산락은 성능에 이점 보다는 <strong>동시성 제어</strong>에만 도움을 주고 있습니다.</p>
<p>대기열을 Redis로 이관하여, 결과를 확인해보겠습니다!</p>
<h2 id="결론">결론</h2>
<ul>
<li>DateConnection을 가져오는 것은 병목 지점이고, 굉장히 많은 리소스를 요구한다. 이부분을 격리해야 진정한 대용량 트래픽을 받아낼 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 백엔드 6기] 서버가 죽고 있어요! 어떡하죠? 😵]]></title>
            <link>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-6%EA%B8%B0-%EC%84%9C%EB%B2%84%EA%B0%80-%EC%A3%BD%EA%B3%A0-%EC%9E%88%EC%96%B4%EC%9A%94-%EC%96%B4%EB%96%A1%ED%95%98%EC%A3%A0</link>
            <guid>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-6%EA%B8%B0-%EC%84%9C%EB%B2%84%EA%B0%80-%EC%A3%BD%EA%B3%A0-%EC%9E%88%EC%96%B4%EC%9A%94-%EC%96%B4%EB%96%A1%ED%95%98%EC%A3%A0</guid>
            <pubDate>Sat, 09 Nov 2024 13:16:16 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/petit-prince/post/409d5f40-031f-4d8b-972a-6f5afa2b1fdd/image.png" alt=""></p>
<p>이런 상황을 상상해볼까요?</p>
<blockquote>
<p>갑자기 슬랙 알림이 미친듯이 오더니, 서버가 터져서 <strong>다운 직전</strong>이고, 
내 API 응답시간은 <strong>10초</strong>를 넘어간다고 생각해보자. 그리고 모든 사람이 담당자인 나를 찾는다.</p>
</blockquote>
<p>그렇다면 개발자인 당신은
<strong>무슨 일 부터 시작할 것인가요?</strong></p>
<p><strong>[들어가기 전]</strong>
다음 용어가 무엇인지 알고, 설명할 수 있나요?</p>
<ul>
<li>APM</li>
<li>필터와 인터셉터</li>
<li>캐쉬(글로벌 캐쉬, 로컬 캐쉬)와 캐쉬 전략</li>
</ul>
<h2 id="1-apm">1. APM</h2>
<p>저는 서버가 터진다면, *<em>APM</em> 을 먼저 확인할 거에요!
<strong>A</strong>pplication <strong>P</strong>erformance <strong>M</strong>onitoring 이란?</p>
<ul>
<li><p><strong>성능 추적</strong> 
애플리케이션의 응답 시간, 처리 속도, 트랜잭션 처리 현황 등을 실시간으로 모니터링하여 성능 저하 지점 추적 및 발견합니다.</p>
</li>
<li><p><strong>오류 추적</strong>
애플리케이션 내에서 발생하는 오류나 예외를 자동으로 추적하고, 이를 상세하게 기록합니다</p>
</li>
<li><p><strong>트랜잭션 모니터링</strong>
사용자 요청이나 트랜잭션의 흐름을 추적하여 병목 현상이나 지연 요소를 파악합니다.</p>
</li>
<li><p><strong>리소스 활용도 분석</strong>
CPU, 메모리, 디스크 I/O 등 시스템 리소스의 사용량을 모니터링하여 리소스 부족이나 비효율적인 사용을 감지합니다.</p>
</li>
</ul>
<p>다음으로, 어떤 요청이 들어오고 어떤 응답을 주고 있는지 확인할 것입니다.
근데.. <strong>그 전에 그런 기록을 하고 있나요?</strong> 한다면, <strong>어떤 방식</strong>으로 하실 건가요?</p>
<p>저라면, 비지니스 로직에 분리되어 공통적으로 처리할 수 있는 기술을 사용할 거에요!
즉, <strong>필터</strong>를 사용할 거에요!</p>
<h2 id="2-필터와-인터셉터">2. 필터와 인터셉터</h2>
<p>저는 <strong>필터</strong>와 <strong>@Slf4j</strong> 이용하여 요청과 응답 파라미터를 로깅했습니다.
이때, 파라미터값을 로깅하기 위해서<code>ContentCachingRequestWrapper</code>를 사용했고, 왜 사용해야 하는지에 대해 학습했어요!</p>
<p>그런데, <strong>필터와 인터셉터를 언제 써야할까요?</strong> 또, 무슨 차이가 있을까요?</p>
<p>자세한 내용은 <a href="https://velog.io/@petit-prince/%ED%95%84%ED%84%B0%EC%99%80-%EC%9D%B8%ED%84%B0%EC%85%89%ED%84%B0">필터와 인터셉터</a>에 정리해뒀습니다!</p>
<h2 id="3-캐쉬">3. 캐쉬</h2>
<p>확인해보니, 너무나 자주 <strong>콘서트 정보 요청</strong>이 오고 있었군요!
그렇다면, 자주 쓰이는 요청은 캐싱을 이용해 DB에 요청하지 않고도 반환할 수 있습니다.</p>
<p>콘서트 관련 조회의 데이터가 크지 않지만, 요청이 너무 많기 때문에 <strong>Redis</strong> 이용하여 캐싱을 했습니다.</p>
<p><strong>Redis</strong>와 같이 글로벌 캐싱을 택하는 이유는, 여러 서버 인스턴스가 각각의 콘서트 상태를 다르게 저장하는 정합성 문제를 최소화하기 위해서입니다.</p>
<p>또한, 콘서트는 많은 조회가 있기에 캐시를 먼저 확인 후 없다면 DB에 조회했습니다.
(<strong>Look Aside</strong>)</p>
<p>그리고 캐쉬에 먼저 좌석 점유 정보를 저장하지 않는다면, 무수히 많은 좌석 점유 요청이 DB에 도달하므로, 캐쉬에 먼저 좌석 점유 정보를 저장하는 전략을 택했습니다.
(<strong>Write Through</strong>, 단 구현 편의를 위해 동기화는 서버가 하는 방식으로 진행)</p>
<details> 
<summary>캐싱 전략</summary>

<p>캐싱 전략은 <strong>1. Look Aside</strong>, <strong>2. Read Through</strong>, <strong>3. Write Behind</strong>, <strong>4. Write Around</strong>가 있습니다.</p>
<h3 id="1-look-aside">1. Look Aside</h3>
<ul>
<li>캐쉬를 먼저 조회합니다. (있다면 반환).</li>
<li>캐쉬 미스시에 서버는 DB와 같은 원본 저장소(Data Store)에 조회한 뒤</li>
<li><strong>서버</strong>는 해당 값을 캐쉬에 업데이트 합니다.</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>많은 읽기에 적합</li>
<li>Cache 서버 장애가 나도 괜찮음</li>
<li>반복적, 동일 쿼리를 수행하는 서비스에 적합</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>캐쉬와 원본 저장소간 정합성 불일치 문제</li>
<li>캐시에 데이터가 없다면, 추가적인 시간 소모</li>
</ul>
<blockquote>
<p>초기 DB 데이터를 캐쉬에 넣어놓으면 성능 향상을 기대할 수 있다.<br>이를 <strong>Cache Warming</strong>이라 한다.</p>
</blockquote>
<h3 id="2-read-through">2. Read Through</h3>
<ul>
<li>캐쉬를 먼저 조회합니다. (있다면 반환)</li>
<li>캐쉬 미스시에 서버는 DB와 같은 원본 저장소(Data Store)에 조회</li>
<li><strong>원본 저장소</strong>는 해당 값을 캐쉬에 업데이트 합니다.</li>
</ul>
<h3 id="3-write-through">3. Write Through</h3>
<ul>
<li>데이터 저장시 캐쉬에 먼저 저장</li>
<li><strong>캐쉬</strong>는 해당 데이터를 원본 저장소에 저장</li>
<li>서버는 캐쉬에 조회</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>캐쉬와 원본 저장소간 정합성 불일치 문제 해소</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>저장시 2단계를 거치기에 느림</li>
<li>캐쉬에 저장된 데이터를 다시 조회하지 않는다면 리소스 낭비 (TTL로 해결)</li>
</ul>
<h3 id="4-write-around">4. Write Around</h3>
<ul>
<li>쓰기 요청은 DB에만 저장한다.</li>
<li>읽기 요청은 캐쉬에만 조회한다.</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>매우 빠르다</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>정합성 문제</li>
</ul>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://kk-programming.tistory.com/83">로컬 vs 글로벌 캐싱</a></li>
<li><a href="https://yoongrammer.tistory.com/101#Write_Around">캐쉬 알아보기</a></details>


</li>
</ul>
<h1 id="항해에-참가하려는-당신">항해에 참가하려는 당신!</h1>
<p>혹시 <strong>[들어가기전]</strong>의 답변이 궁금하신가요!?
글을 읽다보니 <strong>&#39;아..! 나도 저거 알아야하는데!&#39;</strong> 하진 않으신가요?!</p>
<p>여기까지 글을 읽으셨다면, 아마 <strong>항해 플러스 과정</strong>에 관심있으신 분이라고 생각합니다.</p>
<p>궁금한 점이 있으시다면 저에게 편하게 댓글 남겨주세요!
혹은 <code>highestbright98@naver.com</code>으로 메일을 남겨주셔도 굉장히 빨리 답변한답니다!</p>
<p>항해 플러스 백엔드에 관련해서 궁금하신점이 있다면 언제든 편하게 말씀주세요!</p>
<p>그리고 글이 도움됐다면, 등록하실때 제 추천인 코드 <strong>[9MPLfu]</strong> 입력하면,
<strong>20만원 할인의 혜택</strong>이 있답니다! (물론 저에게도 혜택이 있습니다! 😉)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[필터와 인터셉터]]></title>
            <link>https://velog.io/@petit-prince/%ED%95%84%ED%84%B0%EC%99%80-%EC%9D%B8%ED%84%B0%EC%85%89%ED%84%B0</link>
            <guid>https://velog.io/@petit-prince/%ED%95%84%ED%84%B0%EC%99%80-%EC%9D%B8%ED%84%B0%EC%85%89%ED%84%B0</guid>
            <pubDate>Sun, 03 Nov 2024 17:57:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/petit-prince/post/c40d9846-9619-4c30-ab9f-e9c6a41fd8c3/image.png" alt=""></p>
<h2 id="필터와-인터셉터">필터와 인터셉터</h2>
<p>Spring은 공통적으로 여러 작업을 처리함으로써, <strong>중복된 코드를 제거할 수 있도록</strong> 많은 기능을 지원하고 있습니다.
개 중에서, <strong>필터(Filter)</strong> 와 <strong>인터셉터(Interfceptor)</strong> 를 정리합니다.</p>
<h3 id="1-필터">1. 필터</h3>
<p>필터는 J2EE 표준 스펙 기능으로 <strong>디스패처 서블릿(Dispatcher Servlet)</strong> 에 요청이 전달되기 전/후에 url 패턴에 맞는 모든 요청에 대해 부가 작업을 처리할 수 있는 기능을 제공합니다.</p>
<p><strong>디스패처 서블릿</strong> 은 스프링의 가장 앞단에 존재하는 프론트 컨트롤러이므로, 필터는 스프링 범위 밖에서 처리가 됩니다.</p>
<ul>
<li>필터는 리소스에 대한 <strong>요청/응답에 대한 필터 작업</strong> 을 합니다.</li>
<li>필터는 <code>doFilter</code> 메서드를 통해 필터 작업을 합니다.</li>
<li>모든 필터는 초기화 파라미터를 얻을 수 있는 <code>FilterConfig</code>에 접근이 가능하고,</li>
<li>서블릿 컨텍스트 (ServletRequest/ServletResponse)에 참조 및 사용이 가능합니다.</li>
</ul>
<h4 id="init">Init</h4>
<p>필터가 <strong>자리해야할 곳을 표시</strong>하고 서비스에 배치하기 위해 <strong>웹 컨테이너</strong> 로 부터 호출됩니다.
<img width="932" alt="image" src="https://github.com/user-attachments/assets/b7d0193b-1815-4828-975e-b93504def25c"></p>
<p>서블릿 컨테이너는 필터가 인스턴스화된 뒤 정확히 한번 호출합니다. 필터 작업을 하기전, <strong>필터 초기화(init)</strong>가 성공적으로 완료되어야 합니다. 웹 컨테이너는 <strong>초기화</strong> 메서드가 1. <code>ServletException</code>을 던지거나, 2. 웹컨테이너가 사전 지정한 시간 내에 종료(return)하지 않으면, 서비스에 배치 할 수 없습니다.</p>
<h4 id="dofilter">doFilter</h4>
<p>필터의 <code>doFilter</code> 메서드는 체인 끝의 리소스에 대한 클라이언트 요청으로 인해 요청/응답 쌍이 체인을 통과할 때마다 컨테이너에서 호출됩니다.
(좀 의역해보자면, 체인 끝의 리소스-&gt; 디스패쳐 서블릿 이후 접근하고자 하는 리소스이므로, 클라이언트 요청/응답마다 필터 체인의 필터의 <code>doFilter</code>메서드가 컨테이너에 의해 실행된다.. 정도)</p>
<p>이 메서드에 전달된 <strong>필터 체인</strong> 을 통해 <strong>필터</strong> 는 <strong>체인의 다음 개체</strong> 에 요청과 응답을 전달할 수 있습니다.</p>
<p>주로 이 메서드는 주로 다음과 같이 구현됩니다.</p>
<ol>
<li>요청값 검사</li>
<li>(옵션) 요청 객체를 <code>입력 필터링</code>에 맞게 직접 구현한 필터 내용이나 헤더로 바꾸기</li>
<li>(옵션) 요청 객체를 <code>출력 필터링</code>에 맞게 직접 구현한 필터 내용이나 헤더로 바꾸기</li>
<li>아래 둘 중 하나 작업<ol>
<li><code>FilterChain</code> 객체를 이용하여 체인의 다음 개체(<code>entity</code>)를 호출 (<code>chain, doFilter()</code>)</li>
<li>체인의 다음 객체에 요청/응답 값을 보내지 않고, 요청을 차단해버림</li>
</ol>
</li>
<li>필터 체인의 다음 개체를 호출하며 직접 헤더를 지정(<code>set</code>)</li>
</ol>
<h4 id="destroy">destroy</h4>
<p>웹 컨테이너가 서비스에서 필터를 제외시켰다는 것을 나타내기위해 호출합니다.</p>
<p>이 메서드는 필터들의 <code>doFilter</code>메서드가 끝나거나, <code>timeout</code>이 지났을 때 모든 쓰레드에서 한번만 호출됩니다.
웹 컨테이너가 이 메서드를 호출하면, 이 필터의 인스턴스에서 <code>doFilter</code>를 다시는 요청하지 않습니다.</p>
<p>이 메서드는 필터에게 잡혀있는 자원(ex 메모리, 파일 핸들러, 쓰레드)을 모두 정리할 기회를 줍니다. 그리고 어떤 영속적인 상태도 메모리 상 필터의 현재 상태와 동기화되도록 합니다. (🤔 아마 모든 쓰레드에서 모두 동기화 된다는 의미인듯?)</p>
<h3 id="2-인터셉터">2. 인터셉터</h3>
<p>먼저, 우리가 부르는 <strong>인터셉터</strong>들은 <code>**HandlerInterceptor**</code>의 구현체이다. <code>**HandlerInterceptor**</code>는 <code>**HandlerMapping**</code>의 구현된 핸들러의 요청을 가로챈다(Intercept). 그래서, 공통된 요청을 가로질러 기능을 적용하고자 할때 유용하다.</p>
<p><strong>HandlerInterceptor</strong> 인터페이스는 다음 세 메서드를 구현한다.</p>
<h4 id="prehandle">preHandle()</h4>
<p><code>boolean</code>값을 반환하는 실제 핸들러가 실행되기 전 콜백 메서드이다. 만약 <code>true</code> 라면 계속 실행하고, <code>false</code> 라면 그 이후 실행 체인은 우회되고 <strong>핸들러가 실행되지 않는다</strong></p>
<h4 id="posthandle">postHandle()</h4>
<p>핸들러 실행 이후 콜백 메서드이다.</p>
<h4 id="aftercompletion">afterCompletion()</h4>
<p>모든 요청이 끝난 뒤 콜백이다.</p>
<p>[추가]
인터셉터는 J2EE 표준 스펙인 [[Filter]]와 달리, <strong>Spring이 제공하는 기술로써</strong> , 디스패쳐 서블릿이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조/가공하는 기능을 제공한다. 즉, 웹 컨테이너(서블릿 컨테이너)에서 동작하는 필터와는 달리 인터셉터는 스프링 컨텍스트에서 동작한다. 디스패쳐 서블렛은 핸들러 매핑을 통해 적절한 컨트롤러를 찾도록 요청하는데, 그 결과로 실행 체인(HandlerExecutionChain)을 돌려준다. 그래서 이 실행 체인은 1개 이상의 인터셉터가 등록되어 있다면, 순차적으로 인터셉터들을 거쳐 컨트롤러가 실행되도록 하고, 인터셉터가 없다면 바로 컨트롤러를 실행한다.
<img width="709" alt="image" src="https://github.com/user-attachments/assets/653a9db3-d819-42ba-b162-fc4aa00753b0"></p>
<p>*HandlerMapping: <code>@GetMapping</code> (이런식)  핸들러를 지정해주는 역할
디스패쳐 서블릿은 프론트 컨트롤러로서 핸들러 인스턴스를 맵과 같은 자료구조에 저장한다.
이후 url 패턴에 맞는 요청이 오면 해당 클래스를 반환 한 뒤 실행한다.</p>
<h1 id="출처">출처</h1>
<ul>
<li><a href="https://mangkyu.tistory.com/173">MangKyu&#39;s Diary:티스토리</a></li>
<li><a href="https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/handlermapping-interceptor.html">https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/handlermapping-interceptor.html</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[더 진한 동시성 제어 - 2]]></title>
            <link>https://velog.io/@petit-prince/%EB%8D%94-%EC%A7%84%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-2</link>
            <guid>https://velog.io/@petit-prince/%EB%8D%94-%EC%A7%84%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-2</guid>
            <pubDate>Sat, 02 Nov 2024 18:34:04 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@petit-prince/%EB%8D%94-%EC%A7%84%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4">더 진한 동시성 제어 - 1</a>를 보고 오시면 더 
풍부한 이해를 얻을 수 있습니다.</p>
<h1 id="0-previously">0. Previously</h1>
<p>앞선 포스트에서는 <code>낙관락</code>, <code>비관락</code>을 이용하여 3가지 시나리오에서 어떤 결과를 갖는지 결과를 확인했습니다.</p>
<ul>
<li>시나리오 1: 한 유저가 <strong>한 좌석</strong>에 짧은 시간안에 500+번 요청</li>
<li>시나리오 2: 한 유저가 <strong>여러 좌석</strong>에 짧은 시간안에 500+번 요청</li>
<li>시나리오 3: 한 유저가 <strong>여러 좌석</strong>에 짧은 시간(3분)안에 ~=300,000번 요청</li>
</ul>
<p>다만, <strong>DB의 뛰어난 성능대비 떨어지는 VM 성능</strong> 등 다양한 이유로 모든 테스트 결과가 비슷했습니다.</p>
<p>기존 환경 구성으로 이번에는 redis를 이용한 <code>분산락</code>을 이용한 결과입니다.</p>
<table>
<thead>
<tr>
<th></th>
<th>동시성 제어</th>
<th>평균 요청 대기시간</th>
</tr>
</thead>
<tbody><tr>
<td>낙관락 - 시나리오 1</td>
<td>O</td>
<td>3.32s</td>
</tr>
<tr>
<td>낙관락 - 시나리오 2</td>
<td>O</td>
<td>3.1s</td>
</tr>
<tr>
<td>비관락 - 시나리오 1</td>
<td>O</td>
<td>3.36s</td>
</tr>
<tr>
<td>비관락 - 시나리오 2</td>
<td>O</td>
<td>3.14s</td>
</tr>
<tr>
<td>분산락 - 시나리오 1</td>
<td>O</td>
<td>3.13s</td>
</tr>
<tr>
<td>분산락 - 시나리오 2</td>
<td>O</td>
<td>3.5s</td>
</tr>
</tbody></table>
<p>시나리오 1, 2는 너무 적은 요청에, 모두 비슷한 결과치로 실험으로부터 어떠한 인사이트를 도출하긴 어려웠습니다.</p>
<p>시나리오 3 또한 비슷한 결과를 보여줬습니다.</p>
<table>
<thead>
<tr>
<th></th>
<th>동시성 제어</th>
<th>평균 요청 대기시간</th>
</tr>
</thead>
<tbody><tr>
<td>낙관락 - 시나리오 3</td>
<td>O</td>
<td>7.59s</td>
</tr>
<tr>
<td>낙관락 - 시나리오 3</td>
<td>O</td>
<td>8.15s</td>
</tr>
<tr>
<td>비관락 - 시나리오 3</td>
<td>O</td>
<td>7.6s</td>
</tr>
</tbody></table>
<p>VM 성능 낮기 때문에 유효한 결과가 나오지 않았습니다.
아래 스펙의 VM * 5 개와 RR방식의 Public LB를 붙여서 테스트 했습니다.</p>
<p>또한 요청을 보내는 k6 에이전트 목표 TPS도 조금 낮추어 실험을 진행했습니다. (기존 30,000 -&gt; 5,000)</p>
<pre><code>Shape: VM.Standard.A1.Flex
OS: Canonical Ubuntu 24.04
Network bandwidth (Gbps): 1
vCPU: 4
Mem: 6GB</code></pre><p><img src="https://velog.velcdn.com/images/petit-prince/post/88dec34b-c73e-421a-b21a-0ba42b581b18/image.png" alt=""></p>
<ul>
<li>LB Health Ok 모습. 실제로 콘솔 5개를 열고 요청해보면 하나씩 요청이 들어온다.
<img src="https://velog.velcdn.com/images/petit-prince/post/3e139875-3623-4d2a-a47c-d7d09fb5a328/image.png" alt=""></li>
</ul>
<h1 id="1-시나리오-3-리-테스트">1. 시나리오 3 리-테스트</h1>
<h2 id="1낙관락">1.낙관락</h2>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/85645452-7e8b-4619-8025-985bcbd470d2/image.png" alt=""></p>
<ul>
<li>VM
<img src="https://velog.velcdn.com/images/petit-prince/post/c37e285e-d54c-4d77-921b-7e238481a0db/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/d54f554c-71d8-44b7-86fb-3cd3093555ca/image.png" alt=""></li>
</ul>
<ul>
<li>DB
<img src="https://velog.velcdn.com/images/petit-prince/post/b4243c8a-d88f-4be8-aff8-d38ef845d52f/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/a5f5b744-95e6-433c-8cae-216abef4a182/image.png" alt=""></li>
</ul>
<h2 id="2비관락">2.비관락</h2>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/409861a3-a2fd-4442-9d1e-499859471704/image.png" alt=""></p>
<ul>
<li><p>VM
<img src="https://velog.velcdn.com/images/petit-prince/post/8446501a-6569-4faa-85ec-18484cb91258/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/37dc8fc9-256b-415b-b028-4ee7bb833990/image.png" alt=""></p>
</li>
<li><p>DB
<img src="https://velog.velcdn.com/images/petit-prince/post/606261ed-ed40-4ee4-9aab-6076251ce68f/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/257cb443-e27f-4044-933e-e856e3520e3c/image.png" alt=""></p>
</li>
</ul>
<h2 id="분산락낙관락">분산락+낙관락</h2>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/1d83dc2e-ff2e-4c59-a047-8888443e48a6/image.png" alt=""></p>
<ul>
<li>VM
<img src="https://velog.velcdn.com/images/petit-prince/post/be672432-d3e7-42b3-b2de-da9605225088/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/c6993412-775b-4e1e-83aa-980c65ba7e76/image.png" alt=""></li>
</ul>
<ul>
<li>DB
<img src="https://velog.velcdn.com/images/petit-prince/post/e713ca26-2f5a-450c-ad07-749878b4e272/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/90da8707-da80-407e-a19a-de9ee69ec430/image.png" alt=""></li>
</ul>
<table>
<thead>
<tr>
<th></th>
<th>동시성 제어</th>
<th>평균 요청 대기시간</th>
</tr>
</thead>
<tbody><tr>
<td>낙관락 - 시나리오 3</td>
<td>O</td>
<td>1.06s</td>
</tr>
<tr>
<td>비관락 - 시나리오 3</td>
<td>O</td>
<td>6.54s</td>
</tr>
<tr>
<td>분산락 - 시나리오 3</td>
<td>O</td>
<td>1.17s</td>
</tr>
</tbody></table>
<h1 id="결론">결론</h1>
<ul>
<li>비관락 사용시 많은 대기(blocking)가 생긴다.</li>
<li>아직 분산락과 낙관락의 차이로 인한 결과는 없다.</li>
</ul>
<hr>
<p><a href="https://velog.io/@petit-prince/%EB%8D%94-%EC%A7%84%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4">더 진한 동시성 제어 - 1</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[더 진한 동시성 제어 - 1 ]]></title>
            <link>https://velog.io/@petit-prince/%EB%8D%94-%EC%A7%84%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4</link>
            <guid>https://velog.io/@petit-prince/%EB%8D%94-%EC%A7%84%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4</guid>
            <pubDate>Thu, 31 Oct 2024 21:23:36 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가기전">들어가기전</h1>
<p>동시성 제어를 위해 어떤 방법을 쓰시나요?</p>
<p><code>낙관락</code>, <code>비관락</code>, <code>분산락</code> 등등등..</p>
<p>해당 글은 동시성 제어 방법들의 장/단점을 알아보기 위해, 실제로 테스트 하는 과정을 담았습니다.</p>
<p>그러기 앞서 기존 구현한 콘서트 예약을 좀 더 깊게 소개해봅니다.</p>
<h1 id="0-콘서트-예약에-필요한-것">0. 콘서트 예약에 필요한 것</h1>
<p>콘서트 예약은 <strong>유저</strong>와 <strong>좌석</strong>이 필요합니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/0c6d2a13-c439-46b9-b8d4-3cf71eae809a/image.png" alt=""></p>
<p>유저는 <strong>&quot;예약되지 않은 좌석&quot;</strong> 을 예약할 수 있습니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/afb98c93-ea4f-483d-a57a-255bc846c388/image.png" alt=""></p>
<p>이때, 동시성 제어를 위해 <code>좌석(Seat)</code>에 낙관락을 겁니다.</p>
<pre><code class="language-java">    @Transactional
    public MakeReservationResult reserve(MakeReservationCommand command) {
        Long seatId = command.seatId();
        Long userId = command.userId();

        Seat lockedSeat = seatRepository.findWithLockById(seatId).orElseThrow(() -&gt;
                new EntityNotFoundException(&quot;해당 좌석은 존재하지 않습니다. seatId = &quot; + seatId)
        );

        User user = userRepository.findById(userId).orElseThrow(() -&gt;
                new EntityNotFoundException(&quot;해당 유저는 존재하지 않습니다. userId = &quot; + userId)
        );

        // TODO in new Reservation() -&gt; Events.raise(new SeatReservationEvent) with Transaction
        lockedSeat.reserveSeat();
        Reservation reservation = new Reservation(ReservationStatus.RESERVED, lockedSeat.getId(), user.getId());
        Reservation saved = reservationRepository.save(reservation);
        return new MakeReservationResult(saved.getId());
    }</code></pre>
<p>위 코드에서 확인할 수 있듯이, <strong><code>예약</code></strong> 은 새로운 데이터 저장하는 것이므로 락을 걸지 않습니다.</p>
<p>다음과 같이 <strong><code>좌석</code></strong>에 낙관락을 걸어 동시성을 제어합니다.</p>
<pre><code class="language-java">public class Seat {
    ...
    @Version
    private Integer version;
}</code></pre>
<p>예약에 성공하면, <strong><code>예약</code></strong> 정보가 DB에 저장된다. </p>
<h1 id="1-낙관락-선택-이유">1. 낙관락 선택 이유</h1>
<p>한 좌석에 여러 예약 요청이 몰려들면 <strong>단 한 사람만 좌석 예약</strong>에 성공해야 합니다.
콘서트 대기자들이 한 번에 몰려들어 <code>좌석 예약</code> 버튼을 누르면, 가장 첫 번째로  성공한 유저는 좌석 예약에 성공합니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/aaf5c305-9edf-4321-bc79-2c75e3db50ce/image.png" alt=""></p>
<p>하지만, <strong><em>해당 내용이 아직 DB에 반영되기 전에 요청한 이용자</em></strong>는 그 사실을 모르고 &quot;<strong>이선좌</strong>&quot;가 아닌, 비어있는 좌석이라 판단하여 예약 요청을 보내게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/698b5bb9-2b37-4464-bd25-66247ed20b63/image.png" alt=""></p>
<p>여기서 <code>낙관락</code>이 도와줍니다.
<code>낙관락</code>을 건다면, 트랜잭션이 끝나는 시점의 <code>update</code> 쿼리에 다음과 같이 처음 <code>select</code>해왔었던 <code>version</code> 정보를 확인하면서, <code>version</code>을 변경합니다.</p>
<p>그러므로, 처음 예약에 성공한(<code>version update</code> 쿼리를 성공시킨) 그 이외 사람들은 실패합니다.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/d87f8311-6c09-4d6f-96b9-bdd60cf07696/image.png" alt=""></p>
<p>실제로 확인해보겠습니다. </p>
<p>다른 유저가 <strong>해당 좌석이 점유됐다는 사실을 모른채</strong>(트랜잭션이 끝나기 전에) 똑같이 요청을 보내면 어떻게 될까요?</p>
<p>해당 서비스에 2개의 요청을 동시에 보내서 로그를 찍어보면 쓰레드 <code>[pool-1-thread-1]</code>과 <code>[pool-1-thread-2]</code> 둘 모두 새로운 예약 요청 <code>insert</code>, 그리고 좌석 선점 요청 <code>update</code>를 보내고 있습니다. </p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/97dfe04d-221c-4f7b-8f36-b27873cb65b3/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/eff465af-7680-4654-9627-439ad9481a45/image.png" alt=""></p>
<p>여기서 두 개의 <code>update</code>쿼리가 나가면, 아래 그림처럼 진행됩니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/607505c6-f0f0-4729-9f63-b62ce9940b5e/image.png" alt=""></p>
<p>조금 더 늦게 시작한 <code>[pool-thread-2]</code>의 트랜잭션이 <code>ObjectOptimisticLockingFailureException</code>이 발생하며, 롤백됩니다.</p>
<blockquote>
<p>언두 로그에 락을 걸 수 없기에 락을 거는 동작은 모두 언두 로그가 아닌 <strong>실제 테이블</strong>에 적용한다. 이 경우에도 MySQL이 <strong>Repeatable Read</strong> 격리 수준을 갖고 있어도 <strong>update</strong>가 되는 것을 확인할 수 있다.</p>
</blockquote>
<p>그러므로, 가장 <strong>첫번째에 업데이트에 성공한 유저만 좌석 예약 선점</strong>이 가능하므로, 낙관적 락을 쓰는게 가장 적당해보였습니다.</p>
<p>특히 가장 가까운 대안인 <code>비관락</code>을 사용하면, <strong>블락 대기 시간이 늘어나</strong> 성능에 문제가 있다고 판단했습니다.</p>
<p>만약 <strong>낙관락 예외</strong>가 발생한다면, 무조건 &quot;좌석 예약 경합에 실패한 유저&quot;라고 판단해서, 재시도 로직도 넣지 않았습니다. (이건 잘못된 가정이어서 수정이 필요하다.)</p>
<p><strong>정말 그럴까요?</strong></p>
<p>낙관락, 비관락 나아가서 Redis를 이용한 분산락까지 성능을 비교해보겠습니다.</p>
<h1 id="2-환경-설정">2. 환경 설정</h1>
<p>비관락과 비교하기 전 환경부터 설정합니다.
요구사항은 두 가집니다.</p>
<ol>
<li>메트릭 확인이 가능할 것</li>
<li>제한된 리소스를 가질 것 </li>
</ol>
<p>그러므로 저렴한 클라우드 인스턴스를 이용하여 테스트합니다.
OCI를 이용하고 테스트 환경은 다음과 같습니다.</p>
<ol>
<li>VM</li>
</ol>
<ul>
<li>Shape: VM.Standard.A1.Flex</li>
<li>OS: Canonical Ubuntu 24.04</li>
<li>Network bandwidth (Gbps): 1</li>
<li>vCPU: 1 </li>
<li>Mem: 6GB</li>
</ul>
<ol start="2">
<li>Database (PostgreSQL)</li>
</ol>
<ul>
<li>Shape: VM.Standard3.Flex</li>
<li>vCPU: 2</li>
<li>Mem: 32GB(?) -- 최소 단위</li>
<li>PostgreSQL version: 14.11</li>
<li>Performance tier: 75K IOPS</li>
<li>Database system type: Single node</li>
</ul>
<ol start="3">
<li>Cache (Redis)</li>
</ol>
<ul>
<li>node: 1</li>
<li>mem: 2GB</li>
</ul>
<p>망은 아래와 같은 구성입니다. (OCI인점을 제외하면)
<img src="https://velog.velcdn.com/images/petit-prince/post/2340ba5d-28f4-4a63-b718-02e9201ec5dd/image.png" alt=""></p>
<h1 id="3-성능-비교">3. 성능 비교</h1>
<p>선택할 수 있는 성능 지표는 다음과 같습니다.</p>
<ul>
<li>CPU Util</li>
<li>Mem Util</li>
<li>Disk R/W IO</li>
<li>Load</li>
<li>(DB) CPU Util</li>
</ul>
<h2 id="1-한-유저가-한-좌석에-짧은-시간안에-500번-요청">1. 한 유저가 한 좌석에 짧은 시간안에 500+번 요청</h2>
<p>테스트 환경은 10초안에 한 유저 아이디를 갖는 유저가 많은 양의 예약 요청을 한 좌석에 보냅니다. 
&quot;이미 선택된 좌석입니다&quot;가 많이 발생하여 테스트 결과가 크게 재밌지 않을 수 있습니다.</p>
<pre><code class="language-js">/**
 * 한_유저가_한_좌석을_동시에_500+번_예약시도한다_그러나_예약한사람은_오직한명이다
 */

import http from &#39;k6/http&#39;;
import {sleep} from &quot;k6&quot;

var BASE_URL = `http://${__ENV.TARGET_HOST}:8080/`

const TARGET_TPS = 300; // 목표 TPS 설정

export let options = {
    scenarios: {
        burst_test: {
            executor: &#39;constant-vus&#39;,
            vus: TARGET_TPS, // 동시에 요청을 보낼 VU 수 설정
            duration: &#39;10s&#39;, // 지속 시간 (한번의 집중 burst를 위해 짧은 시간 설정)
        }
    },

    thresholds:
        {
            http_req_duration: [&#39;p(95)&lt;100&#39;],
        }
}

function getUri() {
    return &quot;reservations&quot;;
}

export default function () {
    let url = BASE_URL + getUri()

    //set authorization Bearer token
    let headers = {
        &#39;Content-Type&#39;: &#39;application/json&#39;,
        &quot;Wait-Token&quot;: `1`,
    };

    let body = {
        userId: 1,
        seatId: 1
    }

    http.post(url, JSON.stringify(body), {headers});
    sleep(1);
};</code></pre>
<h3 id="1-낙관락-결과">1. 낙관락 결과</h3>
<p>테스트는 803개 요청 중 802개가 실패하고, <strong>1개</strong>가 성공했다.</p>
<ul>
<li>k6 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/90288005-a194-496a-8867-4dd9e5c296e5/image.png" alt=""></li>
</ul>
<ul>
<li><p>VM 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/a9ed9070-c6ab-4e3d-a54d-a771614ec963/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/e23b5e45-5889-4e4a-b722-d98d305090d3/image.png" alt=""></p>
</li>
<li><p>DB 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/606c84b5-f743-4f90-b4e6-9f1e506707b1/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/1dd2b5e7-30b3-49f6-85cf-e7e52cee311f/image.png" alt=""></p>
</li>
</ul>
<ul>
<li>예약 1개가 성공된 DB 쿼리 결과
<img src="https://velog.velcdn.com/images/petit-prince/post/90ad0aae-ee2d-4782-9958-e5a01ce566f3/image.png" alt=""></li>
</ul>
<h3 id="2-비관락-결과">2. 비관락 결과</h3>
<p>테스트는 779개의 요청 중 778개가 실패하고, <strong>1개</strong>가 성공했습니다.</p>
<ul>
<li>k6 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/4f7e4026-9575-4d04-a797-4b29ec36b269/image.png" alt=""></li>
</ul>
<ul>
<li>VM 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/1904a797-91a5-4506-8b13-f29429cca3db/image.png" alt=""></li>
</ul>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/33a83299-c4fd-41e5-a924-7ffcf818aeeb/image.png" alt=""></p>
<ul>
<li>DB 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/8defbd1f-16cd-4332-b7c7-e8ccabd33d1f/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/36a57ba1-66b0-4d64-b588-b9324b3ec86d/image.png" alt=""></li>
</ul>
<ul>
<li>예약 1개가 성공된 DB 쿼리 결과
<img src="https://velog.velcdn.com/images/petit-prince/post/64373b29-c561-4ad1-ab08-096552f2d70e/image.png" alt=""></li>
</ul>
<h3 id="결론">결론</h3>
<ul>
<li>낙관락<ul>
<li>http 요청 대기(http_req_waiting)가 예상보다 시간이 길었습니다.</li>
<li>Load(Cpu 대기)때문으로 보입니다다. (2vCPU면 다른 결과일 듯으로 보임)
현재 테스트로는 큰 차이를 느끼기가 어려운 것 같습니다.
특히, DB 메트릭은 거의 차의가 없습니다.(DB 성능이 좋기도 합니다.)</li>
</ul>
</li>
</ul>
<h2 id="2-한-유저가-여러-좌석에-짧은-시간안에-500번-요청">2. 한 유저가 여러 좌석에 짧은 시간안에 500+번 요청</h2>
<h3 id="1-낙관락-결과-1">1. 낙관락 결과</h3>
<p>테스트는 830개 요청 중 680개가 실패하고, <strong>150개</strong>가 성공했습니다. (좌석수 150개)</p>
<ul>
<li><p>k6 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/9a8e5f09-3b8a-4c1c-ba22-5897c8ca42e3/image.png" alt=""></p>
</li>
<li><p>VM 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/f323bcef-25e7-439b-8fec-de0c33dff5e4/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/51826c86-1652-4d66-b50d-4d0fb1ca9976/image.png" alt=""></p>
</li>
<li><p>DB 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/738990c4-95be-4b7d-81e2-a8652bfe1d18/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/ebab1533-634f-43d5-b687-ef99ed9817b1/image.png" alt=""></p>
</li>
<li><p>예약 150개가 성공된 DB 쿼리 결과
<img src="https://velog.velcdn.com/images/petit-prince/post/cf073561-4c3f-4627-b19c-27f0b43d7476/image.png" alt=""></p>
</li>
</ul>
<h3 id="2-비관락-결과-1">2. 비관락 결과</h3>
<p>테스트는 819개 요청 중 669개가 실패하고, <strong>150개</strong>가 성공했습니다. (좌석수 150개)</p>
<ul>
<li><p>k6 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/0d45ee3e-4a16-498e-91b9-4b601d852e01/image.png" alt=""></p>
</li>
<li><p>VM 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/7980470a-2e15-401e-b564-17c53969d668/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/8785aadf-fd69-448e-a4e2-90d79f3df15b/image.png" alt=""></p>
</li>
<li><p>DB 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/1bdbfa35-9f46-4490-bc05-48c75e9ad9b6/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/55de75d5-2746-41bf-861a-ab30fdfcd33a/image.png" alt=""></p>
</li>
<li><p>예약 150개가 성공된 DB 쿼리 결과
<img src="https://velog.velcdn.com/images/petit-prince/post/ad1f7a0f-dd90-4c55-95ed-93c40f5a58dc/image.png" alt=""></p>
</li>
</ul>
<h3 id="결론-1">결론</h3>
<p>(아마도) VM/DB의 성능이 너무 좋아서, 1000개 요청으로는 실질적으로 낙관/비관락 차이를 느끼기가 어려웠습니다. </p>
<p>그래서 이용자수 3만, 좌석 수를 10만개로 바꾸어, 다시 테스트 해봤습니다.</p>
<h2 id="3-한-유저가-여러-좌석에-짧은-시간3분안에-300000번-요청">3. 한 유저가 여러 좌석에 짧은 시간(3분)안에 ~=300,000번 요청</h2>
<pre><code class="language-js">const TARGET_TPS = 30_000; // 목표 TPS 설정
const MAX_SEAT_ID = 100_000; // seatId의 최대값 설정</code></pre>
<h3 id="1-낙관락-결과-2">1. 낙관락 결과</h3>
<p>테스트는 302,771개 요청 중 288,996개가 실패하고, <strong>13,775개</strong>가 성공했습니다. (좌석수 100,00개, 요청하는 좌석은 3만 이하로 중복되는 번호입니다.)</p>
<ul>
<li><p>k6 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/c6241ffa-0d99-4043-97da-3077750711ba/image.png" alt=""></p>
</li>
<li><p>VM 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/7a36380e-f978-4104-8e16-fbbb29c65a91/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/b7c6f01d-56cf-4b7b-8f7d-91211ec219ed/image.png" alt=""></p>
<ul>
<li>소리치는 CPU</li>
</ul>
</li>
<li><p>DB 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/69371a50-5be3-46b0-8533-86d62b18afce/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/669980be-7ea8-47c6-a162-34bbf5006150/image.png" alt=""></p>
</li>
</ul>
<ul>
<li>예약 20,022개가 성공된 DB 쿼리 결과
<img src="https://velog.velcdn.com/images/petit-prince/post/aa97b818-c4e4-4823-b641-8bba1bb6e062/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/cfc6a992-9be0-4265-846b-82ff8ff7e2a7/image.png" alt=""></li>
</ul>
<h3 id="2-비관락-결과-2">2. 비관락 결과</h3>
<p>테스트는 291,894개 요청 중 275,764개가 실패하고, <strong>16,130개</strong>가 성공했습니다. (좌석수 100,00개이다, 요청하는 좌석은 3만 이하로 중복되는 번호이다)</p>
<ul>
<li>k6 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/3d065008-f8d6-45d0-a5fb-69f6794e0a73/image.png" alt=""></li>
</ul>
<ul>
<li><p>VM 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/79981c71-6886-42e9-9573-5996dc76f5da/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/1d84eea1-5212-44b1-8bc8-26d9ddd2812a/image.png" alt=""></p>
</li>
<li><p>DB 성능 지표
<img src="https://velog.velcdn.com/images/petit-prince/post/cd09f33e-f939-447a-8046-518b03ea0481/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/26e35990-a811-49ba-b085-637614429b70/image.png" alt=""></p>
</li>
</ul>
<ul>
<li>예약 19,885개가 성공된 DB 쿼리 결과
<img src="https://velog.velcdn.com/images/petit-prince/post/30c5727c-0f91-44b8-bfa6-0381f37609f3/image.png" alt=""></li>
</ul>
<h2 id="결론-2">결론</h2>
<ul>
<li>VM 성능이 너무 낮기에 정확한 결과를 얻을 수 없었습니다.</li>
</ul>
<hr>
<h2 id="개인-생각">개인 생각</h2>
<ul>
<li>낙관락은 정상적으로 요청을 마쳤음에도 http 요청 결과의 실패 비율이 왜 더 높을까?<ul>
<li>(낙) http 성공/실제: 13,775 / 20,222 = <strong>68%</strong></li>
<li>(비) http 성공/실제: 16,130 / 19,885 = <strong>81%</strong></li>
</ul>
</li>
<li>비관락은 생각보다 <strong>텍스트</strong>짧게 잡나..? 라는 추측을 해봤다.</li>
<li>(아쉬운점) DB 성능이 너무 좋아서 메트릭을 보고 판단 내리는것이 (나는) 불가능하다. 100만건 데이터도 이렇게 쉽게 적재하는 줄 몰랐다.</li>
<li>(테스트 부정합) 테스트는 1~100,000까지의 수를 랜덤으로 돌리는것이 아니라 순차적으로 올라간다. 이 부분에서 실제 예약 패턴과는 달라 테스트의 실질성은 조금 떨어진다.</li>
<li>(유량 제어) 실험하며 대기열의 필요성 더 잘 느꼈다.</li>
<li>DB는 비싼 자원이다. (하루 안되서 4천원 나옴)
<img src="https://velog.velcdn.com/images/petit-prince/post/ba9f704e-07f8-446a-bf3d-c19309d30105/image.png" alt=""></li>
</ul>
<hr>
<h2 id="알게된-점">알게된 점</h2>
<blockquote>
<ol>
<li>next key락
Reservation 엔티티 자체에도 생성시 락을 걸 수 있다.</li>
</ol>
</blockquote>
<blockquote>
<ol start="2">
<li>gradle
OpenAPI Generator 7.6 버전 기준 gradle 7.6 버전 이하 오류, 8.10 버전 오류 발생
ubuntu 기본 gradle 버전은 4.4이므로 sdk를 이용해야 한다.</li>
</ol>
</blockquote>
<blockquote>
<ol start="3">
<li>redis 접속 prefix
oci Cache 접속시 접두사 rediss:// </li>
</ol>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[DBMS와 트랜잭션]]></title>
            <link>https://velog.io/@petit-prince/%EA%B0%9C%EB%AF%B8-%EB%82%A0%EA%B0%9C-%EA%B8%B0%EB%A5%B4%EA%B8%B0-DBMS%EC%99%80-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98</link>
            <guid>https://velog.io/@petit-prince/%EA%B0%9C%EB%AF%B8-%EB%82%A0%EA%B0%9C-%EA%B8%B0%EB%A5%B4%EA%B8%B0-DBMS%EC%99%80-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98</guid>
            <pubDate>Mon, 28 Oct 2024 15:23:16 GMT</pubDate>
            <description><![CDATA[<h1 id="1-질문">1. 질문</h1>
<blockquote>
<p>만약에 락을 걸지 않고, 트랜잭션도 걸지 않고, 한 유저에 업데이트 쿼리를 동시에 따닥 날리면 동시에 실행이 되나요?</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/6b59ae63-f64c-42e9-b57f-97e454424124/image.png" alt="남들이 Yes라고 할때 당당히 No라고 외치는 새77l,,"></p>
<blockquote>
<p>여기서 <code>트랜잭션</code>도 걸지 않고는 트랜잭션 커밋을 하지 않고 플러쉬를 한 중간 상태라고 정의한다.</p>
</blockquote>
<p>그럼 실제로 수행해보자.</p>
<h2 id="mysql">MySQL</h2>
<p>트랜잭션을 <code>manual</code>로 변경뒤, 동시에 한 레코드에 업데이트 요청을 날린다.</p>
<pre><code class="language-log">[40001][1205] Lock wait timeout exceeded; try restarting transaction</code></pre>
<p>MySQL은 조금 더 명시적으로 <code>Lock wait timeout exceeded</code>라고 표현해준다.</p>
<p><strong>답은 X-lock이 걸려 있어서다!</strong></p>
<h1 id="2-database-lock의-종류">2. Database Lock의 종류</h1>
<h2 id="1-공유락-s-lock-shared-lock">1. 공유락 (S-lock, Shared Lock)</h2>
<ul>
<li>데이터 읽을 때 사용하는 <code>lock</code></li>
<li><strong>공유락</strong> 끼리는 여러 사용자가 동시에 <code>읽기</code> 가능</li>
<li><strong>공유락</strong>이 먼저 설정된 데이터에 <strong>배타락</strong> 불가능</li>
</ul>
<h2 id="2-배타락-x-lock-exclusive-lock">2. 배타락 (X-lock, Exclusive Lock)</h2>
<ul>
<li>데이터 변경시 사용되는 <code>lock</code></li>
<li>트랜잭션이 완료될 때 까지 유지된다 (e.g. <code>select for update</code>)</li>
<li>배타락이 적용 된다면, <strong>다른 리소스는 접근 못하고 대기</strong></li>
<li>배타락은 이미 다른 트랜잭션 내에서 사용하고 있는 데이터에 접근해 Lock을 설정할 수 없음</li>
</ul>
<blockquote>
<p>&quot;아하! 위 1번에서는 같은 레코드에 업데이트 요청을 걸었고, 커밋하기 이전에 다른 리소스가 접근하려 해서 에러가 발생했구나!&quot;</p>
</blockquote>
<h1 id="3-db-격리-수준">3. DB 격리 수준</h1>
<p>위의 문제들을 <strong>정확히!</strong> 이해하려면 결국 DB의 격리 수준에 대해 알아야만 한다.</p>
<p>DB 격리 수준은 DBMS마다 다르다.
MySQL도 스토리지 엔진을 <code>MyISAM</code>을 쓰는지, <code>InnoDB</code>를 쓰는지, PostgreSQL을 쓰는지, DBMS마다, DBMS의 버전마다 다를 것이다.</p>
<p>아래 샘플 테이블과 함께 굉장히 유명한 4가지 격리 수준을 같이 알아보자.</p>
<pre><code class="language-sql">create table hhplus.concert
(
    id   int auto_increment
        primary key,
    name varchar(255) not null
);
</code></pre>
<p><strong>아래 실습은 모두 MySQL InnoDB 기준이다.</strong></p>
<h2 id="1-read-uncommitted">1. Read Uncommitted</h2>
<p>가장 약한 격리수준이다. 이름에서 알 수 있듯이, <strong>커밋되지 않은 것조차 읽는 격리 수준이다.</strong></p>
<p>커밋되지 않은 구문을 읽으면 어떤 문제가 생길까?</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/25128523-16de-4fbf-99f8-ca360e9e71d1/image.png" alt=""></p>
<p>그림으로 상황을 재현해보면, <code>트랜잭션1</code>이 시작되고, update 구문을 날린 뒤, 아직 커밋하지 않은 변경된 행에 접근해서 읽어왔다. </p>
<p>사실, <code>tx1</code>이 그대로 <code>commit</code> 된다면 큰 문제는 없을 것이다.
하지만 <strong>롤백</strong> 된다면? <code>tx2</code>는 <strong>잘못된 정보를 가지고 로직을 수행</strong>할 것이다. </p>
<p>이처럼 어떤 트랜잭션에서 처리한 작업이 <strong>완료(commit)되지 않았는데도</strong> 다른 트랜잭션에서 볼 수 있는 현상을 더티 리드(Dirty Read)라고 한다.</p>
<p>더티 리드가 허용되는 격리 수준이 <strong>Read Uncommitted</strong>이다.</p>
<h3 id="read-uncommitted---오손-읽기-부정합을-발생시키는-실습">Read Uncommitted - 오손 읽기 부정합을 발생시키는 실습</h3>
<pre><code class="language-sql">-- tx1
SELECT @@SESSION.transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

update concert set name=&#39;콘서트101&#39; where id=1;

--tx 2
SELECT @@SESSION.transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

select * from concert where id =1;</code></pre>
<ul>
<li>tx1이 커밋되지 않았음에도, Dirty Read(오손 읽기)가 발생하여 이름이 <code>콘서트101</code>로 바뀐 모습
<img src="https://velog.velcdn.com/images/petit-prince/post/7c723f92-b542-4dd8-95f2-faa942af4289/image.png" alt=""></li>
</ul>
<h2 id="2-read-committed">2. Read Committed</h2>
<p>오라클 DBMS에서 기본적으로 제공하는 격리 수준이며, 온라인 서비스에서 가장 많이 선택되는 격리 수준이다. 어떤 트랜잭션에서 데이터를 변경했다 하더라도, <code>COMMIT</code>이 완료된 데이터만 다른 트랜잭션에서 조회할 수 있기 때문이다.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/f9eacba1-6d04-4941-94b3-3ace36d5102f/image.png" alt=""></p>
<p><code>Read Committed</code>에서는 어떤 트랜잭션이 변경한 내용이 커밋되기 전까지는 다른 트랜잭션에서 그러한 변경 내역을 조회할 수 없다.</p>
<h3 id="read-committed---오손-읽기-부정합이-없음을-보이는-실습">Read Committed - 오손 읽기 부정합이 없음을 보이는 실습</h3>
<pre><code class="language-sql">-- tx1
SELECT @@SESSION.transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

update concert set name=&#39;콘서트101&#39; where id=1;

-- tx2
SELECT @@SESSION.transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

select * from concert where id =1;
</code></pre>
<ul>
<li>tx1이 커밋되지 않았으므로, Dirty Read(오손 읽기)는 발생하지 않았다.
<img src="https://velog.velcdn.com/images/petit-prince/post/7df56309-6c39-4751-b90e-32f71a364609/image.png" alt=""></li>
</ul>
<p>여기서 <code>Read Committed</code> 격리 수준에서도 <code>Non-Repeatable Read</code>가 불가능하다라는 부정합 문제가 있다.</p>
<blockquote>
<p>아니. 커밋된것만 잘 읽으면 됐지, 도대체 <strong>Non-Repeatable Read</strong> 가 뭘까!</p>
</blockquote>
<p>아래 다이어그램을 잠시 보자
<img src="https://velog.velcdn.com/images/petit-prince/post/47755afd-5545-4420-85c7-bb63e9e57cd8/image.png" alt=""></p>
<p>TX2가 먼저 트랜잭션을 시작했고, TX1이 트랜잭션을 이후에 시작하고 먼저 커밋했다. TX2는 한 트랜잭션에 처음 읽은 콘서트의 이름(name)과 두번째 읽은 콘서트의 이름이 다른 것을 봐야만 한다. </p>
<p>이게 의도된 동작이 맞는가? <strong>아니다!</strong></p>
<h3 id="read-committed---반복-읽기-불가non-repeatable-read-부정합을-보이는-실습">Read Committed - 반복 읽기 불가(Non-Repeatable Read) 부정합을 보이는 실습</h3>
<pre><code class="language-sql">-- tx1, tx2
SELECT @@SESSION.transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- tx2
begin;
select * from concert where id =1; -- 콘서트1 반환
-- tx1
begin;
update concert set name=&#39;콘서트101&#39; where id=1;
commit;
-- tx2
select * from concert where id =1; -- 콘서트101 반환
</code></pre>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/6b25c046-6dd6-452d-ad8d-fd628f1f53e5/image.png" alt="">
<img src="https://velog.velcdn.com/images/petit-prince/post/84fb34be-f15d-4ef0-a07d-eb23ebfc3a72/image.png" alt=""></p>
<h4 id="repeateable-read란">Repeateable Read란?</h4>
<p>사용자가 하나의 트랜잭션 내에서 똑같은 SELECT 쿼리를 실행했을 때는 <strong>항상 같은 결과를 가져와야한다.</strong></p>
<blockquote>
<p>🤔 음..이게 꼭 필요할까? 라고 생각한다면,
다른 트랜잭션에서 입금, 출금 처리가 계속 진행될 때 다른 트랜잭션에서 오늘 입금된 금액의 총합을 조회한다고 가정해보자.
그런데 &#39;Repeatable Read&#39;가 보장되지 않기 때문에, 총합을 계산하는 SELECT 쿼리는 실행될 때 마다 다른 결과를 가져올 것이다.
중요한 것은 <strong>사용중인 트랜잭션 수준에 의해 실행하는 SQL 문장이 어떤 결과를 가져오게 되는지 정확히 예측할 수 있어야 한다는 것이다</strong></p>
</blockquote>
<h4 id="트랜잭션-내에서-실행되는-select-문장과-트랜잭션-없이-실행되는-select-차이">트랜잭션 내에서 실행되는 SELECT 문장과 트랜잭션 없이 실행되는 SELECT 차이</h4>
<p><code>Read Committed</code> 수준에서는 트랜잭션 내에서 실행되는 SELECT 문장과, 트랜잭션 외부에서 실행되는 SELECT 문장의 차이가 별로 없다. 하지만 <code>Read Repeatable</code> 격리 수준에서는 기적으로 SELECT 쿼리 문장도 트랜잭션 범위내에서만 작동한다. 즉, <code>BEGIN</code> 명령으로 트랜잭션을 시작한 상태에서 온종일 동일한 쿼리를 반복해서 실행해봐도, 동일한 결과만 보게 된다. (아무리 다른 트랜잭션에서 그 데이트러를 변경하고 COMMIT을 실행하더라도 말이다)</p>
<h2 id="3-repeatable-read">3. Repeatable Read</h2>
<p><code>Repeatable Read</code>는 MySQL의 InnoDB 스토리지 엔진에서 기본으로 사용된다. 바이너리 로그를 가진 MySQL 서버에서는 최소 <code>Repeatable Read</code> 격리 수준 이상을 사용해야한다. 이 격리수준에서는 <code>Read Committed</code> 격리 수준에서 발생하는 <code>Non-Repeatable Read</code> 부정합이 발생하지 않는다.</p>
<p>InnoDB 스토리지엔진은 트랜잭션이 <code>Rollback</code> 될 가능성에 대비해 변경되기 전 레코드를 언두(Undo)공간에 백업해두고, 실제 레코드 값을 변경한다. 이런 변경 방식을 <strong>MVCC</strong>라고 한다.</p>
<p>모든 InnoDB 트랜잭션은 고유한 트랜잭션 번호(순차값)을 가지며, 언두 영억에 백업된 모든 레코드에는 변경을 발생시킨 트랜잭션의 번호가 포함돼 있다. 그리고 언두 영역의 백업된 데이터는 InnoDB 스토리지 엔진이 불필요하다고 판단하는 시점에 주기적으로 삭제한다.</p>
<p><code>Repeateable Read</code> 격리 수준에서는 MVCC를 보장하기 위해, 실행중인 트랜잭션 가운데 가장 오래된 트랜잭션 번호보다 트랜잭션 번호가 앞선 언두 영역 데이터의 삭제는 할 수 없다. 그렇다고 가장 오래된 트랜잭션 번호 이전의 트랜잭션에 의해 변경된 모든 언두 데이터가 필요한 것은 아니다. 더 정확하게는 <em>특정 트랜잭션 번호의 구간 내에서 백업된 언두 데이터</em>가 보존되어야 한다.</p>
<p>아래 그림과 함께 이해해보자
<img src="https://velog.velcdn.com/images/petit-prince/post/f9586831-19a1-4e87-8868-e8b8847afe78/image.png" alt=""></p>
<p>위의 반복 읽기 불가 부정합 예시와 다르게, 트랜잭션이 열린 상태에서 SELECT 요청을 하고 있으므로, 현재 트랜잭션 번호보다 _작은 번호_에서 변경된 것만 보게 된다.</p>
<blockquote>
<p>그림에서는 언두 영역의 백업 데이터가 하나처럼 보이지만, 사실 하나의 레코드에 대해 백업이 하나 이상 존재할 수 있다.
한 사용자가 트랜잭션을 시작한 뒤, 장시간 트랜잭션을 종료하지 않으면, 언두 영역이 백업된 데이터로 무한정 커질수도 있다.</p>
</blockquote>
<h3 id="repeatable-read---반복-읽기-부정합이-없음을-보이는-실습">Repeatable Read - 반복 읽기 부정합이 없음을 보이는 실습</h3>
<pre><code class="language-sql">-- tx1, tx2
SELECT @@SESSION.transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- tx2
begin;
select * from concert where id =1; -- 콘서트1 반환
-- tx1
begin;
update concert set name=&#39;콘서트101&#39; where id=1;
commit;
-- tx2
select * from concert where id =1; -- 콘서트1 반환
</code></pre>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/0f636ff8-6667-406e-8bcb-b0c54a83e99c/image.png" alt=""></p>
<p>두 쿼리 모두 &quot;콘서트1&quot;을 반환한다</p>
<p><code>Repeatable Read</code> 수준에서도 <code>Phantom Read</code> 부정합이 발생할 수 있다.
tx1이 테이블에 삽입하고, 도중에 tx2가 <code>SELECT FOR UPDATE</code>쿼리로 테이블을 조회할 때 어떤 결과가 나오는지 보자.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/1897b879-df93-4bb5-9db5-7e78b0baa56d/image.png" alt=""></p>
<p>다시. 사용자가 하나의 트랜잭션 내에서 똑같은 SELECT 쿼리를 실행했을 때는 <strong>항상 같은 결과를 가져와야한다.</strong></p>
<h3 id="repeatable-read---유령-읽기phantom-read-부정합을-보이는-실습">Repeatable Read - 유령 읽기(Phantom-Read) 부정합을 보이는 실습</h3>
<pre><code class="language-sql">-- tx1, tx2
SELECT @@SESSION.transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- tx2
begin;
select * from concert where id&gt;=2 for update; -- 콘서트2 반환
-- tx1
begin;
insert into concert values (null, &#39;콘서트3&#39;);
commit;
-- tx2
select * from concert where id&gt;=2; -- 콘서트2, 콘서트3 반환</code></pre>
<p>일반적인 MVCC를 지원하는 RDBMS는 위와 같이 작동하지만, MySQL은 다릅니다.</p>
<p>근데 <a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html">MySQL 공식문서 - 격리수준</a>에서, 갭 락/넥스트 키 락을 이용해 <code>Phantom Read</code> 부정합이 없도록 한다고 합니다.</p>
<p>그래서 실제 <code>insert into ...</code> 구문에서 락이 걸려버립니다.</p>
<p><code>Phantom Read</code>를 재현하려면 다음 순서와 같이 실행해야 합니다.</p>
<ol>
<li><code>tx2</code> - select </li>
<li><code>tx1</code> - insert</li>
<li><code>tx2</code> - select <strong>for update</strong></li>
</ol>
<pre><code class="language-sql">-- tx1, tx2
SELECT @@SESSION.transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- tx2
begin;
select * from concert where id&gt;=2 -- 콘서트2 반환
-- tx1
begin;
insert into concert values (null, &#39;콘서트3&#39;);
commit;
-- tx2
select * from concert where id&gt;=2 for update; -- 콘서트2, 콘서트3 반환</code></pre>
<h2 id="4-serializable">4. Serializable</h2>
<p>가장 단순하고 가장 엄격하다. 동시 처리 성능도 다른 트랜잭션 격리 수준보다 떨어진다. InnoDB 테이블에서 기본적으로 순수한 SELECT (INSERT ... SELECT 또는 CREATE TABLE) 은 아무런 레코드 잠금도 설정하지 않고 실행된다.</p>
<p>InnoDB 메뉴얼에서 자주 나타나는 <strong>Non-locking consistent read(잠금이 필요 없는 일관된 읽기</strong>라는 말이 이를 의미하는 것이다.</p>
<p>하지만 트랜잭션 수준이 <code>Serializable</code>로 설정되면 읽기 작업도 공유 잠금(S-lock)을 얻어야하며, 동시에 다른 트랜잭션은 그러한 레코드를 변경하지 못하게 된다.</p>
<p>즉, 한 트랜잭션에 읽고 쓰는 레코드를 다른 트랜잭션에서는 절대 접근할 수 없을 것이다.</p>
<p><strong>하지만 InnoDB 스토리지 엔진에서는 갭 락과 넥스트 키 락 덕분에 Repeatable Read 격리 수준에서도 이미 &quot;Phantom Read&quot;가 발생하지 않기 때문에&quot;</strong>  굳이 <code>Serializable</code>
을 사용할 필요성은 없어보인다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 백엔드 6기] "대용량 트래픽 경험"]]></title>
            <link>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-6%EA%B8%B0-%EB%8C%80%EC%9A%A9%EB%9F%89-%ED%8A%B8%EB%9E%98%ED%94%BD-%EA%B2%BD%ED%97%98</link>
            <guid>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-6%EA%B8%B0-%EB%8C%80%EC%9A%A9%EB%9F%89-%ED%8A%B8%EB%9E%98%ED%94%BD-%EA%B2%BD%ED%97%98</guid>
            <pubDate>Sun, 20 Oct 2024 07:09:32 GMT</pubDate>
            <description><![CDATA[<p><strong>대용량 트래픽..</strong></p>
<p>사실상 어딜가든 빠지지 않는 키워드! 🤬</p>
<p><del>(근데 본인은 애초에 트래픽 자체를 받고 있지도 않음)</del></p>
<p>아주 잠깐 시간을 되돌려 1주차부터 생각해보면</p>
<p><strong>왜 동시성 처리에 관하여 고민했을까?</strong>
<strong>왜 테스트 코드를 작성했을까?</strong>
<strong>왜 클린 아키텍쳐를 배울까?</strong></p>
<p>이런 질문들을 곰곰히 생각해봤다.</p>
<p>답은 <strong><del>이직하려면..</del>  좋은 개발자</strong>가 되기 위해서!</p>
<p>그럼 <strong>좋은 개발자</strong>란?</p>
<ul>
<li>가독성/유지보수성이 높은 코드를 작성할 줄 아는!</li>
<li>다양한 비지니스 요구사항에 맞는 코드를 재빠르게 작성하는!</li>
<li>많은 트래픽에서도 견고한 시스템을 만들 줄 아는!</li>
</ul>
<p>그런 개발자가 <strong><del>시장에서 매력적인</del> 좋은 개발자</strong>인 것 같다.</p>
<p><strong>[들어가기 전]</strong>
다음 용어가 무엇인지 알고, 설명할 수 있나요?</p>
<ul>
<li>대용량 트래픽 시스템 설계</li>
<li>대기열 시스템</li>
</ul>
<h1 id="1-기술-회고">1. 기술 회고</h1>
<h2 id="대기열-시스템">대기열 시스템</h2>
<p>예를 들어보자,</p>
<blockquote>
<p><strong>우이천</strong>은 <strong>블러디 프라이데이(줄여서 블프)</strong>라는 이벤트를 기획하는 이커머스 회사에 다니고 있다.
 <img src="https://velog.velcdn.com/images/petit-prince/post/f609c8d4-1f25-4dd5-bbda-d600a235d75d/image.png" alt="">
 <strong>블프</strong>는 엄청나게 유명한 이벤트이다.
<strong>블프 좌석</strong>을 예약하기 위해 사람들이 서로 실제로 <strong>피를 본다</strong>
당일 이벤트가 열리면 사람들이 마구마구 쏟아져서 모두 예약하려고 한다. 
 간단하게 DB 락을 이용하여 <strong>블프 이벤트 좌석</strong>을 예약하는 시스템을 만들어보니...</p>
</blockquote>
<blockquote>
<p><strong><em>한 번에 너무나도 많은 요청이 쏟아져 어플리케이션/DB가 죽는다</em></strong></p>
</blockquote>
<p><strong>어떻게 해결할까?</strong></p>
<p>사실 이 문제의 해결 방법은 굉장히 다양하지만,</p>
<p>우리는 <strong>대기열 시스템</strong>에 대하여 먼저 얘기해 보려고 한다.</p>
<h3 id="왜-대기열-시스템이-필요한가">왜 대기열 시스템이 필요한가?</h3>
<p>아래와 같이 작고 귀여운 시스템 아키텍쳐가 있다고 해보자.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/fec08239-02b4-44c6-b999-54cd7b975f70/image.png" alt=""></p>
<p>12시 떙! <em>블프 이벤트</em>가 시작됐다. 무슨 일이 일어날까?</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/40be986b-421e-4217-91ac-b09387a09c1c/image.png" alt=""></p>
<p>일단 블프 이벤트와 좌석에 대한 <strong>조회</strong> 요청이 엄청나게 올 것이다.
그럼 우리가 할 수 있는 방법은, 블프 이벤트/좌석 조회시스템, Slave DB를 스케일 아웃 하는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/6a35e725-d7b3-492b-b2b0-1c579ae8ffc7/image.png" alt=""></p>
<p>휴! 다행히도, 모든 요청을 어찌저찌 잘 처리했다. (그렇다 치자!)</p>
<p>자! 이제 모든 조회 요청을 끝난 이용자들이 다음으로 할 행동은 무엇일까?</p>
<p><strong>예약이다!</strong>
<img src="https://velog.velcdn.com/images/petit-prince/post/9a1bceca-23c0-4e9b-ab54-8319141457af/image.png" alt=""></p>
<p>예약이 오면 아래와 같이 어플리케이션과 DB가 힘들어한다.</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/60e34c56-3b89-4150-9115-d5c254d26602/image.png" alt=""></p>
<p>심지어, 예약은 <code>Write</code> 요청이기 때문에 <strong>Master를 스케일 아웃 하는 전략도</strong> 가져가지 못한다.</p>
<p>예약 이후 <strong>결제도 마찬가지일 것이다!</strong></p>
<p>(길었지만) 위와 같은 이유로, 시스템 내 트래픽을 조절할 수 있는 &quot;무언가&quot;가 필요하다. </p>
<p>&quot;무언가&quot;는 <strong>대기열 시스템</strong>이고, 이를 고급지게 <strong>유량 제어</strong>라고 표현한다.</p>
<blockquote>
<p>물론 마스터를 미리 스케일 업 하는 방법, 샤딩, 파티셔닝, 이벤트 소싱 등등이 있을 수 있다.
그러나 굉장히 많은 공수/시스템 리소스가 필요하게 된다.
또한 기술적 성숙도가 부족한 조직에서는 시도 자체가 어려울 수도 있다.</p>
</blockquote>
<h3 id="어떻게-구현할-것인가">어떻게 구현할 것인가?</h3>
<p>먼저, 방식부터 얘기해보자. 
유량제어는 크게 두 가지 방식이 있다.</p>
<h4 id="1-은행-창구식">1. 은행 창구식</h4>
<ul>
<li>항상 총 N명 이하의 이용자가 시스템을 이용할 수 있도록 한다.</li>
</ul>
<h4 id="2-놀이-공원식">2. 놀이 공원식</h4>
<ul>
<li>주기적인 시간마다 추가적으로 N명에게 시스템을 이용할 수 있도록 한다.</li>
</ul>
<p>자. 그러면 실제로 DB로 구현은 어떻게 해야할 까?</p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/545005c5-4415-4f56-9288-fcaf503dd4f8/image.png" alt=""></p>
<p>이런 테이블 하나를 만들고, <code>userId</code>값을 가진 요청을 받으면 <code>waitId</code>라는 특정한 고유 Id를 클라이언트에게 헤더에 담아 반환한다.</p>
<p>그런 뒤, 스케쥴러(배치)를 돌려, <code>status(WAIT, ACTIVE, EXPIRE)</code> 값을 <code>ACTIVE</code>로 업데이트해, 이용 권한을 부여한다.</p>
<p>은행 창구식이라면 다음과 같은 로직을 이용할 수 있다.</p>
<blockquote>
<p>다음 입장 인원 = (시스템에서 총 유지되어야 하는 인원 - 현재 ACTIVE 인원)</p>
</blockquote>
<p>그런 뒤, 아래와 같은 스케쥴러를 돌려 ACTIVE 시킨다.</p>
<pre><code class="language-java">@Slf4j
@Component
@RequiredArgsConstructor
public class QueuePolicyScheduler {
    private final QueuePolicy queuePolicy;

    @Scheduled(fixedDelay = 1000)
    public void activate() {
        queuePolicy.activate();
    }

    @Scheduled(fixedDelay = 10000)
    public void expire() {
        queuePolicy.expire();
    }
}</code></pre>
<p>놀이 공원식이라면, <code>내 입장 순서까지 시간</code>를 계산할 수 있다.</p>
<blockquote>
<p>내 입장순서 시간 = (내 번호 - 마지막에 들어간 사람 번호)/(한 번에 들어가는 사람 수)*(스케쥴러가 도는 주기)</p>
</blockquote>
<p>두 방식 각각 장단이 있다.</p>
<h4 id="특징">특징</h4>
<ul>
<li><p>은행창구</p>
<ul>
<li>Active 된 지 특정 시간이 지난 유저는 Expire시킨다.</li>
<li>스케쥴러가 돌면서 ACTIVE인원 - 최대 N명만큼 권한을 부여한다(ACTIVE)</li>
<li>유량 제어가 타이트하게 가능하다.</li>
<li>제한적이고, 유동적이지 못한 재원을 갖고 있다면, 은행 창구식이 유리하다.</li>
<li>그러나 처리량은 놀이공원식보다 떨어질 수 있다.</li>
</ul>
</li>
<li><p>놀이공원</p>
<ul>
<li>스케쥴러가 돌면서 N명씩 Active시킨다.</li>
<li>커넥션 풀이 터질 수도 있다.</li>
<li>스케일러블한 재원을 갖고 있고, 처리량을 높이려면 놀이공원식이 유리하다.</li>
</ul>
</li>
</ul>
<h2 id="마치며">마치며</h2>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/2f8276d4-aad2-4852-a39a-45e609cdb733/image.png" alt=""></p>
<p>물론, 실전에서는 <code>Redis + 분산락</code>이 국밥마냥 든든하게 사용하는 것 같다. 
하지만, 김영한님 강좌처럼 차근 차근 시스템을 발전시켜보려는 것 같다!</p>
<h1 id="2-kpt">2. KPT</h1>
<h2 id="keep">Keep</h2>
<ul>
<li>열심히 하셨잖아 한잔해</li>
</ul>
<h2 id="problem">Problem</h2>
<ul>
<li>시간 관리 ㅠㅠ</li>
</ul>
<h2 id="try">Try</h2>
<ul>
<li>뽀모도로 타이머를 적극 활용해보자 ^^</li>
</ul>
<h2 id="이번-챕터를-시작하며-꼭-해내고-싶었던-목표">이번 챕터를 시작하며 꼭 해내고 싶었던 목표</h2>
<ul>
<li>와 OpenAPI Spec 저거 간편해보이는데 직접해봐야지!</li>
<li>일단 주어진 스펙들을 잘 구현해보자! (구현이 또 ㅎㅎ..)</li>
</ul>
<h2 id="이번-챕터를-마무리하며-가장-기억에-남는-성취">이번 챕터를 마무리하며 가장 기억에 남는 성취</h2>
<ul>
<li>거의 모든 시스템을 작성 한뒤, 통합테스트 돌렸는데 테스트에 초록불이 뜬 그 순간!</li>
</ul>
<h2 id="이번-챕터에서-반드시-이뤘으면-했는데-이루지-못한-것">이번 챕터에서 반드시 이뤘으면 했는데 이루지 못한 것</h2>
<ul>
<li>사실.. 포인트 충전/조회와 결제 시스템은 못했다..</li>
</ul>
<h2 id="내가-강화해야-할-강점-중-가장-중요하다고-생각하는-한-가지">내가 강화해야 할 강점 중 가장 중요하다고 생각하는 한 가지</h2>
<ul>
<li>조금 더 깊게 파보기</li>
<li>조금 더 생각을 정리/정돈한 뒤 말하고, 글을 써보기</li>
</ul>
<hr>
<h1 id="항해에-참가하려는-당신">항해에 참가하려는 당신!</h1>
<p>혹시 <strong>[들어가기전]</strong>의 답변이 궁금하신가요!?
글을 읽다보니 <strong>&#39;아..! 나도 저거 알아야하는데!&#39;</strong> 하진 않으신가요?!</p>
<p>여기까지 글을 읽으셨다면, 아마 <strong>항해 플러스 과정</strong>에 관심있으신 분이라고 생각합니다.</p>
<p>궁금한 점이 있으시다면 저에게 편하게 댓글 남겨주세요!
혹은 <code>highestbright98@naver.com</code>으로 메일을 남겨주셔도 굉장히 빨리 답변한답니다!</p>
<p>항해 플러스 백엔드에 관련해서 궁금하신점이 있다면 언제든 편하게 말씀주세요!</p>
<p>그리고 글이 도움됐다면, 등록하실때 제 추천인 코드 <strong>[9MPLfu]</strong> 입력하면,
<strong>20만원 할인의 혜택</strong>이 있답니다! (물론 저에게도 혜택이 있습니다! 😉)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[싫어하는 사람과 일하는 방법]]></title>
            <link>https://velog.io/@petit-prince/%EC%8B%AB%EC%96%B4%ED%95%98%EB%8A%94-%EC%82%AC%EB%9E%8C%EA%B3%BC-%EC%9D%BC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@petit-prince/%EC%8B%AB%EC%96%B4%ED%95%98%EB%8A%94-%EC%82%AC%EB%9E%8C%EA%B3%BC-%EC%9D%BC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Mon, 14 Oct 2024 08:56:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/petit-prince/post/642a9cfc-08e1-4914-9dc6-08b0f9dbd398/image.png" alt=""></p>
<p>2022년 - CBO 장인성 | 이게 무슨 일이야! 컨퍼런스
<a href="https://www.youtube.com/watch?v=7_3MqiVr9Sw&amp;t=16s">유튜브 원본 링크</a>를 보고 정리한 글</p>
<p>같이 일하는 사람들과의 관계
협업하는 사람들과 일하는 것은 어렵다.</p>
<h1 id="🙂-퇴사하는-이유-1위-번아웃이-오는-이유">🙂 퇴사하는 이유 1위, 번아웃이 오는 이유</h1>
<p>상사랑 안맞아서, 같이 일하는 사람이랑 안맞아서</p>
<h1 id="🤔-어떤-사람이-싫어요">🤔 어떤 사람이 싫어요?</h1>
<ul>
<li>왜 하는지 모르고 하는 사람</li>
<li>비협조적인 사람</li>
<li>착취하고 착취당하는 사람</li>
<li>남 탓하는 사람</li>
<li>나만 옳다는 사람</li>
<li>방어적인 사람</li>
</ul>
<h1 id="1-이상한-사람이-덜-있는-회사로">1. 이상한 사람이 덜 있는 회사로</h1>
<p>이상한 사람이 있는 밀도는 회사마다 천차 만별이다.</p>
<p><strong>밀도</strong>는 <strong>기업 문화</strong>에서 온다.</p>
<p><strong>기업 문화가</strong> 좋은 회사에서 이상한 사람이 적은 이유는 뭘까?</p>
<p>잠시, 우리가 회사를 고르는 프로세스를 생각해보자.</p>
<p>우리가 회사를 어떻게 선택하는가?</p>
<ul>
<li>급여</li>
<li>성장성</li>
<li>일이 재밌어 보여서</li>
<li>복지가 좋아보여서</li>
<li>일이 안빡세서</li>
<li>뽑아줘서 붙여줘서</li>
<li>.... <em>기업문화가 좋아서?</em>
기업 문화를 우선순위로 잘 두지 않는다.</li>
</ul>
<p>이상한 사람들이랑 일하는게 나는 너무 힘들고, 퍼포먼스에 큰 영향을 받는다면,
다른 조건들보다 <strong>기업 문화가 좋은가?</strong>를 최우선으로 고려해보자.</p>
<p>물론, 입사전 해당 기업의 <strong>기업문화</strong>를 알아내긴 어렵다.
실제로 어떤지 알아내는데 총력을 기울여보자.</p>
<h3 id="사실은-이상한-사람이라는게-없어서">사실은 이상한 사람이라는게 없어서</h3>
<p>사실, 이상한 사람이라는게 있을까?</p>
<p>내가 이상한 행동을 하도록 하는 환경도 있고, 내가 이상한 면을 가지고 있으나, 그 환경과 좋은 결과를 만들기도 한다.
우리는 똑같은 사람인데 <strong>어디에 있냐</strong>에 따라서 퍼포먼스가 다른 것을 경험해봤을 것이다.</p>
<h1 id="2-경쟁보다는-협력을-지향하는-문화">2. 경쟁보다는 협력을 지향하는 문화</h1>
<p>예를 들면, 경쟁 문화가 발달한 조직 문화는</p>
<ul>
<li>내가 낫고, 남이 못하다를 끊임없이 증명</li>
<li>내가 채택이 되고, 저 일은 내 공, 잘못은 니 잘못이라는 전제가 있어야 한다</li>
<li>내가 나쁜놈이 아니라, <strong>회사가 원래 이런 곳이야</strong></li>
<li>나의 생존을 위해 사람이 그렇게 된다.</li>
</ul>
<blockquote>
<p>마케팅팀 -&gt; 제품이 별론데, 어떻게 팔아
제조 팀-&gt; 우리 제품이 우리 업계 최곤데 니가 못판걸 왜 남탓을 해?</p>
</blockquote>
<p>이거는 개인이 어쩔 수 있는 문제가 아니라, 필연적으로 상대 탓을 하게 된다.</p>
<p>반대로 협조적인 문화라면</p>
<ul>
<li>이 일이 잘된건 나 때문도, 너 때문도 아닌 우리가 잘한거야</li>
<li>이 일은 나때문이다라고 할 필요가 없다</li>
</ul>
<blockquote>
<p>디자이너: 마케팅 이렇게 하는게 더 좋지 않아요?
마케터: 듣고 보니 일리가 있네요!</p>
</blockquote>
<p>우리는 <strong>우리가 더 잘되야 좋은 회사</strong>에서 일하니까
자연스럽게 상대 말을 경청하고, 우리가 <strong>우리의 결과를 잘 낼 수 있는 쪽으로</strong> 
우리는 하나의 목표를 향해 가고 있다고 신뢰할 수 있게 된다.</p>
<h3 id="이건-개인의-문제가-아닌-기업-문화다">이건 개인의 문제가 아닌, 기업 문화다</h3>
<p>기업 문화가 좋은, 협조적인 문화가 있는 조직은 <strong>이상한 사람이 이상한 면을 발휘할 수 있는 기회가 적다.</strong></p>
<h3 id="이상한-사람이-아니었구나">이상한 사람이 아니었구나</h3>
<p>사람들은 내가 좋아하는 사람들한테 호의적인 판단을, 싫어하는 사람한테는 부정적인 판단을 한다.
<strong>사람이 부족해서가 아니라, 원래 인간이 그렇다</strong></p>
<pre><code>회의 시간, 내가 싫어하는 사람이 늦게 들어왔다. 왜 늦었을까?
- 상대방의 시간을 소중히 여기지 않으니까
- 나를 무시하니까
- 자기가 최고라고 생각하니까
- 원래 시간 관념이 희미한 사람이니까

내가 존경하고, 좋아하는 사람이 들어온다. 왜 늦었을까?
- 직전에 중요한 회의가 있었으니까!</code></pre><h3 id="의도를-짐작하기를-멈춰주세요">의도를 짐작하기를 멈춰주세요.</h3>
<p>계속해서 악순환이 반복된다.
한번 <code>저사람 이상해</code>라고 한다면, 바뀌기가 쉽지 않다.
그런데 내가 이렇게 평가하는 건 그 사람 한테 영향을 주는게 아니라 나한테 영향을 끼친다.</p>
<p>내가 싫어하는 사람이어도 불구하고, <code>좋은 의도로 했다</code>라고 생각해보자.</p>
<blockquote>
<p>나의 제안을 거절한 이유는 내가 싫어서, 내 부서에서 하는 걸 불신해서가 아니라
내가 미처 모르는 중요한 것을 알고 있어서 큰 일, <strong>손해보지 말라고 커트해준거야!</strong></p>
</blockquote>
<p>라고 오바해서 생각해보자.</p>
<h3 id="이해가-안간다">이해가 안간다</h3>
<blockquote>
<p>이 말을 경계하자!
내 마음을 알아채는 바로미터다</p>
</blockquote>
<p><code>아 이해가 안가</code> -&gt; 내가 혹시 (이 사람을) 싫어하나?</p>
<p>사실 이해가 안가는 일은 별로 없다. 아 내가 싫어하는구나 생각해보고, 내가 왜 싫어하지? 내가 의도를 (나쁘게) 지레짐작 하는지 확인해보자</p>
<h3 id="내가-충분히-__-하지-않은-것이-아닐까">내가 충분히 __ 하지 않은 것이 아닐까</h3>
<p>나는 전혀 그럴 의도가 없었는데, 내가 선택한 단어가 상대 마음을 상하게 한 건 없을 지
<strong>나는 아니까 대충 설명한건 아닌지</strong></p>
<p>바쁜 시기에 <code>이런건 알겠지</code>, <code>이해하고 있겠지</code>라는 이유로 충분히 얘기하지 않는다면,
듣는 사람의 반응은 <code>왜이래? 나를, 우리 부서를 무시하는거 아냐?</code>라고 갈등이 생기기 시작할 수 있다.</p>
<p>우리, 이렇게 충분히 얘기해도 잘 안돌아간다면..</p>
<h2 id="일의-세계관">일의 세계관</h2>
<pre><code>일은 재미없다 - 일은 재미있다.
높은 사람은 정답을 안다 - 정답은 없다, 아무도 모른다
설득엔 확신을 가진다 - 설득엔 확신을 버린다
약점은 숨긴다 - 약점을 알려도 안전하다
약점을 보완한다 - 강점을 잘 쓴다
우리 조직에 실패는 없다 - 아무리 잘못하지 않아도 일은 때로 실패한다
상사 마음에 들게 - 고객 마음에 들게 </code></pre><p>이런 세계관을 가진 사람들이 서로 섞여 있을때</p>
<blockquote>
<p>아니 왜 일을 이렇게하지?</p>
</blockquote>
<p>생각 할 수 있다</p>
<p>그렇다면</p>
<p>우리는 <strong>안맞는다는 걸</strong> 알게 됐다.
우리는 <em>도저히 같이 일을 할 수 없다면</em>..</p>
<h1 id="3-오히려-좋아">3. 오히려 좋아</h1>
<p>어떤게 더 중요하세요?
<strong>내가 싫어하는 사람을 싫어하는 에너지 vs 내가 맡은 일을 잘 해내는 것</strong></p>
<p>전자라면, 내 마음의 정의를 구현히 위해서라면 어쩔 수 없다.
후자라면, 저 사람때문에 내 일이 다치지 않도록. 내 일의 퍼포먼스가 특정 사람에게 영향을 받지 않도록.
나의 감정을 꺼두자
<strong>그리고 내 일을 더 철저히 마무리하자</strong></p>
<p>그 사람에게는 내가 이상한 사람이고, 구설수에도 오를 수 있다.
그러므로 내가 일을 더 철저히 책잡히지 않고, 철저히 예의 바르게 커뮤니케이션하도록 일 하자.
이렇게 일하다보면 일을 느슨하게 하지 않고 일의 퍼포먼스를 더 높일 수 있다.</p>
<h1 id="36-도망가세요">36. 도망가세요</h1>
<p>좋은 회사 문화를 가진 곳으로!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 백엔드 6기]  당신은 코드를 작성하는게 아니라, 설계를 하고 있다]]></title>
            <link>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-3%EC%A3%BC%EC%B0%A8-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8-%EB%AC%B8%EC%84%9C%ED%99%94</link>
            <guid>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-3%EC%A3%BC%EC%B0%A8-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8-%EB%AC%B8%EC%84%9C%ED%99%94</guid>
            <pubDate>Sat, 12 Oct 2024 14:46:17 GMT</pubDate>
            <description><![CDATA[<p>예.. 어그로 죄송합니다 제목의 당신은 바로 저였습니다.</p>
<p>이번주차는 <strong>설계</strong>와 <strong>문서화</strong>가 메인입니다.
코치님이 말씀해준 것 중에 뇌리에 깊게 박힌 내용이 있어요.</p>
<blockquote>
<p>여러분들 설계 안하고 코드 먼저 작성한다고 할 때 어떻게 하시나요?
클래스 한 개 만든 뒤, <strong>필드 하나 넣었다.. 뺐다.. 이게 맞나?</strong> 이거 하시죠?
이게 코드를 작성하는 건가요? 
아니죠? 오히려 <strong>설계를 하고 있는거죠!</strong></p>
</blockquote>
<p><img src="https://2runzzal.com/media/NUNPOXR3VStMRnN3d2F5OGFMTWpqUT09/zzal.gif" alt="">
네.. 충격 많이 먹었습니다.</p>
<p><strong>[들어가기 전]</strong>
다음 용어가 무엇인지 알고, 설명할 수 있나요?</p>
<ul>
<li>마일스톤</li>
<li>시퀀스 다이어그램</li>
<li>ER 다이어그램</li>
<li>Open API Spec/Swagger</li>
</ul>
<h1 id="1-기술-회고">1. 기술 회고</h1>
<h2 id="1-마일스톤">1. 마일스톤</h2>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/cc2092ee-44a4-4e5a-b343-1ba42a2c547d/image.png" alt="">
깃허브 프로젝트를 이용해 <a href="https://github.com/orgs/hpp-backend-15/projects/2">마일스톤</a>을 만들었다.
<del>음.. 이쁘다!</del></p>
<h2 id="2-시퀀스-다이어그램">2. 시퀀스 다이어그램</h2>
<p><a href="https://mermaid.js.org/intro/getting-started.html">머메이드</a>를 이용해 시퀀스 다이어그램을 그렸다. 원래는 <a href="https://github.com/terrastruct/d2/blob/master/README.md">d2</a>를 이용하려 했는데, 깃허브 마크다운에서 지원한다해서 사용했다!
<img src="https://velog.velcdn.com/images/petit-prince/post/ef6e26fa-902d-42da-869c-e5b65bc34418/image.png" alt=""></p>
<blockquote>
<p>혹시 그런 고민 안해봤나요?
&quot;시퀀스 다이어그램에 어디까지 그려야하지..&quot;</p>
</blockquote>
<p>위 질문에 대한 답은 코치님들도 모두 달랐지만, 내가 동의한 결론은 <strong>모든 도메인</strong>을 포함시키자 였다!</p>
<p>그럼 개발할 때 공수가 줄어든다 😁</p>
<h2 id="3-erd">3. ERD</h2>
<p>이 또한 <a href="https://mermaid.js.org/intro/getting-started.html">머메이드</a>를 이용해서 그렸다. 
<img src="https://velog.velcdn.com/images/petit-prince/post/c00fd4b9-d362-480b-8c51-fdea0b959e77/image.png" alt=""></p>
<p>크게 다음과 같이 구성된다.</p>
<ul>
<li>유저</li>
<li>대기열</li>
<li>결제/충전 내역</li>
<li>예약</li>
<li>콘서트<ul>
<li>공연일</li>
<li>좌석</li>
</ul>
</li>
</ul>
<h2 id="4-oas를-이용한-api-스펙mockapi">4. OAS를 이용한 API 스펙/MockAPI</h2>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/a609bcf5-d188-4a23-ace9-cfbff8049fd0/image.png" alt=""></p>
<p><a href="https://swagger.io/specification/">OpenAPI Spec</a>이라는 것을 이용해서, <code>yaml</code>파일을 먼저 작성해 만들었다.</p>
<h3 id="장점">장점</h3>
<ul>
<li>코드를 작성하기 전, 이해관계자(ex 프론트)와 스펙상으로 회의 가능하다</li>
<li>_OAS_로 작성하면 바로 MockAPI를 뽑아 제공 가능하다.<ul>
<li><code>이 API 언제 뽑혀요?.. 테스트 해봐야 하는데..</code> 를 안들을 수 있다.</li>
<li>다른 사람들이 내 API에 의존하여 블락되는 지점 해소 가능</li>
</ul>
</li>
<li>_OAS_는 스펙이다. 즉, 다른 툴로 export 가능하다<ul>
<li>(Java/Kotlin) Spring Swagger</li>
<li>Postman</li>
<li>등등...</li>
</ul>
</li>
<li>단일 진실 공급원(SSOT)이 될 수 있다.<ul>
<li><code>이거 API 왜 업데이트 안했어요?</code>, <code>이거 저번에 XX 하기로 하지 않았어요?</code></li>
<li>모든 이해관계자에게 <strong>개발하기 전부터 스펙먼저 합의할 수 있기에</strong> 위와 같은 일들을 방지할 수 있다.</li>
<li><a href="https://www.youtube.com/watch?v=mhGz8q-aOZ0">인프콘 2023 김정규 - 오늘도 여러분의 API는 안녕하신가요?</a>를 참고해보자</li>
</ul>
</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>코드를 먼저 안짜는 것도 단점일 수도 있다<ul>
<li>도대체 뭘 API 스펙으로 제공해야할지 감이 안올수도 있다.</li>
</ul>
</li>
<li>러닝 커브 (OAS 문법)</li>
<li>Swagger로 뽑았는데 무수히 많은 클래스들이 생성됨<ul>
<li>이 또한, 위의 김정규님 세션에서 해결법을 제시한다.</li>
<li><a href="https://github.com/LenKIM/openapi-code-gen">Custom CodeGen</a>을 참고해보자</li>
</ul>
</li>
</ul>
<h2 id="5-ddd의-어그리게이트-루트엔티티">5. DDD의 어그리게이트 루트/엔티티</h2>
<p>프로젝트를 계속 진행하며 날 괴롭히고 오해한 <code>DDD</code>의 개념이 하나 있다.
바로 <code>DDD의 어그리게이트 루트</code>이다. 
<code>DDD</code>에서 <code>어그리게이트 루트</code>는 한 도메인의 진입점이자, 모든 수행을 관장하는 뿌리다.
모든 서비스와 요청은 <code>어그리게이트 루트</code>를 DB에서 불러와서 처리한다. </p>
<p>예를 들자면</p>
<pre><code class="language-java">Order order = orderRepository.findById(1L);

order.changeOrder(new Order(...));</code></pre>
<p><code>한 도메인의 어그리게이트 루트는 한 개이고, 한 개의 엔티티로 관리된다.</code> 라고 이해하면서 한 도메인 내의 모든 JPA 엔티티를 <code>@Embeddable</code>, <code>@ElementCollection</code>을 이용하여 관리하려고 노력했다. </p>
<p><strong>근데 이게 틀렸다.</strong></p>
<p>왜냐하면 <code>DDD의 엔티티</code>는 <code>JPA의 엔티티</code>와 다르기 때문이다.</p>
<p>이 문제를 코치님께 여쭤봐서 깨달았다. 같은 용어라도 다른 컨텍스트라면 다른 개념이다. </p>
<hr>
<h1 id="2-kpt">2. KPT</h1>
<h2 id="keep--현재-만족하고-계속-유지할-부분"><strong>Keep : 현재 만족하고 계속 유지할 부분</strong></h2>
<ul>
<li>꾸준히 코어타임을 지키는 점</li>
<li>꾸준히 아고라에 질문 올리는 점</li>
</ul>
<h2 id="problem--개선이-필요하다고-생각하는-문제점"><strong>Problem : 개선이 필요하다고 생각하는 문제점</strong></h2>
<ul>
<li>아.. _머메이드_에 설계하전에 종이에 할 걸. 코치님 말씀대로 너무나도 딴짓하게 되더라</li>
<li>왜 기술적인 회고를 쓰지 않지? 써야겠다!</li>
</ul>
<h2 id="try--문제점을-해결하기-위해-시도해야-할-것"><strong>Try : 문제점을 해결하기 위해 시도해야 할 것</strong></h2>
<ul>
<li>다음부터 설계는 종이에다가 먼저 스케치를 그리자!</li>
<li>오늘부터 기술 회고도 시작!</li>
</ul>
<hr>
<h1 id="항해에-참가하려는-당신">항해에 참가하려는 당신!</h1>
<p>혹시 <strong>[들어가기전]</strong>의 답변이 궁금하신가요!?
글을 읽다보니 <strong>&#39;아..! 나도 저거 알아야하는데!&#39;</strong> 하진 않으신가요?!</p>
<p>여기까지 글을 읽으셨다면, 아마 <strong>항해 플러스 과정</strong>에 관심있으신 분이라고 생각합니다.</p>
<p>궁금한 점이 있으시다면 저에게 편하게 댓글 남겨주세요!
혹은 <code>highestbright98@naver.com</code>으로 메일을 남겨주셔도 굉장히 빨리 답변한답니다!</p>
<p>항해 플러스 백엔드에 관련해서 궁금하신점이 있다면 언제든 편하게 말씀주세요!</p>
<p>그리고 글이 도움됐다면, 등록하실때 제 추천인 코드 <strong>[9MPLfu]</strong> 입력하면,
<strong>20만원 할인의 혜택</strong>이 있답니다! (물론 저에게도 혜택이 있습니다! 😉)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 백엔드 6기] 아직도 지저분한 코드를 짜고 있다 - 자기소개 및 
 짧은 후기]]></title>
            <link>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-6%EA%B8%B0-%EC%95%84%EC%A7%81%EB%8F%84-%EC%A7%80%EC%A0%80%EB%B6%84%ED%95%9C-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%A7%9C%EA%B3%A0-%EC%9E%88%EB%8B%A4-%EC%9E%90%EA%B8%B0%EC%86%8C%EA%B0%9C-%EB%B0%8F-2%EC%A3%BC%EC%B0%A8-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-6%EA%B8%B0-%EC%95%84%EC%A7%81%EB%8F%84-%EC%A7%80%EC%A0%80%EB%B6%84%ED%95%9C-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%A7%9C%EA%B3%A0-%EC%9E%88%EB%8B%A4-%EC%9E%90%EA%B8%B0%EC%86%8C%EA%B0%9C-%EB%B0%8F-2%EC%A3%BC%EC%B0%A8-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sat, 05 Oct 2024 12:23:44 GMT</pubDate>
            <description><![CDATA[<p>간단한 자기소개와 2주차 후기입니다.</p>
<p><strong>[들어가기 전]</strong>
다음 용어가 무엇인지 알고, 설명할 수 있나요?</p>
<ul>
<li><code>AOP</code>, <code>@Transactional</code> 동작 원리</li>
<li>DBMS 격리 수준</li>
<li>비관적 락, 낙관적 락</li>
</ul>
<h1 id="1-간단한-자기소개">1. 간단한 자기소개</h1>
<p>안녕하세요! 주로 백엔드 개발을 하는 만 2년차 주니어 개발자 우이천입니다.<br>현업에서 주로 <em>알림 컨텐츠/발송 기능</em>을 맡고 있습니다. (SMS, 이메일등)</p>
<h1 id="2-이번-챕터를-시작하며-꼭-해내고-싶었던-목표">2. 이번 챕터를 시작하며 꼭 해내고 싶었던 목표</h1>
<p>사실 점점 챕터가 힘들어질 것이라고 생각해서, <code>기본 개념들을 꼭 정립하고 가자</code>는 마음을 갖고 있었습니다!
그리고, 항해 하는 기간동안 일상을 건강하고 규칙적으로 보내보자! 라고 마음먹었구요. 항해가 끝나도 그 <strong>관성을 유지하고 싶었어요.</strong></p>
<h1 id="3-이번-챕터를-마무리하며-가장-기억에-남는-성취">3. 이번 챕터를 마무리하며 가장 기억에 남는 성취</h1>
<p>같은 팀원분들과 <code>@Transactional</code>이 걸린 테스트를 디버깅하며 하루종일 고민했던게 가장 기억에 남습니다.</p>
<p>실제로 해당 어노테이션이 어떻게 동작하고, 어떨 때 쓰여야하는지 더 배웠습니다.</p>
<blockquote>
<p>예를 들면, 왜 부모 메서드에 걸린 @Transactioanl은 전파되고, 안쪽에만 걸린 @Transactional은 부모에도, 안쪽에도 트랜잭션이 걸리지 않는걸까?에 대한 고찰</p>
</blockquote>
<p>그러는 와중에 다양한 항플 구성원에게 도움을 구하고, 의견을 들었을 때도 기뻤습니다.
특히 코치님들이 정답을 알려주셨을 때 너무 기쁘면서 부끄러웠습니다. <code>아.. 내가 이런 것도 몰랐다고?</code>하는 감정과 <code>이거 하루종일 봤는데 이걸 딱 해결해주시네</code>하는 감정이 복합적으로 몰려왔어요.
<img src="https://velog.velcdn.com/images/petit-prince/post/096df9db-39d8-470d-b8b9-764a6ceb163c/image.png" alt=""></p>
<blockquote>
<p>항플 코치 진짜 좋아요. <del>개쩔어요.</del></p>
</blockquote>
<h1 id="4-이번-챕터에서-반드시-이뤘으면-했는데-이루지-못한-것">4. 이번 챕터에서 반드시 이뤘으면 했는데 이루지 못한 것</h1>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/d1d5afea-e720-4016-8a1f-9c5224c9504f/image.png" alt=""></p>
<p>건강하게, 규칙적으로.. <strong>지속가능하도록 성장</strong>하고 싶었어요.
근데 <strong>항해 플러스 백엔드에서 그런걸 기대하면 안될 것 같아요</strong>. 
짧은 시간동안 <del>고농축 동맥 주사 문도 박사형</del> 성장하기에 <em>트레이드 오프</em>가 있다. 생각해주시는 게 더 좋아요! 😉</p>
<p>&quot;<strong>주말 포함하여 최소 40시간</strong>정도 내줄 수 있는지?&quot; 미리 생각해보시길 바랍니다.
항해에서 결국 뭐하겠어요? 코드 짜겠죠. 근데 그런 코드를 짜는 순간에 치열하게 고민했던 생각들이 <del>사골 액기스마냥</del> 성장 자체인 것 같습니다.</p>
<blockquote>
<p><strong>40시간? 어렵지 않네?</strong> 다시 생각해보세요.
평일 4시간정도는 공부하지 않는다면 현실적으로 불가능합니다.</p>
</blockquote>
<p>그리고 <em>이번 과제 주제가 뭐였지?</em> 생각이 듭니다. 
이번에도 <code>기본 개념을 익힌다!</code> 보다는 구현에만 급급했던게 아닐까 후회가 크게 남아요. 😅</p>
<h1 id="5-내가-강화해야-할-강점-중-가장-중요하다고-생각하는-한-가지">5. 내가 강화해야 할 강점 중 가장 중요하다고 생각하는 한 가지</h1>
<p><strong>도메인의 역할 설계에 좀 더 다양한 요구사항을 녹여내는 법</strong>
특히 이번 과제에서 <code>비관락</code>을 이용해서 분산 환경에서 동시성을 제어하는게 크리티컬한 과제였는데, 실패했습니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/ec0ccc80-d7b0-4ad3-8c46-96551b20f3ce/image.png" alt=""></p>
<p>깔끔한 도메인 설계를 지향해보려고 테이블 설계를 했는데, <strong>치명적이게도</strong> 비관락을 전혀 고려하지 않은 설계였기 때문입니다.
<img src="https://velog.velcdn.com/images/petit-prince/post/a70ceac0-b08e-46ab-8e9b-d87d6caa717c/image.png" alt=""></p>
<blockquote>
<p>어떻게 보면 당연하다. 부끄럽지만 한번도 현업에서 동시성 문제를 진지하게 고려해보지 않았으니까.
이제부터 배우면 된다. 개선해서 적용하면 된다.
<del>현업에서 &quot;<em>엥 신청이 이상하다고요..? 그거 프론트쪽 문제 아니에요??</em>&quot; 안한게 어디야..</del>
쪽팔리지 말자! 쪽팔려야 한다면, 미리 쪽팔리자!</p>
</blockquote>
<p>여러분들은 다음 코드에서 왜 <strong>동시성 처리를 못할 수 밖에 없는지</strong> 알아차리셨나요?</p>
<pre><code class="language-java">@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class RegisterService {
    private final LectureService lectureService;
    private final UserService userService;
    private final RegisterRepository registerRepository;

    @Transactional
    public ApplyResponse apply(String userId, String lectureId, LocalDate date) {
        LectureItem lectureItem = lectureService.findLectureItemByLectureIdAndDate(LectureId.of(lectureId), date);
        User user = userService.findById(UserId.of(userId));

        // 여기서 문제가 됨!
        Register register = registerRepository.findByLectureItemId(lectureItem.getId()) //PESSIMISTIC WRITE 적용 (for update)
                .orElse(new Register(LectureId.of(lectureId), lectureItem));

        register.register(user);
        registerRepository.save(register); //-&gt;10개의 `Register`가 생김..
        return new ApplyResponse(userId, lectureId);
    }

}</code></pre>
<p>저는 이제 답을 알고 있습니다.. <del>메롱</del></p>
<h1 id="6-내가-개선해야-할-개선점-중-가장-중요하다고-생각하는-한-가지">6. 내가 개선해야 할 개선점 중 가장 중요하다고 생각하는 한 가지</h1>
<p><strong>시간 관리</strong></p>
<blockquote>
<p>결국 시간 싸움이다. 누가 해당 과제에 1초라도 더 고민해봤는지, 누가 한번이라도 더 작성한 코드를 엎어 봤는지, 멘토링 코치에게 뭘 물어봐야 하는지 한번이라도 더 고민하는 사람이 얻어가는게 많을 것이다.</p>
</blockquote>
<p><strong>멘탈 관리</strong></p>
<blockquote>
<p>그리고 멘탈 관리. 멘탈 나가지 말자. 어차피 끝까지 간 놈이 이기는거니까.
<img src="https://velog.velcdn.com/images/petit-prince/post/3ee6d6f8-8e9f-4d92-8cc2-c73201ea1712/image.png" alt=""></p>
</blockquote>
<hr>
<h1 id="항해에-참가하려는-당신">항해에 참가하려는 당신!</h1>
<p>혹시 <strong>[들어가기전]</strong>의 답변이 궁금하신가요!?
글을 읽다보니 <strong>&#39;아..! 나도 저거 알아야하는데!&#39;</strong> 하진 않으신가요?!</p>
<p>여기까지 글을 읽으셨다면, 아마 <strong>항해 플러스 과정</strong>에 관심있으신 분이라고 생각합니다.</p>
<p>궁금한 점이 있으시다면 저에게 편하게 댓글 남겨주세요!!
혹은 <code>highestbright98@naver.com</code>으로 메일을 남겨주셔도 굉장히 빨리 답변한답니다!</p>
<p>항해 플러스 백엔드에 관련해서 궁금하신점이 있다면 언제든 편하게 말씀주세요!</p>
<p>그리고 글이 도움됐다면, 등록하실때 제 추천인 코드 <strong>[9MPLfu]</strong> 입력하면,
<strong>20만원 할인의 혜택</strong>이 있답니다! (물론 저에게도 혜택이 있습니다! 😉)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 백엔드 6기] 테스트 먼저? 코드 나중에! TDD 모험기 🚀]]></title>
            <link>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-1%EC%A3%BC%EC%B0%A8-TDD</link>
            <guid>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-1%EC%A3%BC%EC%B0%A8-TDD</guid>
            <pubDate>Sat, 28 Sep 2024 08:01:50 GMT</pubDate>
            <description><![CDATA[<p>여러분은 <strong>TDD</strong> 좀 치십니까?
저는 잘 못칩니다. 해본 적도 없고, 왜 해야하는지도 모르고..
<del>어차피 기획 없이 내가 기획하면 사실 오늘이건 내일이건 도메인 로직 바뀔거고..</del></p>
<p><strong>그러다 내게 TDD가 와버렸습니다.</strong>
<img src="https://velog.velcdn.com/images/petit-prince/post/ce98e6e8-3370-4373-8ee7-bbaf74b6c20b/image.png" alt=""></p>
<p><strong>[들어가기 전]</strong>
다음 용어가 무엇인지 알고, 설명할 수 있나요?</p>
<ul>
<li>동시성 문제</li>
<li>단위 테스트, 통합테스트</li>
<li>스텁, 스파이, 모킹</li>
</ul>
<h1 id="1-기술-회고">1. 기술 회고</h1>
<h2 id="동시성을-고려하여-포인트-충전해보기">동시성을 고려하여 포인트 충전해보기!</h2>
<p>그런데 나는 <code>동시성</code>을 몰라요!
어.. 부끄럽지만 나는 동시성 문제를 고려해본적도 해결해본적도 없다. 왜냐하면 이용자가 없으니까! 
<img src="https://velog.velcdn.com/images/petit-prince/post/7ec8bb22-3eea-48b2-8b40-5c01a580fe4c/image.png" alt=""></p>
<p><del>일단 짜야하니까.</del> 포인트 충전 코드를 아래와 같이 작성해봤다.</p>
<pre><code class="language-java">public UserPoint chargeUserPoint(long id, long amount) {
  // 포인트 테이블은 단순히 HashMap이다.
  UserPoint userPoint = userPointTable.selectById(id);

  UserPoint chargedPoint = userPoint.chargePoint(amount);
  pointHistoryTable.insert(id, chargedPoint.point(), CHARGE, System.currentTimeMillis());

  return userPointTable.insertOrUpdate(id, chargedPoint.point());
}</code></pre>
<p>잠시 글을 멈추고 생각해보자.</p>
<p><strong>포인트가 0에서 시작하고, 같은 아이디 유저 1명이 동시에 N번 1만큼 충전하는 요청을 실행시키면 어떻게 될까?</strong> </p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/d330e1b7-2e43-443c-9041-dca7815de439/image.png" alt=""></p>
<p>&quot;멀티 쓰레드에서 실행 결과의 순서를 보장할 수 있는 방법은 없다.&quot;는 단순한 사실을 기억한다면, <strong>충전의 일관된 결과를 보장할 수 없다.</strong></p>
<blockquote>
<p>조금 더 자세히 풀어보자면,
N개의 쓰레드가 거의 동시에 userPointTable에 현재 포인트 값을 조회하고, 해당 조회값 + 충전할 값을 유저 포인트에 업데이트한다.
업데이트 하기 전 값을 조회 성공한다면 전 값을, 업데이트가 완료 된 후 값을 조회 성곤한다면 후 값을 가져온다. 
어떤걸 가져올지 <strong>우리는 모른다</strong>. (쓰레드 스케쥴링 하는 OS는 안다.)</p>
</blockquote>
<h2 id="알게된-것">알게된 것</h2>
<p>이 과정을 통해 다음을 배웠다.</p>
<ul>
<li>동시성이 뭔지? <code>lost update</code>가 왜 일어나는지? 동시성 문제가 되면 발생하는 일들이 무엇인지?</li>
<li>테스트 코드 종류와 그를 위한 도구들<ul>
<li>단위 테스트, 통합테스트</li>
<li>스텁, 스파이, 모킹</li>
</ul>
</li>
<li>동시성 테스트코드 작성법</li>
<li>전반적인 동시성 개념<ul>
<li>JAVA 동시성 처리에는 <code>lock</code>, <code>Atomic</code>, <code>Concurrent</code>, <code>flow</code> 등이 사용된다. 각 개별마다 구현하는 방법은 다르지만, 비관락, 낙관락이랑 비슷한 알고리즘을 가지고 접근한다. (점유중이라면 접근 불가, 이미 변경됐다면 변경 취소)</li>
</ul>
</li>
<li>도메인 메서드에 <code>repository</code>을 넣어 CRUD 할 수 있게 해도 괜찮은가? 에 대한 고민 <ul>
<li>A. 하지마라</li>
</ul>
</li>
<li>불변 객체 record 상태값 변경 및 저장을 어떻게 처리해야하지?<ul>
<li>A. 재생성해라</li>
</ul>
</li>
</ul>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=mTGdtC9f4EU&amp;list=PLL8woMHwr36EDxjUoCzboZjedsnhLP1j4">동시성에 대한 강의 수강</a></li>
</ul>
<hr>
<h1 id="2-kpt-회고">2. KPT 회고</h1>
<h3 id="keep--현재-만족하고-계속-유지할-부분"><strong>Keep : 현재 만족하고 계속 유지할 부분</strong></h3>
<ul>
<li>죽이 되든 밥이 되든 밤 새서 할 수 있는 곳까지는 가봤다.</li>
</ul>
<h3 id="problem--개선이-필요하다고-생각하는-문제점"><strong>Problem : 개선이 필요하다고 생각하는 문제점</strong></h3>
<ul>
<li>이번주처럼 10주간 지속가능하게 공부할 수 있는 가?</li>
<li>(시간 관계상) 코치님께 질문할 때, 질문을 철저히 준비해서 갈 것.</li>
</ul>
<h3 id="try--문제점을-해결하기-위해-시도해야-할-것"><strong>Try : 문제점을 해결하기 위해 시도해야 할 것</strong></h3>
<ul>
<li>노션 팀 사전 질문을 꼼꼼히 확인하고 내가 모르는 부분을 정확히 인지</li>
</ul>
<hr>
<h1 id="항해에-참가하려는-당신">항해에 참가하려는 당신!</h1>
<p>혹시 <strong>[들어가기전]</strong>의 답변이 궁금하신가요!?
글을 읽다보니 <strong>&#39;아..! 나도 저거 알아야하는데!&#39;</strong> 하진 않으신가요?!</p>
<p>여기까지 글을 읽으셨다면, 아마 <strong>항해 플러스 과정</strong>에 관심있으신 분이라고 생각합니다.</p>
<p>궁금한 점이 있으시다면 저에게 편하게 댓글 남겨주세요!!
혹은 <code>highestbright98@naver.com</code>으로 메일을 남겨주셔도 굉장히 빨리 답변한답니다!</p>
<p>항해 플러스 백엔드에 관련해서 궁금하신점이 있다면 언제든 편하게 말씀주세요!</p>
<p>그리고 글이 도움됐다면, 등록하실때 제 추천인 코드 <strong>[9MPLfu]</strong> 입력하면,
<strong>20만원 할인의 혜택</strong>이 있답니다! (물론 저에게도 혜택이 있습니다! 😉)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 백엔드 6기] 시작하는 마음]]></title>
            <link>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%82%AC%EC%A0%84-OT</link>
            <guid>https://velog.io/@petit-prince/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%82%AC%EC%A0%84-OT</guid>
            <pubDate>Sun, 15 Sep 2024 15:09:00 GMT</pubDate>
            <description><![CDATA[<h2 id="1-항해-플러스-백엔드란">1. 항해 플러스 백엔드란?</h2>
<ul>
<li>내용: 시니어 개발자와 참여자가 코드 리뷰해주며, <code>TDD</code>, <code>클린 아키텍쳐</code>, <code>대용량 트래픽 처리</code>, <code>장애 관리</code> 내용을 배운다.</li>
<li>가격: 얼리버드에 따라 다르고, 선배 할인코드/동시 수강생이 있다면 할인된다. (140~?)</li>
</ul>
<h2 id="2-신청-이유">2. 신청 이유</h2>
<p>현재 회사에서 바쁘게 진행되던 프로젝트가 한 사이클이 끝났다.</p>
<ol>
<li>여태 잘못짜고 있었던 코드들을 보며, <code>이젠 이렇게 해선 안되겠다.</code> 생각이 들었다.</li>
<li>같은 팀내 (굉장히 뛰어나셨던) 사원님들이 2분이나 이직하며 위기감이 들었다.</li>
</ol>
<p>같은 팀에서 일하시던 두 분 모두 배울점이 많았는데, 이젠 누가 끌어다 주지 않을 거라 생각하니, 외부에서 끌어줄 사람을 찾았다.</p>
<h2 id="3-기대하는-점">3. 기대하는 점</h2>
<p>내가 항해에 바라는 점은..</p>
<ul>
<li>적어도 객관화를 할 수 있는 정도의 실력</li>
<li>사수 없이도, 불안해하지 않고 혼자 답을 찾아내갈 힘</li>
<li>다양한 연차/백그라운드 네트워킹</li>
<li>🚀</li>
</ul>
<h2 id="4-각오">4. 각오</h2>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/f7be4c53-5f3b-460e-85ca-a2c1fd7e725b/image.png" alt=""></p>
<p>그냥 하면 된다.
너무 많이 고민하지 말고, 꾸준히 하자는 마음으로 임한다.</p>
<hr>
<h1 id="항해에-참가하려는-당신">항해에 참가하려는 당신!</h1>
<p>여기까지 글을 읽으셨다면, 아마 <strong>항해 플러스 과정</strong>에 관심있으신 분이라고 생각합니다.</p>
<p>궁금한 점이 있으시다면 저에게 편하게 댓글 남겨주세요!!
혹은 <code>highestbright98@naver.com</code>으로 메일을 남겨주셔도 굉장히 빨리 답변한답니다!</p>
<p>항해 플러스 백엔드에 관련해서 궁금하신점이 있다면 언제든 편하게 말씀주세요!</p>
<p>그리고 글이 도움됐다면, 등록하실때 제 추천인 코드 <strong>[9MPLfu]</strong> 입력하면,
<strong>20만원 할인의 혜택</strong>이 있답니다! (물론 저에게도 혜택이 있습니다! 😉)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[성능/구조 개선 한 거 구경 한번 해보세요~ (1 - 비동기 Spring Event 편)]]></title>
            <link>https://velog.io/@petit-prince/%EC%84%B1%EB%8A%A5%EA%B5%AC%EC%A1%B0-%EA%B0%9C%EC%84%A0-%ED%95%9C-%EA%B1%B0-%EA%B5%AC%EA%B2%BD%ED%95%98%EA%B3%A0%EA%B0%80%EC%84%B8%EC%9A%94-1-%EB%B9%84%EB%8F%99%EA%B8%B0-Spring-Event-%ED%8E%B8</link>
            <guid>https://velog.io/@petit-prince/%EC%84%B1%EB%8A%A5%EA%B5%AC%EC%A1%B0-%EA%B0%9C%EC%84%A0-%ED%95%9C-%EA%B1%B0-%EA%B5%AC%EA%B2%BD%ED%95%98%EA%B3%A0%EA%B0%80%EC%84%B8%EC%9A%94-1-%EB%B9%84%EB%8F%99%EA%B8%B0-Spring-Event-%ED%8E%B8</guid>
            <pubDate>Mon, 08 Jul 2024 17:29:05 GMT</pubDate>
            <description><![CDATA[<h1 id="1-구경-한번-와보세요">1. 구경 한번 와보세요~</h1>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/0824affa-5861-4222-9f6b-7ad1e3827b32/image.png" alt=""></p>
<h2 id="1-구조">1. 구조</h2>
<p>현재 진행하는 프로젝트에서 카프카에서 메시지를 받아, 알림을 보내는 기능이 있다.
<img src="https://velog.velcdn.com/images/petit-prince/post/c5e08549-aa50-47f4-87c3-aa3422fe671a/image.png" alt=""></p>
<ul>
<li>알림은 여러개의 알림 항목을 가진다.</li>
<li>알림 항목은 제목, 본문을 가진다.</li>
<li>알림 항목은 여러개의 알림 대상을 가진다.</li>
<li>알림 대상은 실제 알림을 보내기 위해 필요한 데이터를 저장한다.</li>
</ul>
<p><em>숙련된 개발자인 본인은 두뇌 풀가동을 시전했다.</em></p>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/c1cc6ff5-fbc0-4abe-a887-630095ff7ae3/image.png" alt=""></p>
<p>&quot;알림 등록(생성)하고, 카프카 이벤트 리스너를 달아놓아서, 위에 그림처럼 알림 울리면 되겠다!&quot; 라고 생각했다.</p>
<pre><code class="language-java">// CreateAlarm.java
@PostMapping
public void registerAlarm(@Valid @RequestBody CreateAlarmRequest createAlarmRequest) {
        AlarmResponse response = alarmService.registerAlarm(createAlarmRequest);
}

// kafkaListener.java
@Transactional  
@KafkaListener(topics = &quot;#{&#39;${spring.kafka.topic}&#39;}&quot;, containerFactory = &quot;KafkaListenerContainerFactory&quot;)  
public void push(@Payload MessageDto messageDto) {  
    // 알림 항목을 가져오는 코드
    List&lt;AlarmItemResponse&gt; alarmItemResponses = alarmRepository
            .findById(messageDto.id())
            .orElseThrow(NotFoundException::new)
            .getAlarmItems().stream().map(AlarmItemResponse::new).toList();

    // 알림 타입 (SMS, EMAIL 등)을 골라, 알림을 요청하는 코드
    alarmItemResponses.forEach(alarmItemResponse -&gt; {
        PushActionService pushActionService = pushActionFactory.getStrategy(alarmItemResponse.type());
        pushActionService.send(alarmItemResponse, alarmItemResponse.receivers(), messageDto);
    });
} </code></pre>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/56300da4-31aa-42c5-afcd-dba1c0e7196c/image.png" alt=""></p>
<p>위와 같이, PushActionFactory을 만들고, <code>Map</code> 자료구조를 만들어, Type에 따라, Push Action을 가져오는 전략 패턴을 구성했다. 
개발 당시에 디자인 패턴 하나 활용하는것 자체로 나는 갓개발자다 싶었다.
<img src="https://velog.velcdn.com/images/petit-prince/post/3d9602fb-8a31-4abf-8db5-b2782eec8358/image.png" alt=""></p>
<h2 id="2-빈약한-도메인-코드">2. 빈약한 도메인 코드</h2>
<p>그러다 문득 내 알림(Alarm 코드)는 화면단의 정보만 받기만하고, 도메인 자체로서 아무런 기능을 안하고 있다는 것을 깨달았다. </p>
<p>그래서 <code>Alarm</code> 도메인에 가장 중요한 역할인 <code>알림을 발송하는 책임</code>을 <code>Alarm</code>에서 처리할 수 있도록 <code>이벤트</code>를 도입했다.</p>
<p>도입하려면 다음 과정이 필요하다.</p>
<ol>
<li><p>EventConfiguration 추가</p>
<pre><code class="language-java">@Configuration  
public class EventsConfiguration {  
private final ApplicationContext applicationContext;  

public EventsConfiguration(ApplicationContext applicationContext) {  
   this.applicationContext = applicationContext;  
}  

@Bean  
public InitializingBean eventsInitializer() {  
   return () -&gt; Events.setPublisher(applicationContext);  
}  
}</code></pre>
</li>
<li><p>Events 클래스 추가</p>
<pre><code class="language-java">public class Events {  

private static ApplicationEventPublisher publisher;  

static void setPublisher(ApplicationEventPublisher publisher) {  
   Events.publisher = publisher;  
}  

public static void raise(Object event) {  
   if (publisher != null) {  
      publisher.publishEvent(event);  
   }  
}  
}</code></pre>
</li>
<li><p>냅다 원하는곳에 이벤트 발생 (Event)</p>
<pre><code class="language-java">// 이벤트 발생시키는 곳
Events.raise(new YourCustomEvent()); 
</code></pre>
</li>
</ol>
<p>// 이벤트 처리하는 곳
@EventListner(YourCustomEvent.class)
public void handleYourCustomEvent(YourCustomEvent yourCustomEvent){
    ...
}</p>
<pre><code>
그래서 내 프로젝트의 `Alarm` 다음과 같은 역할을 추가했다.
![](https://velog.velcdn.com/images/petit-prince/post/bd3c66b0-51a3-4be0-a6d7-8a29bf6dcab4/image.png)

```java
// Alarm.java
public void poundAlarm(MessageDto messageDto, AlarmPounder alarmPounder) {  
   alarmItems  
      .forEach(alarmItem -&gt; alarmItem.poundAlarmItem(messageDto, alarmPounder));
}

// AlarmItem.java
public void poundAlarmItem(MessageDto messageDto, AlarmPounder alarmPounder) {  
   Events.raise(new PoundAlarmEvent(this, this.receivers, messageDto, alarmPounder));  
}

// PoundAlarmEvent.java
@EventListener(PoundInnerAlarmEvent.class)  
public void poundAlarm(PoundInnerAlarmEvent alarmEvent) {  
   AlarmItemResponse alarmItemResponse = new AlarmItemResponse(alarmEvent.alarmItem());  
   List&lt;AlarmReceiverResponse&gt; alarmReceiverResponses = alarmEvent.alarmReceivers()  
      .stream()  
      .map(AlarmReceiverResponse::new)  
      .toList();  
   MessageDto messageDto = alarmEvent.messageDto();  
   AlarmPounder alarmPounder = alarmEvent.alarmPounder();  

   PushActionService pushActionService = pushActionFactory.getStrategy(alarmItemResponse.pushType());  
   pushActionService.send(alarmItemResponse, alarmReceiverResponses, messageDto, alarmPounder);  
}</code></pre><p>이제 내 <code>Alarm</code>은 실제로 알림을 울리는 역할도 갖게 됐다!</p>
<h2 id="3-비동기-처리">3. 비동기 처리</h2>
<p>뿌듯해서 동료분들께 자랑했더니, 동료분은 조심스럽지만 굉장히 의아해하시며 &quot;이건 <strong>구조를 개선한 것보단 그냥 다른 곳에서 서비스를 처리해주는 거 아닐까요?</strong> 비동기 처리 하시려고 그러시는거죠?&quot; 하셨다.</p>
<p>생각해보니 그랬다. 내가 생각했던 것은 아래와 같은 그림이었지만, 알림 외엔 실제 구현하는 기능이 없었다.
<img src="https://velog.velcdn.com/images/petit-prince/post/d3643c09-9b13-40ba-8c19-156e05f00258/image.png" alt=""></p>
<p>그러므로 실제로는 아래 짤방처럼 된것이다.
<img src="https://velog.velcdn.com/images/petit-prince/post/af979443-56c2-458c-89f5-8ac3bcdd2c2a/image.png" alt=""></p>
<p>그렇다면 어쩔 수 없다. 이렇게 된 이상 쓸모를 찾아보자.
동료분 조언대로 <code>비동기 처리</code>를 하면 되겠다 싶었다.
 <code>@Transactional</code>으로 하나의 쓰레드를 유지하고 있었기에, Kafka에 먼저 <code>ACK</code>를 보내고, 실제 알림 발송을 비동기처리하면 성능 향상을 기대할 수 있었다.</p>
<p><code>비동기 처리</code>는 생각보다 간단했다.</p>
<ol>
<li><code>@EnableAsync</code> 어노테이션 추가<pre><code class="language-java">@EnableAsync  
public class PushModuleApplication {
 ...
}</code></pre>
</li>
<li>원하는 EventHandler에 <code>@Async</code>추가<pre><code class="language-java">@Async  
@EventListener(PoundAlarmEvent.class)  
public void poundAlarm(PoundAlarmEvent alarmEvent) {
...
}</code></pre>
</li>
</ol>
<p>또한, 동료분께서 <code>@Transactional</code> 의 경우, DB의 세션을 유지해야하기 때문에 (혹시 @Transactional 전파 했을지 모르니) 다른 쓰레드를 사용하고 있는지도 확인해보라 하셨다.
그래서 실제 Thread Number(<code>Thread.currentThread().getId()</code>)를 로그에 찍어보며 확인했다.</p>
<ul>
<li><p><code>@EnableAsync</code> 없을 시
<img src="https://velog.velcdn.com/images/petit-prince/post/b29ff5c5-f9be-45f4-a513-72561d3ae66c/image.png" alt=""></p>
</li>
<li><p><code>@EnableAsync</code> 적용 후
<img src="https://velog.velcdn.com/images/petit-prince/post/40f0ceb8-38c6-4314-adc4-a94693ec3a26/image.png" alt=""></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/petit-prince/post/d9fd260d-eebf-4f4b-b09b-188fa1d3ac67/image.png" alt=""></p>
<p>다행히도, <code>@Async</code>어노테이션이 붙은 곳에서 다른 쓰레드 번호를 갖고 있었다. 
사실 실제 <code>k6</code> 테스트를 해봤을 때 결과로 사실 큰 차이가 발생하진 않았다.
그럼에도 <code>KafkaListener - Alarm - Event</code> 시퀀스에서, 이벤트를 발행하기만 하면 그 뒤로는 다른 쓰레드로 돌아가기에  어느정도 성능 개선을 기대해볼 수 있다. </p>
<p>실제 알림을 울리는 작업이 비교적 리소스를 많이먹고, 시간이 오래걸리는 <code>Disk Write IO</code>와 <code>외부 시스템 연동</code>을 하기 때문에 성능 개선해볼 수 있는 귀중한 경험이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[사이드 프로젝트 잘하는 법]]></title>
            <link>https://velog.io/@petit-prince/%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9E%98%ED%95%98%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@petit-prince/%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9E%98%ED%95%98%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Thu, 27 Jun 2024 15:17:38 GMT</pubDate>
            <description><![CDATA[<h1 id="인프런-퇴근길-밋업-4">인프런 퇴근길 밋업 #4</h1>
<p>부제: 성공적인 사이드프로젝트를 위한 조언
발표자: 유용태(테오의 스프린트)</p>
<h2 id="1-사이드-프로젝트">1. 사이드 프로젝트</h2>
<p>&quot;내가 하고싶은 일을 비공식적으로 결과물을 만들어 내는것&quot;</p>
<h3 id="이유">이유?</h3>
<ul>
<li>새로운 기술</li>
<li>자아 실현</li>
<li>창의력</li>
<li>네트워킹 </li>
<li>자기만족 … 내적동기! (혹은 외적 동기 -&gt; 이직)</li>
</ul>
<h3 id="꾸준함">꾸준함</h3>
<p>쉽지 않다. 시간과 많은 에너지가 소비된다.
-&gt; 하지만 다른 사람과 함께 할 때, 의지가 된다!
혼자서 만들어보는게 아닌 다른사람들과 만들어보는 경험
-&gt; 함께하니 더 어려운 점. 어떻게 잘 할까!</p>
<h3 id="그렇다면-나한테-물어볼-질문">그렇다면, 나한테 물어볼 질문</h3>
<p>사이드 프로젝트 하고 싶은 이유</p>
<ul>
<li>배경과 경험 있는 사람과 함께? (어떤 사람과 하고 싶은지)</li>
<li>네트워킹</li>
<li>시간투자, 지속성</li>
<li>나는 어느정도 기술과 역량이 있는지, 어느정도 기술/역량 있는 사람과 하고 싶은지</li>
</ul>
<h3 id="사이드-프로젝트가-실패하는-이유">사이드 프로젝트가 실패하는 이유</h3>
<p>서로 다른 생각의 인원이 모이지만, 강제성도 보수도 없다
원래 애초에 지속성이 없다(원래 잘 안되는거다)</p>
<ul>
<li>시간이 없어요</li>
<li>동기 부족</li>
<li>계획 부족</li>
<li>완뱍 주의</li>
<li>팀내 불화</li>
</ul>
<h2 id="2-핵심-원인">2. 핵심 원인</h2>
<p><code>기대의 불일치, 안전감의 상실, 소속감의 부재</code></p>
<h3 id="1-기대의-불일치">1. 기대의 불일치</h3>
<p>내가 기대한 바와 맞지 않는다면 동기부여가 되지 않는다. 
그 만큼 실망하고 나에게  기대하는 바와 내가 할 수있는게 다를때 힘들어진다.
-&gt; 솔직하게 내 기대감을 공유해야한다</p>
<h3 id="2-안전감의-상실">2. 안전감의 상실</h3>
<p>그러기 위해 솔직하게 말하는 괜찮다는 <strong>안전감</strong>이 필요하다</p>
<blockquote>
<p><em>사이드프로젝트는 내가 하고 싶어서 하는 프로젝트다</em></p>
</blockquote>
<h3 id="3-소속감의-부재">3. 소속감의 부재</h3>
<p>소속감을 잃게 되면 헌신이 아니라 내가 이용당한다는 기분이 든다. 
그러한 감정으로는 공동의 목표와 함께 할 수 없고, 결국 사프가 실패한다.</p>
<h2 id="3-팀빌딩">3. 팀빌딩</h2>
<p>팀과 그룹의 차이 -&gt; 목적!
팀은 그룹과 달리 공동의 목적을 달성하기 위한 단체다.
개인의 목표와 팀의 목적은 다를 수 있다</p>
<p><strong>서로의 목적을 반드시 공유하자</strong></p>
<p>목적과 목표는 다르다.
목적은 방향, 목표는 도착점.</p>
<p>다시, 나는 왜 사프를 하나</p>
<ul>
<li>성취 성장</li>
<li>경험 탐험</li>
<li>관계 영향력</li>
<li>자율성과 자유</li>
</ul>
<blockquote>
<p>팀빌딩에 진솔하게 목적과 목표를 공유해라.
-&gt; 안물어보면 안알려준다.</p>
</blockquote>
<h3 id="팀빌딩-조건">팀빌딩 조건</h3>
<ol>
<li>기대를 조율하기</li>
</ol>
<p>내가 사이드 프로젝트 허는 목적은 목표는? 성공은?
나의 최선은? 나의 한계는? 하고싶은 과제와 역할은 무엇인가?</p>
<blockquote>
<p>상대방이 그럴꺼구나 추측과 그렇구나는 다르다.</p>
</blockquote>
<ol start="2">
<li>안전감 만들기</li>
</ol>
<p>여기서는 솔직하게 말해도 괜찮다. 실수/실패해도 괜찮다는 믿음
어디까지 솔직해도 되는지 가늠해보기, 솔직하게 말하기 위해선 그래도 괜찮다는 믿음이 필요하다.</p>
<p>&lt;나는 어떤 사람인가&gt;까지는 잘 알려줘야 한다.
ex) 만남이 많은게 좋다. 등</p>
<blockquote>
<p>나는 이런사람이니까 너가 이래야해
너는 어떤 사람이니까 내가 어떻게 할까?</p>
</blockquote>
<p><strong>솔직함은 친절함과 배타적인게 아니다</strong>
얼마든지 솔직하게, 하지만 친절하게 말할 수 있다.
불편한 주제라도 솔직하게, 그리고 친절하게 대화가 가능하다는 믿음이 중요하다.</p>
<blockquote>
<p>다시, 안알려주면 상대방은 모른다.</p>
</blockquote>
<ol start="3">
<li>소속감 만들기
우리 이름/목표/규칙/문화/추억
팀을 함께 만들어가는 감갇, 내가 여기 속해있고 기여한다는 느낌</li>
</ol>
<h2 id="4-정리">4. 정리</h2>
<p>우리는 서로 <strong>안전감</strong>을 가지고 서로의 <strong>기대</strong>를 조정하면서 나를 꾸준히 공유하여 <strong>서로를 이해</strong>하고,
<strong>나와 너의 목표가 우리의 목표</strong>가 되고 나도 여기에 속해서 기여하고 싶다는 <strong>소속감</strong>이 있어야한다.</p>
<p>그래서 대화보다는 장치가 필요할때가 있다.
-&gt; ”팀 캔버스“
팀빌딩 시간이 있어요! 로 시작</p>
<h3 id="안전감을-위한-장치">안전감을 위한 장치</h3>
<p>쓰고싶은 말을 <strong>미리 쓰고</strong> 돌아가며 말하기</p>
<h4 id="사이드프로젝트는-취미-활동이다">사이드프로젝트는 취미 활동이다.</h4>
<h3 id="숙제가-되지-말자"><strong>숙제가 되지 말자</strong></h3>
<p>정해진 시간에 내가 가고싶어서 정해진 시간에 가지만, 숙제는 남는 시간을 내어서 한다.</p>
<h3 id="나의-열정을-다른-사람에게-강요하지-말자">나의 열정을 다른 사람에게 강요하지 말자.</h3>
<p>당연히 각자의 열정과 사정이 다르다. 
취미 활동이다.
내가 많이하면 내가 좋은 거다.</p>
<h3 id="정해진-시간을-만들자">정해진 시간을 만들자.</h3>
<p>시간을 내자 미뤄져도 좋다. 자주 만나자.</p>
<h3 id="숙제가-어렵다면-활동을-하자">숙제가 어렵다면 활동을 하자</h3>
<p>숙제가 펑크나는 빈도가 잦아지먼 그냥 숙제를 없애자. 
그 날 수다 떨다가 코딩을 해라</p>
<h3 id="모두가-모여야-할-수-있다-생각하지-말자">모두가 모여야 할 수 있다 생각하지 말자</h3>
<p>그냥 모이는 사람들끼리 할 수 있는 사람들끼리 하는게 맞다
언제든지 들어올 수 있고 언제든지 나갈 수 있고 
책임감을 가지고 함께 해야하지만 부담이 되어서도 안된다.
-&gt; 열려있는 운동 클럽처럼</p>
<h3 id="생각과-행동이-다르면-행동이-진짜-나입니다">생각과 행동이 다르면 행동이 진짜 ‘나’입니다.</h3>
<p>하고 있지 않다면 솔직하게 말하세요.
할 수 있는 만큼을 분명하게 솔직하게 말하자.
그리고 내가 기여할 수 있는 부분을 말하자.</p>
<ul>
<li>불편함/강요를 통한 외적동기를 만들지말자</li>
<li><blockquote>
<p>벌금 등 </p>
</blockquote>
</li>
</ul>
<h3 id="저마다-동기부여가-되는-이유는-다르다">저마다 동기부여가 되는 이유는 다르다.</h3>
<p>책임/열정 기대하기전에 <strong>먼저 물어보라.</strong>
내적동기에 대한 이야기를 솔직히 공유하고 문화를 고민하는 것이 동기가 된다.
안전감을 가지고 목표/기대와 관한 이야기들을 나눠보자.
각자 기대를 충족해줄 우리 문화를 만들어보자</p>
<h3 id="좋은-외적동기">좋은 외적동기?</h3>
<p><strong>릴리즈</strong>
우리들만의 이벤트! 함께 고생했던 순간을 함께 공감하자
목표는 언제나 테스트/릴리즈!</p>
<h3 id="늘어지는-이유">늘어지는 이유</h3>
<p>목표가 너무 크거나
마감기한이 너무 크거나
보상이 없을 때 -&gt; 사용자 반응 회고 회식</p>
<blockquote>
<p>결국 <strong>릴리즈가 제일 중요하다</strong>
완벽함보다 <strong>그럴싸함</strong>을 목표로 하자.</p>
</blockquote>
<p>-&gt; 우선순위 매트릭스</p>
<p>목표의 일정을 산정하려하지 말고, 정기적인 미감을 정하자.
목표기반으로 일정을 역산하지말고, 정기적인 체크를 해보다.
목표달성이 아닌 속도를 체크해보자</p>
<blockquote>
<p>마감때문에 죄책감 생기게 하지 말자</p>
</blockquote>
<h2 id="5-pm의-역할">5. PM의 역할</h2>
<p>할 수 있는 만큼 - 기대하지말라 
고양이손이라도 고마워 하자</p>
<h3 id="pm은-pt선생님이다">PM은 PT선생님이다.</h3>
<p>개인화된 계획, 일정, 진행도를 체크하는 역할이다. 
기대를 조율하고 개인에 맞는 적절한 태스크 부여
태스크를 만들고
하나더! 라고 조련하는 , 달래주는 사람이다.</p>
<h2 id="6-갈등-해결하기--결정의-기술">6. 갈등 해결하기 : 결정의 기술</h2>
<p>대부분의 문제는 결정해야할 때 발생한다.
내 의견이 무시당한다면 의견에 옳고 그름에 상관 없이 내가 부정당하는 느낌이 든다.
우리가 해야하는 것은 너와 나의 대립(문제)가 아니라 우리가 함께 풀어야 할 문제로 만들자.</p>
<h3 id="우리의-문제로-만드는-법">우리의 문제로 만드는 법</h3>
<h3 id="1">1.</h3>
<p>결정하지 않겠다 라고 정해놓고, 두 의견을 비교해보자. -&gt; 장단이 아니라 다른점을 비교하라
결정하지 말고 힘을 빼고 순수한 호기심으로 왜! 라는 질문으로 문제 인식하기. </p>
<ul>
<li>우선 결정하지 않기</li>
<li>해결법과 의도를 분리하기</li>
<li>의견과 판단과 인식을 분리해서 적어두기</li>
</ul>
<h3 id="2">2.</h3>
<p>어떻게 하면 ~ 할 수 있을꺼
상호존중은 내 입장이서 상대방의 문제를 해결하고자 도움을 주는 것이다.
그리고 도움을 요청받는 것이다.</p>
<p>우리가 원하는 문제의 해결 방안이 뭘까? </p>
<h3 id="3">3.</h3>
<p>어떻게 결정할 것인가.</p>
<blockquote>
<p>결정하기 전 합의해라.</p>
</blockquote>
<p>꼭 지금 결정할 필요가 없다. 
우리가 결정해야 하는 것은 결정이 아니라 결정하는 방법이다. 
<code>누가? 어떻게? 언제까지? 결정할 것인가!</code>
결정방법은 합의헸다면 결정은 top-down 이든 독단이든 관계없다.</p>
<h3 id="4">4.</h3>
<p>결정을 위한 마인드셋1</p>
<ul>
<li>A vs B가 아니라 A+B = C</li>
</ul>
<p>  두 의견이 베타적인가?</p>
<ul>
<li>A vs B 더 나은게 있으면 진작에 골랐다.</li>
</ul>
<p>대부분 비슷힌 (별 차이 없는) 고민이다.
이럴때 필여한것은 고민과 븐석이 아니라 신속하고 강직한결정이다.
(대개 그러지 못한다)</p>
<p>결정의 내용이아니라 <strong>결정의 방법을 합의하고 공표</strong>하면 어떤 결정이 나더라도 쉽게 빋아들일 수 있다</p>
<h3 id="pl을-뽑아라">PL을 뽑아라</h3>
<p>우리 결정을 책임질 사람</p>
<h2 id="7-사프-아이디어">7. 사프 아이디어</h2>
<ul>
<li>Pain point</li>
<li>나의 관심사에서 발견하기</li>
<li>남들이 하는 것이서 발견하기 (project hunt)</li>
</ul>
<p>사프의 가장 큰 재미는 누군가 써주는 재미다</p>
<h3 id="bm은-어떻게-찾을까">BM은 어떻게 찾을까</h3>
<p>꼭 BM있어야하는지 먼저 고민해보자
BM없다구 사프 안할 순 없다
BM중요해? 그럼 결제/광고 덕지덕지 발라라</p>
<h4 id="장르적-문법을따라가라">장르적 문법을따라가라</h4>
<p>내 서비스 카테고리가 x면 그 카테고리에 bm 가져다 써라! X)</p>
<h3 id="사프의-목적중-기술-스택이-있나">사프의 목적중 기술 스택이 있나?</h3>
<p>그럼 공표해라
많이 쓰는 보편 기술 스택을 써라
결정자를 만들어라</p>
<h3 id="사프로-이직하기">사프로 이직하기</h3>
<p>이직시 가장 중요한 경험은 최근 직장 프로젝트 경험이지, 현업의 깊이에 따라가기 어렵다.
2년차 이상은 ”업무능력“을 본다</p>
<p>사프에서는 자신감과 네트워킹, 주류기술 연습과 이력 보완정도로 생각해라.
주류 스택이 아니라면 그런 부분을 채워서 가져가라</p>
<h3 id="열정은-식는-속도가-다르지-결국-식는다">열정은 식는 속도가 다르지 결국 식는다.</h3>
<p>나는 아직 안식었다고 남한테 뭐라 뭐라 하지 말자</p>
<p><em>”나는 이 사이드 프로젝트에서 뭘 얻어가고 싶었나“</em></p>
<p>어떻게 완성할까 보다 <strong>어떻게 마무리할까</strong>를 고민해보자.</p>
<p>좋은 사람들과 지속하기 위한 동력으로 사용하되 너무 목표가 되지 않길 바랍니다.
결국에 마지막에 남는것은 사람과 추억이다.</p>
<blockquote>
<p>사람과 추억을 남기는 사이드 프로젝트를 하길 바랍니다.</p>
</blockquote>
<h2 id="8-qa">8. Q&amp;A</h2>
<h3 id="1-수익화할때-기여도와-분배">1. 수익화할때 기여도와 분배</h3>
<p>사이드로 남을건지 사업으로 갈건지의 기로라면 지금 빨리 정리해야한다(기대의 차이) </p>
<p>-&gt; <strong>팀장/주장이 정해야한다.</strong>
한번에 말고, 이런 얘기를 주기적으로. 사프/ 사업으로 분리할건데 미리 얘기하자.</p>
<h3 id="2-친구와-사이드-프로젝트">2. 친구와 사이드 프로젝트</h3>
<p>나는 기술 스택 신기술! 구현 하고 싶은대로! </p>
<p>친구는 빨리 멋있는걸 원한다(취직을 위하여)</p>
<p>팀빌딩부터 삐걱하는데 갈라설까요?</p>
<p>-&gt; 되던 안되든 <code>1달만 해보자</code> 라고 하고 시작해보자.</p>
<h3 id="3-현업-풀스택2년차-fe">3. 현업 풀스택2년차 (FE)</h3>
<p>넥스트 공부하고 싶은디 사프는 이미 해본 사람들을 원해요 어떡하죠?
-&gt; 기술 스택위한 사프라면, 스터디를 추천한다.</p>
<h3 id="4-데브옵스be">4. 데브옵스/BE</h3>
<p>PL이 없을때 의사결정 어떻게 해야할까. 리더로서 이사람한테 맡겨도 될까? (리더가 잘 모른다던지) </p>
<p>-&gt; 의사결정하는 방식만 합의가 되면 된다. 가위바위보도 괜찮고 탑다운도, 나이로 해도 된다. 또, PL의 범위를 나눠라(아키텍쳐, UI 등)</p>
]]></description>
        </item>
    </channel>
</rss>