<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-pir</title>
        <link>https://velog.io/</link>
        <description>흉내내는 사람이 아닌, 이해하는 사람이 되자</description>
        <lastBuildDate>Sun, 08 Jan 2023 08:24:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev-pir</title>
            <url>https://velog.velcdn.com/images/hgs-study/profile/7bfbd0a2-1c75-4fa4-9246-4effaf61cf53/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev-pir. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hgs-study" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[2022년 주니어 개발자 회고]]></title>
            <link>https://velog.io/@hgs-study/2022-log</link>
            <guid>https://velog.io/@hgs-study/2022-log</guid>
            <pubDate>Sun, 08 Jan 2023 08:24:47 GMT</pubDate>
            <description><![CDATA[<h3 id="첫-회고록">첫 회고록</h3>
<p>2021년 11월 1일에 줌인터넷에 입사하여 2022년 한해를 줌인터넷에서 보내면서 &#39;나도 한 번 회고를 써볼까? 말까?&#39; 고민을 많이 했던 것 같습니다. 저는 제가 했던 활동들을 깃허브 프로필에 올리곤했었는데, 생각해보니 아래 활동들 모두 다 <strong>2022년 줌인터넷에 합류하고 활동했던 것</strong>이었습니다. &#39;아, 그래도 2022년에 꾸준히는 달려왔었구나&#39;라고 생각하고, 꾸준하게 달려온 제 자신을 돌아보기 위해 첫 회고록을 써보려고 합니다.</p>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/e6f7bbfe-3388-45a4-b429-336167da8d12/image.png" alt=""></p>
<br>

<h3 id="포털개발팀-합류">포털개발팀 합류</h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/53bfc8fc-732d-48d1-8c2d-7c6a275454c3/image.png" alt="">
저는 블록체인 웹 어플리케이션을 개발하는 회사에서 1년 10개월 근무하고, 지인분의 추천으로 작년 2021년 11월에 포털개발팀(줌앱파트)에 운 좋게 합류했습니다.</p>
<p>처음 겪어보는 서비스 회사의 분위기는 제 전회사와 사뭇 달랐습니다. 한 서비스를 만들어내기 위해서 팀원들이랑 일하면서 꾸준하게 커뮤니케이션하고 한 프러덕트를 여러 팀이 <strong>열정적이고 자유롭게 수평적인 분위기에서</strong> 의견을 제시하면서 만들어가는 모습이 너무 인상 깊었습니다. 지인분에게 정말 제가 원하는 분위기의 회사에 합류하게 해주셔서 너무 감사하다고 원래 이렇게 대화를 많이하면서 개발하냐고 여러번 여쭤봤던 기억이 납니다..😅</p>
<h3 id="파일럿-프로젝트">파일럿 프로젝트</h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/18d49dba-8487-4eb0-b294-65d0829f8fa6/image.png" alt="">
줌인터넷은 대대로(?) 신규 입사자는 파일럿 프로젝트를 진행합니다. 독립적인 줌 푸시 서버가 필요했기 때문에 <strong>푸시 데이터</strong>와 기존 줌닷컴과 스타트줌에서 개인화 데이터가 브라우저 로컬 스토리지에 저장돼있었기 때문에 <strong>개인화 데이터</strong>를 <strong>AWS DynamoDB로 Reverse Modeling</strong>하는 것을 진행했습니다. </p>
<p>NoSQL을 처음 접하는 시기였고, <strong>DynamoDB를 사내에 처음 도입</strong>하는  작업이었기 때문에 DynamoDB에 대한 깊은 이해가 필요했습니다. 운이 좋게 기술이사님께서 DynamoDB를 깊게 공부하시던 시절이 있었기 때문에 이사님에게 많은 조언을 구하면서 개인적으로 공식 홈페이지와 관련 강의를 듣고, 유튜브를 보면서 DynamoDB를 학습했습니다.</p>
<p>팀 내에서는 저 혼자 선행으로 DynamoDB 학습했기 때문에 팀원들에게 리뷰 시간을 가졌고, 기술블로그를 작성했습니다. 이맘때쯤에 회사 내부 사정으로 저는 <strong>포털개발팀에서 핀테크개발팀으로 팀을 옮기게 됐습니다.</strong> 사실 이 때, 포털개발팀과 많은 친밀감을 나눈 상태였고, 제가 DynamoDB 설계를 맡았지만 어플리케이션 레벨에서 DynamoDB를 사용하지 않고 팀을 떠나게 되는 것이 조금은 아쉬웠습니다. 제가 팀을 옮기는 것이 포털개발팀에게 피해가 가지 않기 위해 핀테크개발팀에 와서도 DynamoDB 설계 시간에 꾸준히 참석해서 설계를 마쳤더니 조금은 제 마음이 후련했던 것 같습니다.</p>
<h3 id="첫-번째-기술-블로그">첫 번째 기술 블로그</h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/dc806d34-eeab-490f-9a1b-31dc1091ec74/image.png" alt=""></p>
<p>처음엔 &#39;어떻게 써야하지?&#39;, &#39;다른 분들이 너무 잘써서 내가 쓸 수 있을까?&#39; 고민을 많이 했던 것 같습니다. 많은 분들의 기술블로그를 살펴보게 됐고, 살펴본 결과 내린 결론은 <strong>&#39;과장하지 말고 정말 내가 이해하고, 아는 만큼만 제대로 전달해보자!&#39;</strong>라는 결론을 내렸습니다. </p>
<p>사실, 회사에 적응도 해야하고, 새로운 기술을 깊게 파악해야했고, 포털개발팀을 떠나야 하는 안정적이지 않은 상황에 포털개발팀원분들에게 도움을 많이 드리고 싶은 마음으로 조금은 더 침착하게, 열심히 쓰려고 했었던 것 같습니다.</p>
<p>이사님의 피드백을 받고, 제 인생 첫 번째 기술블로그인 [AWS DynamoDB 모델링] (<a href="https://zuminternet.github.io/DynamoDB/)%EC%9D%84">https://zuminternet.github.io/DynamoDB/)을</a> 포스팅할 수 있었습니다. 이 땐 정말 기뻤고, 줌인터넷에 한 일원이 된 것 같아 보람찼고, 제가 쓴 글이 회사에 도움이 될 수 있다는 게 정말 보람찼던 것 같습니다 😁</p>
<h3 id="핀테크개발팀-합류">핀테크개발팀 합류</h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/75eeaa00-c3ef-41b3-b990-ec42b2b65a62/image.png" alt="">
AWS DynamoDB 모델링 기술블로그 작성 후에 비상장 주식 거래 서비스인 <a href="https://www.getstock.io">GETSTOCK</a>을 런칭하는 팀인 핀테크개발팀에 합류했습니다.
사실, 합류하기 전에 퇴사하신 핀테크개발팀 파트장님이랑 이야기를 많이 나눴습니다. 제일 인상 깊었던 대화는 &quot;포털개발팀에서 핀테크개발팀으로 합류하게 됐는데 핀테크개발의 매력은 어떤 것이라고 생각하시나요?&quot; 라고 여쭤봤었는데, <strong>&quot;포털개발팀은 컨텐츠 위주의 데이터를 보여줌으로써 사용자의 이목을 집중시킨다면, 핀테크개발팀은 실시간성 데이터를 보여줌으로써 빠르게 변하는 데이터와 편의를 사용자에게 제공할 수 있습니다.</strong> 저희팀에 오시면 실시간성 데이터를 다루는 좋은 경험을 많이 할 수 있을 것입니다.&quot;라고 말씀해주셨습니다.
이 말씀을 듣고 난 뒤, 핀테크개발팀에 편안한 마음으로 합류하고 적응할 수 있었습니다.</p>
<h3 id="두-번째-기술블로그">두 번째 기술블로그</h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/e982b814-d616-4391-9c15-5c3ce3afadf0/image.png" alt="">
GETSTOCK 고도화에 투표 시스템이 포함됐었기 때문에, 제가 투표 시스템 API를 맡게 됐습니다. 투표 시스템의 요구사항을 받고 설계부터 API 작업까지 진행해야했습니다.</p>
<p>요구사항을 보면서 공통적으로 묶여야할 기능들과 특수한 기능들을 나열하면서 설계가 시작됐습니다. 추가, 수정, 삭제에 용이한 확장적인 비즈니스적인 설계와 처음 도입하는 기술을 다양한 도메인에서도 쉽게 사용하기 위해 설계를 해야했기 때문에 참 고민을 많이 했던 것 같습니다. </p>
<p>많은 고민을 한 결과 위와 같은 아키텍처를 구상하게 됐고, 제가 위 아키텍처를 구상하면서 했던 고민들은 <a href="https://zuminternet.github.io/vote-architecture/">OOP 기반 선착순 투표 시스템 아키텍처</a>에 포스팅하면서 상세하게 적어봤습니다.</p>
<p>두 번째 기술블로그였지만, <strong>주관적인 저의 생각과 제 글이 대외적인 회사 이미지이기 때문에 개인블로그에서 포스팅하던 느낌과는 사뭇 다르게 무게감과 책임감은 더 컸던 것 같습니다.</strong> </p>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/2377ce68-1b66-44f4-803c-b896aeb594ee/image.png" alt=""></p>
<p>기술 블로그 포스팅을 마치고 최근에 기술이사님한테 DM이 왔습니다.</p>
<p>개발자 채용 2차 기술면접은 기술이사님께서 혼자 진행하시는데, 지원자 분들에게 <strong>왜 줌인터넷에 들어오고 싶으신가요?</strong>라는 질문을 항상 하시곤 합니다. </p>
<p>정말 감사하게도 지원자분들이 제 포스팅을 거론을 하시고 줌인터넷에 들어오고 싶은 이유를 말씀하시는 것 같습니다. 저는 기술 블로그에 제 글이 최신 포스팅이라 제 글을 많은 분들께서 말씀해주시고 계시다고 생각하고 있습니다. 운이 좋게 최신글이라서 많은 분들이 거론해주시지만, 앞으로 더 노력해서 도움을 많이 드릴 수 있는 포스팅을 해야겠다는 생각을 많이 하게 된 계기가 된 것 같습니다🙇‍♂️</p>
<h3 id="사내-스터디">사내 스터디</h3>
<p><strong>1. 블로그 스터디 (2022.01 ~2022.05)</strong>
<img src="https://velog.velcdn.com/images/hgs-study/post/59e45b7f-6608-4452-9610-f88ed89f05f2/image.png" alt="">
2021년 11월에 줌인터넷에 합류를 했었을 무렵, 이미 프론트엔드와 백엔드 개발자분들 5분이서 블로그 스터디 1기가 진행되고 있었고, 저는 1월달에 <a href="https://github.com/zum-study/zum-blog-study">블로그 스터디 2기</a>에 참여하게 됐습니다. 스터디 방식은 블로그에 포스팅하여 1주차에 초안을 작성했고, 2주차에 스터디원 앞에서 발표하면서 진행됐습니다. 혼자 포스팅하려면 귀찮기도 하고, 주기도 일정하지가 않아서 &#39;나중에 공부해서 포스팅 한 번 해봐야지&#39; 라고 생각하고 미뤄왔던 주제들을 선정해서 실행으로 옮기는 것이 무척 기뻤습니다.</p>
<p>블로그 스터디의 장점은 명확했습니다. <strong>1시간이라는 시간 안에서 다른 팀원들이, 다른 환경에서 경험한 장애들, 이슈들을 들을 수 있었고, 사내 시스템에서 발생했던 장애와 이슈</strong>들이기 때문에 조금은 더 살처럼 와닿았던 것 같습니다 😄</p>
<p>지금은 타 서비스 기업에 합류하셨지만 그당시에 <a href="https://iiaii.tistory.com/11">[Redis - 실시간 랭킹 가이드 01] 캐시와 레디스</a> 라는 제목의 레디스의 글을 작성해주신 정헌님의 발표가 너무 인상 깊었습니다. &#39;레디스에 대해 이렇게 깊게 생각하시고 고민해보셨구나&#39;를 느끼게 됐고, 레디스로 인한 사내에서 긴박했던 장애 경험을 추가적으로 들을 수 있어서 너무나 좋았습니다. 정헌님의 발표를 보면서 &#39;나도 다양한 경험과 공부를 해서 팀원들에게 많은 인사이트와 경험을 공유하고 싶다&#39; 라는 생각이 많이 들었던 것 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/f0bc470b-f151-4dba-b06b-462d9a3f0b35/image.png" alt="">
<a href="https://github.com/zum-study/zum-blog-study/tree/main/%ED%98%84%EA%B1%B4%EC%88%98">1월부터 5월까지 블로그 스터디 2기</a>를 참여하면서, 그 당시 운영하던 서비스인 GETSTOCK의 미래에 대한 상황을 예측하고 생각하면서 GETSTOCK에 도움이 많이 되고, 스터디원들에게 도움이 될 수 있었던 주제로 선정했었던 것 같습니다.</p>
<p>실제로 제가 서비스하고 있는 GETSTOCK에 적용된 것이 있습니다. 
Redis SortedSet은 나중에 선착순 비상장 주식 지급 이벤트를 진행할 경우, 사용하기 위해 테스트하고 블로그 포스팅하며 팀원들에게 리뷰했었습니다. 추후에 팀원분이 GETSTOCK 내에서 시가총액을 구해서 비상장 주식 랭킹을 구해야했는데, 이때 &quot;Redis SortedSet을 사용해보시는 것은 어떠신가요?&quot; 제안을 드렸고 팀원분도 긍정적으로 제 블로그 포스팅을 보시고 적용했습니다.</p>
<p>매수자와 매도자의 거래가 시작될 때 거래번호를 생성하고 있었는데, 여러 요청이 몰리면 중복 데이터가 생성되는 것이 불가피했습니다. 이때도 저는 jOOQ Pessimistic Lock을 제안해서 팀원분께서 제 포스팅과 테스트코드를 직접 보시고 적용했었습니다. </p>
<p>마지막으로, 증권사랑 의존하고 통신하는 일이 많았기 때문에, 서버의 안정성을 생각해서 서킷브레이커를 공부하고 테스트하게 되었고, 팀원들에게 리뷰를 마친 후에 개발실장님에게 컨펌을 받고 고도화때 도입 예정이 됐습니다. (하지만 결국 사내 내부 사정으로 도입되진 않았습니다.)</p>
<p>이처럼 블로그 스터디를 시작으로 저는 다양한 인사이트를 얻을 수 있었고, 프로젝트에 도움이 됐다는 것이 너무 기뻤습니다..! 😄</p>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/ac83583f-9c40-4aab-9caa-2868a84346dd/image.png" alt="">
블로그 스터디가 마무리되고 몇 달 후에 &#39;블로그 스터디를 진행하길 잘했구나&#39;라고 생각하며 정말 보람찬 기억이 있습니다. 같이 스터디를 진행하셨던 연주님께서는 작년 여름쯤에 줌인터넷을 떠나 커머스 기업에 합류하셨는데, 해당 커머스 기업에서도 서킷브레이커가 곳곳에 쓰이신다는 말씀을 하셨고, 다행히 제가 조금이나마 도움이 된 것 같아서 너무 뿌듯했습니다. 앞으로도 팀원과 지인분들을 위해 더 좋은 글을 써야겠다고 많이 다짐하게 된 계기였던 것 같습니다..!</p>
</br>

<p><strong>2. Real Mysql 8.0 스터디 (2022.05 ~2022.07)</strong>
<img src="https://velog.velcdn.com/images/hgs-study/post/6f2163db-ecdd-4f2f-ae6e-81cc81ced7c0/image.png" alt="">
<a href="https://github.com/zunior-study/real-mysql-study">Real Mysql 8.0 스터디</a>는 저를 줌인터넷에 추천해주신 율님이랑 입사 전부터 같이 스터디를 하자고 말해왔었고, 제가 회사에 적응하면서 포털개발팀에서 이야기를 많이 나눴던 정헌님과 영지님과 함께 진행하게 됐습니다. </p>
<p>스터디를 시작하기 전에 스터디원이랑 이야기를 많이 나누었고, 공통적인 의견이 &quot;회사 일정이 여의치 않을 경우, 스터디가 소홀해질 수가 있는 것 때문에 피해를 주지 않을까 우려가 된다는 점&quot;, 그리고 너무 &quot;빡빡한 스터디가 아니었으면 좋겠다&quot;라는 점이었습니다. 그래서 <strong>자유, 함께 그리고 완주</strong>라는 모티브를 설정했고, 저는 이번 스터디는 친한 분위기 속에서 회사 일정에 구애받지 않도록 자유로운 문화를 추구했습니다.</p>
<p>스터디 초반에 스터디 끝나자마자 스터디원들에게 연락을 해서 &quot;저희 스터디가 어떤 방향성을 가지면 좋겠나요?&quot;, &quot;오늘은 이렇게 진행이 됐는데 다음 스터디 때 저희가 보완해야할 점이나 고려했으면 하는 점을 말씀해주실 수 있을까요?!&quot; 등 스터디 직후 스터디원들의 속마음을 들어 보고싶어서 스터디원을 괴롭(?)히는 스터디장이었던 것 같습니다😅
<img src="https://velog.velcdn.com/images/hgs-study/post/0309ab4e-d9a5-451a-9137-4c1b7291683e/image.png" alt=""></p>
<p>저희는 각자 주차별로 질문 2~3개씩 준비해서 스터디하는 날 각 스터디원에게 질문하면서 스터디를 진행했습니다. 이를 통해서 <strong>복습도 할 수 있었고, 서로 다르게 이해했던 부분을 정</strong>정하면서 토론하면서 질문을 주고 받을 수 있었습니다. 혼자 읽었더라면 &#39;아 이런 게 있구나&#39; 라고 생각만하고 넘어갔을텐데, 스터디를 진행하니까 학습한 내용을 <strong>직접 이야기를 해보고 토론하는 과정을 통해 더 효과적으로 이해</strong>할 수 있었던 것 같습니다.</p>
<p>저는 스터디 내내 스터디원들을 많이 괴롭혀서(?) 속마음을 많이 들어볼 수 있었습니다. 다행히도 다들 <strong>&quot;만족한다.&quot;, &quot;정말 원하던 스터디였다.&quot;, &quot;일정 압박 받지 않고 자유롭게 할 수 있어서 너무 좋았다.&quot;</strong> 등 좋게 말씀해주셔서 다같이 웃으면서 즐거운 스터디를 진행하고 마칠 수 있었습니다. 저는 앞으로도 스터디 분위기를 만들어 나갈 수 있다면 제가 경험했던 스터디 분위기와 경험을 공유드려볼 것 같습니다 😄</p>
<h3 id="동료-👨👨👦👦">동료 👨‍👨‍👦‍👦</h3>
<p><strong>&quot;제가 리더가 된다면, 용현님 같은 리더가 될 것입니다.&quot;</strong>라고 용현님(파트장)께 자주 말씀드립니다. 정말 많이 배우고 있습니다. 핀테크개발팀에 와서 느낀 것 같습니다. <strong>개발자는 개발만 잘하면 되는 것이 아니라는 것을..!</strong> 용현님을 보면서 &#39;와 이 용현님의 에너지와 열정은 어디서 나오는 거지?&#39;, &#39;팀에 대한 책임감과 프로젝트에 대한 애정이 어떻게 이렇게나 넘치지?&#39; 등 개발과 팀 그리고 프로젝트까지 애정을 쏟으며 에너자이저 같은 용현님을 볼 때마다 존경하는 마음이 많이 나오는 것 같습니다. 사실 개발뿐만 아니라 팀을 리딩하시고, 사람에게 대하는 자세, 팀원의 역량을 발휘할 수 있도록 자유로운 스케줄링을 부여, 팀원이 제시하는 새로운 기술에 대한 포용력 등 항상 다양한 인간적인 면에서 저를 놀라게 해주시고 계십니다. 용현님은 저희팀이 진행해야할 태스크를 보여주시면서 <strong>&quot;부족하신 부분이나 재밌고, 하고 싶었던 부분 말씀해주세요. 최대한 해당 태스크로 부여해드리겠습니다.&quot;</strong>라고 말씀해주십니다. 그럴 때마다 &quot;저희가 재밌는 일만 다 하면 어떡하나요?&quot; 라고 질문드리면 <strong>&quot;괜찮습니다. 재미 없는 일은 다 제가하면 됩니다. 저희 팀원분들은 저희 프로젝트 하시면서 얻어가시는 게 있으면 좋겠습니다.&quot;</strong> 라고 하시면서 저희에게 재밌는 테스크, 하고 싶었던 태스크를 다 주시고 팀내에서 재미없는 일들을 장난으로 소위 말하는 <strong>&quot;닭가슴살&quot;</strong>이라고 칭하는데, 용현님은 닭가슴살 일을 많이 맡아서 하십니다. <strong>항상 팀원들을 먼저 생각해주시고 헌신하시는 모습이 리더가 갖춰야할 모습이구나</strong>를 많이 느끼게 해주셨습니다.</p>
<p>저번 달에 타회사로 이직을 하셨지만 1년 동안 GETSTOCK을 런칭&amp;고도화하면서 <strong>저와 용현님과 힘들 때 웃고, 기쁠 때 웃으며 같이 농담을 많이 주고 받았던 재현님</strong>에게도 많은 걸 배웠습니다. GETSTOCK의 코어인 거래시스템을 담당하셨기 때문에 외부와 커뮤니케이션할 일이 정말 많았었는데, 정말 열심히 차질 없이 적극적으로 진행하시는 모습을 보며 정말 존경스러웠습니다. 그 후에도 재현님이 할당한 태스크는 정말 책임감있게 진행하시고, 퇴사날까지도 거래 플로우를 문서화해서 팀원들에게 보기 좋게 공유해주시는 재현님을 보고 <strong>&#39;와 진짜 일 잘하시고, 팀과 동료에 대한 애정이 남다르시구나&#39;</strong> 느꼈습니다. 지금 생각해보면 제가 핀테크개발팀에 합류했을 때, 용현님과 재현님의 편안함과 든든함 때문에 안정적으로 팀에 적응할 수 있었던 것 같습니다. 재현님은 자신을 <strong>&quot;농담 주고받는 걸 좋아하는 사람&quot;</strong>이라고 기억해달라고 하시고, 항상 실력에 대해서는 <strong>자신을 낮게 평가하시고 겸손하신 모습</strong>이 정말 많이 배울점이라고 생각합니다. 재현님의 퇴사날에도 전해드렸었지만, 용현님, 재현님과 함께한 1년 동안 정말 팀워크가 좋았고 또 다시 이런 <strong>&#39;재밌고, 실력있고, 서로 신뢰하는 팀을 만날 수 있을까?&#39;</strong> 생각이 많이 듭니다. 재입사를 추진할 수 있다면 참 좋을 것 같습니다. </p>
<p><strong>&quot;의빈님 좀 쉬면서 하세요 몸 상합니다.&quot;</strong> 용현님과 재현님과 제가 5개월 전에 합류하신 저희팀 신입 의빈님께 자주 말씀드리던 말이었습니다. (과거형인 이유는 저희가 계속 말씀드려도 아직까지도 항상 새벽까지 열심히 공부하고 오시기 때문입니다!) <strong>의빈님은 신입분이시지만 제가 정말 존경합니다.</strong> 취업 준비를 열심히 하시면 취업하시고는 조금은 공부를 덜 할 수도 있을텐데, 의빈님은 입사하시고도 <strong>활활 타오르시는 열기가 느껴질 정도</strong>입니다. 저도 많이 모자라지만, 의빈님에게 공부 방향성과 가이드를 조금 제시해드렸는데 의빈님은 제 의견을 적극 수렴해주시고 스터디와 포스팅을 하면서 정말 빠른 속도로 체득하시고 계십니다. 정말 대단하다고 느꼈던 것이, 기존에 어느정도 의빈님께서 설정한 공부 방향(카프카, 쿠버네티스)이 있었음에도 불구하고 <strong>&quot;현재 우선순위는 제가 설정한 방향성이 아니라 건수님께서 말씀해주신 방향성(OOP, 자바, 스프링)이 더 우선순위인 것 같습니다.&quot;</strong> 라고 말씀하시고 기존에 진행하려던 스터디도 취소하시고 제 공부 방향성으로 공부를 하고 계시다는 것입니다. 저도 사실 이게 정말 쉽지 않은 것이라고 생각하고 있기 때문에 의빈님께 정말 대단하신 것 같다고 말씀드리고 있고, 정말 감사하게 생각하고 있습니다. 저도 꾸준히 공부를 하고 있지만, 의빈님이 팀에 합류하심으로써 <strong>활활 타오르시는 좋은 자극과 영향</strong>을 받고 있어서 정말 리스펙하고 감사하게 생각하고 있습니다. 의빈님은 부족한 저의 글들을 주변 지인분들에게 공유하고 소개해주시기 때문에, 더 좋은 글들을 포스팅할 수 있는 자극을 주시고 있어서 정말 감사하게 생각하고 있습니다.</p>
<h3 id="오픈소스-기여">오픈소스 기여</h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/f7d29486-7901-40df-94da-c8d472d1a1cd/image.png" alt=""></p>
<p>언젠가 한 번쯤, 오픈소스를 기여해보고 싶다는 생각은 하고 있었지만 실천은 하고 있지 않고 &#39;내가 기여할 수 있을까?&#39;라는 생각을 많이 했던 것 같습니다.</p>
<p>여러 오픈소스에 기여하고 계신 지인 개발자분께서 <strong>&quot;오픈소스 코드를 보면서 많이 배울 수 있고, 실제로 OOP 학습을 따로 하지 않고 오픈소스를 보면서 많이 체득한 것 같다&quot;</strong>라고 말씀해주셨습니다. 이 이야기를 듣고, 저도 &#39;나중에 기회가 된다면 한 번 시도를 해봐야겠다&#39;라고 생각했습니다.</p>
<p>한창 Redisson에 대해 테스트 코드를 실행하면서 공부하던 중에, 작은 문제를 하나 발견해서 &#39;나도 기여를 해볼까?&#39; 생각하게 됐고, 운이 좋게 작게나마 기여를 하게 됐습니다. 개발 인생 처음으로 기여했던 것이여서 내심 기뻤으며, 앞으로도 오픈소스를 사용하다 문제를 발견하면 주도적으로 고쳐보고 시도해볼 수 있겠다는 생각을 하게 됐습니다.</p>
<p>지인 개발자분 덕분에 작은 시도를 할 수 있었던 것에 너무 감사했습니다.</p>
<h3 id="학습-방법">학습 방법</h3>
<p>저는 2022년도에 학습 방법을 바꿨습니다.
<img src="https://velog.velcdn.com/images/hgs-study/post/1cba3b86-49a9-4bb0-b82a-81999cbb8680/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/27511c3c-4861-41de-865d-5f3c23ae70e4/image.png" alt="">
2021년도에는 공부하면서 기록을 남기는 것을 좋아했습니다.</p>
<p>공부하고 기록을 남기게 되면, 내가 학습한 것을 추후에 다시 되돌아 볼 때 기억하기 편하고, 깃허브에 잔디도 심어지기 때문에 일석이조라고 생각했습니다.</p>
<p>하지만, 저는 어느새부터 <strong>&quot;기록을 위한 학습&quot;</strong>을 하고 있었습니다. 책을 읽고, 강의를 들으면서 깃허브나 노션에 기록을 하기 때문에 &#39;나중에 다시 보며 되지~&#39; 라고 생각하면서 해당 내용을 제대로 이해하지 않은 채, 책과 강의를 듣고 있었습니다. 이렇기 때문에 학습 시간은 더 길어졌고 (책 +기록, 강의+기록), 기록에 의존하는 상황이 됐습니다.</p>
<p>2022년엔 지인 개발자분들과 이야기를 나누게 됐습니다. 지인 개발자분께서는 학습 하시면서 <strong>이해되지 않는 부분이 있으면, 그 부분을 이해하기 위해 노력을 많이 하시고 끝내 자신의 것</strong>으로 만든다고 하셨습니다. 그리고 읽는 속도 또한 저보다 훨씬 빠르게 읽고 이해하고 계셨습니다. 이 말씀을 들으면서, 저도 제 학습방법이 저한테 맞지 않다는 걸 깨닫고 2022년엔 학습할 때 최대한 이해하면서 제 것으로 만드는 것을 중점적으로 학습했습니다.</p>
<p>학습 방법을 바꿔보니, 제 머릿 속에 저 많은 내용들이 남게 되었고 &#39;내가 이젠 이해하면서 읽고 있구나&#39;를 느끼게 됐습니다. 그렇기 때문에 2022년 깃허브에 기록이 목적이 아닌, 학습을 위한 커밋들로 채우게 된 것 같습니다. </p>
<p>학습을 하면서 저만의 학습 방법을 찾은 정말 감사한 한 해였던 것 같습니다.</p>
<h3 id="앞으로-목표">앞으로 목표</h3>
<p>2년 전부터 다짐한 개발 신념은 <strong>&quot;조금을 하더라도 매일 꾸준하게 공부하는 습관을 유지하자&quot;</strong> 입니다.</p>
<p>운동을 꾸준히 하다가 3일~5일을 쉬면 다시 헬스장에 가기 싫은 것처럼, 저는 공부를 꾸준히 하다가 며칠 쉬게되면 다시 책상에 앉기가 너무 힘들었습니다. 그렇기 때문에 2년 전부터 꾸준하게 습관을 들이려고 나름대로 많이 노력해왔던 것 같습니다. </p>
<p>저는 <strong>경력이 쌓이면 쌓일 수록 더 불안하고 무게감을 더 느끼게 되는 것 같습니다.</strong> 경력이 쌓일수록 회사에서 바라는 저의 역할과 책임은 더 많아 지고, 저보다 연차가 더 낮으신 분들은 리드하고 방향성을 제시해야하는 등 책임감이 더 커져만 가기 때문에 꾸준한 성장을 갈구하고 있는 것 같습니다.</p>
<p>개발 특성상 현재 사용하고 있는 기술은 계속 릴리즈 되고 있고, 최신 기술 트렌드는 빠르게 변화하고 있기 때문에 기존 기술들은 익히고 새로운 기술엔 유연하게 대응해야하기 때문에 시대를 따라가려면 회사에서 하는 업무로는 조금은 부족할 수도 있겠다 라는 생각을 하게 됐습니다. 회사에서의 요구사항과 비즈니스는 계속 추가되고 변화하기 때문에 변화할 때마다 기술 리서치를 해볼 수 있겠지만, 미리 지식을 조금이라도 더 알고 있으면 변화하는 요구사항과 비즈니스를 리뷰하는 회의에서 조금은 더 주도적으로 커뮤니케이션 할 수 있고, 더 다양한 해결방법도 적극적으로 제시할 수 있을 것 같다고 생각했습니다.</p>
<p>추가적으로 경쟁사회에서 &quot;개발자&quot;라는 직업을 선택한 이상, 저의 가치를 올리는 것은 다른 누구도 아닌 제 자신이라고 생각합니다. 그렇기 때문에 앞으로도 꾸준히 성장하는 것이 목표이며, 제 작은 소망이 있다면, 먼 훗날 미래의 제가 과거를 되돌아봤을 때, &#39;한 분야에서 최고는 못 됐지만 꾸준히 노력은 했구나&#39; 라는 생각이 들었으면 합니다 🙏🙏</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Saga패턴을 이용한 분산 트랜잭션 제어(결제 프로세스 실습)]]></title>
            <link>https://velog.io/@hgs-study/saga-1</link>
            <guid>https://velog.io/@hgs-study/saga-1</guid>
            <pubDate>Mon, 15 Aug 2022 17:21:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이번 포스팅은 msa에서 분산 트랜잭션을 제어하는 방법 중 하나인 <strong>saga-pattern</strong>을 활용해서 분산 서버에서 간단한 결제 프로세스를 구현해봅니다.</p>
</blockquote>
<h2 id="1-분산-트랜잭션">1. 분산 트랜잭션</h2>
<h3 id="💡-span-stylecolor-lightcoral분산-트랜잭션을-왜-제어해야하는가span">💡 <span style="color :LightCoral">분산 트랜잭션을 왜 제어해야하는가?</span></h3>
<ul>
<li>아래 그림은 결제 프로세스를 MSA 분산 환경으로 간략하게 나타냈습니다. 각 도메인은 각각의 DB를 바라보고 있으며, 결제 프로세스는 세 서비스와 DB를 거쳐야 완료됩니다.</li>
<li>문제없이 항상 완료만 되는 상황이면 좋겠지만 Order 모듈에서 주문을 생성해서 DB에 저장한 후에, <strong>Stock에서 예외처리 혹은 장애</strong>가 나서 롤백해야한다면 어떻게 처리해야할까요? </li>
</ul>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/7273f7ae-22a2-4c31-ba62-e8003cf87631/image.png" alt=""></p>
<h2 id="2-saga-pattern">2. Saga Pattern</h2>
<h3 id="❓-span-stylecolor-lightcoral-saga-pattern이란span">❓ <span style="color :LightCoral"> Saga Pattern이란?</span></h3>
<ul>
<li>Saga Pattern은 마이크로 서비스에서 데이터 일관성을 관리하는 방법입니다.</li>
<li>각 서비스는 로컬 트랜잭션을 가지고 있으며, 해당 서비스 데이터를 업데이트하며 <strong>메시지 또는 이벤트를 발행</strong>해서, 다음 단계 트랜잭션을 호출하게 됩니다.</li>
<li>만약, 해당 프로세스가 실패하게 되면 데이터 정합성을 맞추기 위해 이전 트랜잭션에 대해 <strong>보상 트랜잭션</strong>을 실행합니다.</li>
<li>NoSQL 같이 분산 트랜잭션 처리를 지원하지 않거나, 각기 다른 서비스에서 다른 DB 밴더사를 이용할 경우에도 Saga Pattenrn을 이용해서 데이터 일관성을 보장 받을 수 있습니다.</li>
</ul>
<blockquote>
<p>간단히 정리하자면, 각기 다른 분산 서버에 다른 DB 밴더사를 사용하고 있어도, Saga Pattern을 사용하면 <strong>데이터 일관성을 보장</strong>받을 수 있다. 또한 트랜잭션 실패시, <strong>보상 트랜잭션</strong>으로 데이터 정합성을 맞출 수 있다.</p>
</blockquote>
<br>

<h3 id="❓-span-stylecolor-lightcoral-choreography-방식이란span">❓ <span style="color :LightCoral"> Choreography 방식이란?</span></h3>
<blockquote>
<p>Saga Pattern은 Orchestration 방식과 Choreography 방식이 존재하는데 이번 포스팅에서는 Choreography 방식만 소개합니다. Orchestration 방식을 알아보고 싶으시다면 <a href="https://docs.microsoft.com/ko-kr/azure/architecture/reference-architectures/saga/saga">마이크로소프트 공식 홈페이지</a>를 참조해주세요</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/8894d10a-d687-4e2b-b121-babef08e65ec/image.png" alt=""></p>
<ul>
<li>Choreography 방식은 서비스끼리 직접적으로 통신하지 않고, 이벤트 Pub/Sub을 활용해서 통신하는 방식입니다.</li>
<li>프로세스를 진행하다가 여러 서비스를 거쳐 서비스(Stock, Payment)에서 실패(예외처리 혹은 장애)가 난다면 <strong>보상 트랜잭션 이벤트</strong>를 발행합니다.</li>
<li>장점으론, 간단한 workflow에 적합하며 추가 서비스 구현 및 유지관리가 필요하지 않아서 간단하게 세팅할 수 있습니다.</li>
<li>단점으론, 트랜잭션을 시뮬레이션하기 위해 모든 서비스를 실행해야하기 때문에 통합테스트와 디버깅이 어려운 점이 있습니다.</li>
</ul>
<br>

<h2 id="3-결제-어플리케이션-구성">3. 결제 어플리케이션 구성</h2>
<h3 id="✅-span-stylecolor-lightcoral정상적인-분산-트랜잭션-프로세스-span">✅ <span style="color :LightCoral">정상적인 분산 트랜잭션 프로세스 </span></h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/efd07950-91e8-434a-8214-5f58f189294f/image.png" alt="">
<strong>(1)</strong> 사용자 요청을 받은 Order 서비스에서 주문 번호를 생성해서 DB에 적재 
<strong>(2)</strong> Kafka에 주문번호 생성 이벤트 발행
<strong>(3)</strong> Stock에서 주문번호 생성 이벤트를 구독해서 해당 재고 빼기
<strong>(4)</strong> Kafka에 재고 빼기 이벤트 발행
<strong>(5)</strong> Payment에서 재고 빼기 이벤트를 구독해서 결제 프로세스 진행</p>
<h3 id="❌-span-stylecolor-lightcoral실패-분산-트랜잭션-프로세스-span">❌ <span style="color :LightCoral">실패 분산 트랜잭션 프로세스 </span></h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/d00adf4d-21dc-494e-8b0b-e9fb511162d4/image.png" alt=""></p>
<blockquote>
<p>결제 분산 트랜잭션 진행 중, <strong>Payment 서비스에서 트랜잭션이 실패할 경우</strong>를 가정한 그림입니다. 빨간색 화살표는 <strong>Producer</strong>, 파란색 화살표는 <strong>Consumer</strong>를 뜻합니다.</p>
</blockquote>
<p><strong>(1)</strong> Payment 서비스에서 트랜잭션 실패
<strong>(2)</strong> Payment에서 <strong>재고 롤백</strong> 이벤트 발행
<strong>(3)</strong> Stock에서 재고 롤백 이벤트를 구독해서 해당 재고 플러스
<strong>(4)</strong> Stock에서 <strong>주문 롤백</strong> 이벤트 발행
<strong>(5)</strong> Order에서 주문 롤백 이벤트를 구독해서 해당 주문 삭제</p>
<br>

<h2 id="4-결제-어플리케이션-실습">4. 결제 어플리케이션 실습</h2>
<blockquote>
<p>order, stock, payment 세 서비스 코드가 비슷하기 때문에 order 서비스 코드로만 포스팅하겠습니다. 코드가 궁금하신 분은 <a href="https://github.com/hgs-study/payment-saga-pattern">GitHub</a>을 참고해주세요. 실제 데이터는 order 서비스에서만 저장하고 stock, payment 서비스에서는 로그 출력으로 대신합니다. 또한 카프카와 주키퍼는 docker-compose로 띄우며 실습 환경 설정합니다.</p>
</blockquote>
<h4 id="의존성-설정">의존성 설정</h4>
<pre><code class="language-java">dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    implementation &#39;org.springframework.kafka:spring-kafka&#39;
    compileOnly &#39;org.projectlombok:lombok&#39;
    annotationProcessor &#39;org.projectlombok:lombok&#39;
    runtimeOnly &#39;com.h2database:h2:2.1.214&#39;
}</code></pre>
<br>

<h4 id="applicationyml-설정">application.yml 설정</h4>
<ul>
<li>카프카 Producer, Consumer 설정<pre><code class="language-yml">spring:
kafka:
  consumer:
    bootstrap-servers: localhost:9092
    group-id: group-01
    key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    value-deserializer: org.apache.kafka.common.serialization.LongDeserializer
  producer:
    bootstrap-servers: localhost:9092
    key-serializer: org.apache.kafka.common.serialization.StringSerializer
    value-serializer: org.apache.kafka.common.serialization.LongSerializer</code></pre>
</li>
</ul>
<br>

<h4 id="docker-composeyml-설정">docker-compose.yml 설정</h4>
<ul>
<li>주키퍼, 카프카 컨테이너 설정</li>
<li>카프카 토픽 설정 (주문번호 생성, 주문번호 롤백, 재고 빼기, 재고 롤백)<pre><code class="language-yml">version: &quot;3&quot;
services:
zookeeper:
  image: wurstmeister/zookeeper
  ports:
    - &quot;2181:2181&quot;
kafka:
  image: wurstmeister/kafka
  ports:
    - &quot;9092:9092&quot;
  environment:
    KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
    KAFKA_CREATE_TOPICS: &quot;order-create:1:1, order-rollback:1:1, stock-decrease:1:1, stock-rollback:1:1&quot;
    KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock</code></pre>
</li>
</ul>
<h4 id="orderservicejava">OrderService.java</h4>
<pre><code class="language-java">@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    private final StockProducer stockProducer;

    public void order(String productId){
        final Order order = new Order(productId);
        final Order newOrder = orderRepository.save(order);
        stockProducer.order(newOrder.getId());
    }

    public void delete(Long orderId){
        orderRepository.deleteById(orderId);
        log.info(&quot;{}번 주문번호 삭제&quot;, orderId);
    }
}
</code></pre>
<ul>
<li><code>order()</code> : 주문 번호를 생성하고 <code>주문번호 생성</code>이벤트 호출</li>
<li><code>delete()</code> : <strong>분산 트랜잭션 실패 시</strong>, 주문번호를 삭제하며 롤백을 위한 메소드</li>
</ul>
<br>


<h4 id="stockproducerjava">StockProducer.java</h4>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
public class StockProducer {
    private final KafkaTemplate kafkaTemplate;

    public void order(Long orderId){
        kafkaTemplate.send(&quot;order-create&quot;, orderId);
    }
}</code></pre>
<ul>
<li><code>order()</code> : 주문번호 생성 이벤트로, <code>order-create</code> 토픽에 orderId를 보냄</li>
<li>Stock 서비스에선 <code>order-create</code> 토픽을 구독하며 해당 이벤트를 읽고 다음 트랜잭션을 진행</li>
</ul>
<br>


<h4 id="rollbackconsumerjava">RollbackConsumer.java</h4>
<pre><code class="language-java">@Slf4j
@Component
@RequiredArgsConstructor
public class RollbackConsumer {
    private final OrderService orderService;

    @KafkaListener(topics = &quot;order-rollback&quot;, groupId = &quot;group-01&quot;)
    public void rollbackOrder(Long orderId){
        log.error(&quot;======== [Rollback] order-rollback, orderId :{}======== &quot;, orderId);
        orderService.delete(orderId);
    }
}</code></pre>
<ul>
<li><code>rollbackOrder()</code> : <strong>보상 트랜잭션</strong>으로, <code>order-rollback</code> 토픽을 구독하며 <strong>데이터 일관성</strong>을 보장하기 위해 해당 주문을 삭제</li>
</ul>
<br>


<h3 id="보상-트랜잭션-실습">보상 트랜잭션 실습</h3>
<blockquote>
<p>Payment 서비스에서 트랜잭션이 실패해서 보상 트랜잭션 발생상황을 가정합니다</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/1d6ab282-edf3-4932-a676-f69d76e14ee4/image.png" alt=""></p>
<p>기대 <strong>(1) :</strong> Payment 서비스에서 <code>재고 롤백</code> 이벤트 발행<br>기대 <strong>(2) :</strong> Stock 서비스에서 <code>재고 롤백</code> 이벤트 구독 후 재고 롤백 
기대 <strong>(3) :</strong> Stock 서비스에서 <code>주문 롤백</code> 이벤트 발행
기대 <strong>(4) :</strong> Order 서비스에서 <code>주문 롤백</code> 이벤트 구독 후 주문 롤백 </p>
<h4 id="✅-1-payment-서비스에서-재고-롤백-이벤트-발행">✅ 1. Payment 서비스에서 재고 롤백 이벤트 발행</h4>
<h4 id="stockdecreasedconsumerjava">StockDecreasedConsumer.java</h4>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/d371dd19-856c-4f4f-91e9-9141fe3bf6d6/image.png" alt=""></p>
<p align="center"><img src="https://velog.velcdn.com/images/hgs-study/post/04946032-932f-476a-b52f-9dcb34ba6e23/image.png" width="70%"/></p>

<h4 id="paymentservicejava">PaymentService.java</h4>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/a062ae73-561e-4eda-ba39-328e45c9f6bc/image.png" alt=""></p>
<h4 id="stockproducerjava-1">StockProducer.java</h4>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/3ce7131a-c2c0-4ca5-9b78-af89249ffbd9/image.png" alt=""></p>
<p><strong>1.</strong> <code>errorPerHalf()</code> 메서드로 50% 확률로 RuntimeException을 발생 
<strong>2.</strong> 트랜잭션 실패로 인해 catch문의 <code>rollbackDecreasedStock()</code> 메서드 실행
<strong>3.</strong> 카프카 브로커에 <code>stock-rollback</code> 토픽에 orderId가 2인 메세지를 전달하면서 <strong>보상 트랜잭션 실행</strong></p>
<br>


<h4 id="✅-2-stock-서비스에서-재고-롤백-이벤트-구독-후-재고-롤백">✅ 2. Stock 서비스에서 재고 롤백 이벤트 구독 후 재고 롤백</h4>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/94c6b1e8-fb09-4c94-940b-3bde94bb9fd3/image.png" alt=""></p>
<ul>
<li>정상 트랜잭션 : 재고가 정상적으로 1개 줄여진 것을 확인</li>
<li>보상 트랜잭션 : Payment 서비스에서 트랜잭션이 실패해서 orderId가 2인 상품 <strong>재고 롤백</strong></li>
</ul>
<br>

<h4 id="✅-3-order-서비스에서-주문-롤백-이벤트-구독-후-주문-롤백">✅ 3. Order 서비스에서 주문 롤백 이벤트 구독 후 주문 롤백</h4>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/9f223ce7-c6f7-4430-96ad-08b2c19a3a4e/image.png" alt=""></p>
<ul>
<li>정상 트랜잭션 : 주문 번호 생성</li>
<li>보상 트랜잭션 : Payment 서비스에서 트랜잭션이 실패해서 orderId가 2인 주문 번호 삭제하면서 <strong>보상 트랜잭션 완료</strong></li>
</ul>
<blockquote>
<p>결제 프로세스를 진행하면서 Payment 서비스에서 트랜잭션이 실패했기 때문에 Kafka를 통해서 이벤트 Pub/Sub으로 각각 서비스에 보상 트랜잭션이 발행돼서 모든 서비스가 데이터 일관성을 보장된 것을 확인할 수 있다.</p>
</blockquote>
<br>


<h4 id="실습-github-repository">실습 GitHub Repository</h4>
<ul>
<li><a href="https://github.com/hgs-study/payment-saga-pattern">https://github.com/hgs-study/payment-saga-pattern</a></li>
</ul>
<h4 id="참고">참고</h4>
<ul>
<li><a href="https://docs.microsoft.com/ko-kr/azure/architecture/reference-architectures/saga/saga">https://docs.microsoft.com/ko-kr/azure/architecture/reference-architectures/saga/saga</a></li>
<li><a href="https://csy7792.tistory.com/349">https://csy7792.tistory.com/349</a></li>
<li><a href="https://sarc.io/index.php/development/2128-saga-pattern">https://sarc.io/index.php/development/2128-saga-pattern</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[레디스를 이용한 기프티콘 선착순 이벤트 구현]]></title>
            <link>https://velog.io/@hgs-study/redis-sorted-set</link>
            <guid>https://velog.io/@hgs-study/redis-sorted-set</guid>
            <pubDate>Sat, 07 May 2022 08:38:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이번 포스팅은 레디스에서 제공해주는 자료구조 중 하나인 <strong>Sorted Set</strong>을 간단하게 설명하고, Sorted Set을 이용해서 치킨 기프티콘 선착순 이벤트를 구현해봅니다.</p>
</blockquote>
<h2 id="1-왜-레디스로-구현해야하나">1. 왜 레디스로 구현해야하나?</h2>
<h3 id="💡-span-stylecolor-lightcoral선착순-이벤트에서-레디스가-사용되는-이유span">💡 <span style="color :LightCoral">선착순 이벤트에서 레디스가 사용되는 이유?</span></h3>
<ul>
<li>보통 선착순 이벤트는 특정 시간에 트래픽이 몰리기 때문에 서버가 다운되거나 원활하지 못한 이벤트를 참여해보신 적이 한 번씩 경험해보셨을 겁니다.</li>
<li>이번 포스팅에선, 레디스에서 제공하는 자료구조 중 하나인 <strong>Sorted Set</strong>을 활용하여 <code>모든 요청이 DB에 바로 부하가 가지 않고 차례대로 일정 범위만큼씩 처리</code>하는 구성을 해보려고합니다.</li>
<li>선착순 이벤트 시 대기하고 있는 인원에 대해 대기열 순번을 표출하기 용이합니다.</li>
</ul>
<h2 id="2-레디스-sorted-set">2. 레디스 Sorted Set</h2>
<h3 id="❓-span-stylecolor-lightcoral-sorted-set이란span">❓ <span style="color :LightCoral"> Sorted Set이란?</span></h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/2e3811e7-7e8c-4df9-aa15-961d8a03bd46/image.png" alt=""></p>
<ul>
<li>Sorted Sets은 <strong>key 하나에 여러개의 score와 value</strong>로 구성하는 자료구조입니다.</li>
<li>Value는 score로 sort되며 중복되지 않습니다.</li>
<li>Score가 같으면 value로 sort됩니다.</li>
<li>Sorted Sets에서는 집합이라는 의미에서 value를 member라 부릅니다.</li>
<li>Sorted Sets은 주로 sort가 필요한 곳에 사용됩니다.</li>
</ul>
<blockquote>
<p>간단히 정리하자면, 한 Key에 여러 value와 score를 가지고 있으며 <strong>중복되지 않는 value로 score순으로 데이터를 정렬</strong>합니다. </p>
</blockquote>
<h2 id="3-기프티콘-선착순-이벤트-구조">3. 기프티콘 선착순 이벤트 구조</h2>
<h3 id="📌-span-stylecolor-lightcoralsorted-set을-활용한-선착순-이벤트-span">📌 <span style="color :LightCoral">Sorted Set을 활용한 선착순 이벤트 </span></h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/9add5f28-20d4-4d36-b72e-2413bf323955/image.png" alt=""></p>
<ul>
<li>Sorted Set Key에는 <strong>GIFTICON_EVENT</strong> 를 설정합니다.</li>
<li>Value에는 <strong>사용자명(Pir, David, Foo, John)</strong>을 설정합니다. 해당 예제는 사용자명으로 했지만 사용자명이 중복일 경우, 사용자에 대한 <code>고유한 값</code>으로 세팅하면 됩니다.</li>
<li>Score에는 <code>참여한 사람들을 순서대로 정렬</code>하기 위해, 이벤트를 참여한 시간을 <strong>유닉스타임(m/s)</strong> 값으로 넣어줍니다. </li>
</ul>
<h2 id="4-어플리케이션-구성">4. 어플리케이션 구성</h2>
<h3 id="📌-span-stylecolor-lightcoral-기프티콘-30개-선착순-이벤트-시-100명이-요청했을-경우-span">📌 <span style="color :LightCoral"> 기프티콘 30개 선착순 이벤트 시 100명이 요청했을 경우 </span></h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/ec1bf878-c934-499a-8f3f-ab7a8f93bfa1/image.png" alt=""></p>
<p><strong>(1)</strong> 100명의 유저가 기프티콘 발급 요청을 합니다.
<strong>(2)</strong> 100명의 유저는 대기열에 쌓이게 됩니다.
<strong>(3)</strong> 1초마다 동기화 돼어 기프티콘 발급 성공, 실패 로직을 수행합니다.
<strong>(4)</strong> 성공시, 이벤트가 종료되지 않았으면 <strong>100명의 유저중 먼저 들어온 순서대로 <code>10명씩 기프티콘 발급</code></strong>합니다.
<strong>(5)</strong> 실패시, 다음 대기열로 돌아가면서 <strong><code>남은 대기열 순번을 표출</code></strong>합니다.
<strong>(6)</strong> 해당 과정을 반복하면서 이벤트는 종료(30개 발급완료)합니다</p>
<blockquote>
<p>10개씩 발급하는 이유는 <strong><code>DB 부하를 줄이기 위해</code></strong>입니다. 해당 예시는 10개이지만 실제 서비스에선 10000개의 요청 중 1000개의 기프티콘을 발급할 경우 1초마다 50개씩 순차적으로 발급해서 DB부하를 줄일 수 있을 것 같습니다.</p>
</blockquote>
<h2 id="5-치킨-기프티콘-선착순-이벤트-실습">5. 치킨 기프티콘 선착순 이벤트 실습</h2>
<blockquote>
<p>30개의 치킨 기프티콘 선착순 이벤트를 진행합니다. 100명의 사용자가 요청하고, 1초마다 더 빨리 들어온 10명의 사용자에게 치킨 기프티콘을 발급합니다. 치킨을 발급 받지 못한 사용자는 대기열 순번이 1초마다 동기화됩니다. 30개의 치킨 기프티콘이 모두 발급되면 해당 이벤트는 종료됩니다.</p>
</blockquote>
<h4 id="의존성-설정">의존성 설정</h4>
<pre><code class="language-java">dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-data-redis&#39;
}</code></pre>
<ul>
<li>spring data redis 버전별 지원현황 확인(<a href="https://spring.io/projects/spring-data-redis#support">https://spring.io/projects/spring-data-redis#support</a>)</li>
</ul>
<br>

<h4 id="applicationyml-설정">application.yml 설정</h4>
<pre><code class="language-yml">spring:
  redis:
    host: localhost
    port: 6379</code></pre>
<ul>
<li>레디스 기본 설정 host와 port 설정</li>
</ul>
<br>

<h4 id="eventschedulerjava">EventScheduler.java</h4>
<pre><code class="language-java">@Slf4j
@Component
@RequiredArgsConstructor
public class EventScheduler {

    private final GifticonService gifticonService;

    @Scheduled(fixedDelay = 1000)
    private void chickenEventScheduler(){
        if(gifticonService.validEnd()){
            log.info(&quot;===== 선착순 이벤트가 종료되었습니다. =====&quot;);
            return;
        }
        gifticonService.publish(Event.CHICKEN);
        gifticonService.getOrder(Event.CHICKEN);
    }
}
</code></pre>
<ul>
<li>1초마다 도는 대기열 동기화 스케줄러 구성</li>
<li>기프티콘이 30개 모두 발급되면 선착순 이벤트 종료</li>
<li>이벤트가 종료되지 않았으면, 기프티콘을 발급하고 남은 대기열에 순번 표출</li>
</ul>
<br>


<h4 id="gifticonservicejava">GifticonService.java</h4>
<pre><code class="language-java">    public void addQueue(Event event){
        final String people = Thread.currentThread().getName();
        final long now = System.currentTimeMillis();

        redisTemplate.opsForZSet().add(event.toString(), people, (int) now);
        log.info(&quot;대기열에 추가 - {} ({}초)&quot;, people, now);
    }

    public void getOrder(Event event){
        final long start = FIRST_ELEMENT;
        final long end = LAST_ELEMENT;

        Set&lt;Object&gt; queue = redisTemplate.opsForZSet().range(event.toString(), start, end);

        for (Object people : queue) {
            Long rank = redisTemplate.opsForZSet().rank(event.toString(), people);
            log.info(&quot;&#39;{}&#39;님의 현재 대기열은 {}명 남았습니다.&quot;, people, rank);
        }
    }

    public void publish(Event event){
        final long start = FIRST_ELEMENT;
        final long end = PUBLISH_SIZE - LAST_INDEX;

        Set&lt;Object&gt; queue = redisTemplate.opsForZSet().range(event.toString(), start, end);
        for (Object people : queue) {
            final Gifticon gifticon = new Gifticon(event);
            log.info(&quot;&#39;{}&#39;님의 {} 기프티콘이 발급되었습니다 ({})&quot;,people, gifticon.getEvent().getName(), gifticon.getCode());
            redisTemplate.opsForZSet().remove(event.toString(), people);
            this.eventCount.decrease();
        }
    }</code></pre>
<ul>
<li><code>addQueue()</code> : 사람들의 요청을 value : 사람의 고유한 값, score : 현재 시간(m/s)으로 대기열에 추가</li>
<li><code>getOrder()</code> : 차례대로 들어온 사람들의 요청을 기반으로 대기열 순번 표출</li>
<li><code>publish()</code> : 1초마다 이벤트에 참여하는 사람수(10명)씩 기프티콘 발급 후 대기열에서 제거</li>
</ul>
<br>


<h4 id="테스트-목록">테스트 목록</h4>
<img src="https://velog.velcdn.com/images/hgs-study/post/703162cb-be01-4d70-b43a-fe5556dc5ff8/image.png">

<br>


<h4 id="선착순이벤트-100명에게-기프티콘-30개-제공">선착순이벤트 100명에게 기프티콘 30개 제공</h4>
<pre><code class="language-java">    @Test
    void 선착순이벤트_100명에게_기프티콘_30개_제공() throws InterruptedException {
        final Event chickenEvent = Event.CHICKEN;
        final int people = 100;
        final int limitCount = 30;
        final CountDownLatch countDownLatch = new CountDownLatch(people);
        gifticonService.setEventCount(chickenEvent, limitCount);

        List&lt;Thread&gt; workers = Stream
                                .generate(() -&gt; new Thread(new AddQueueWorker(countDownLatch, chickenEvent)))
                                .limit(people)
                                .collect(Collectors.toList());
        workers.forEach(Thread::start);
        countDownLatch.await();
        Thread.sleep(5000); // 기프티콘 발급 스케줄러 작업 시간

        final long failEventPeople = gifticonService.getSize(chickenEvent);
        assertEquals(people - limitCount, failEventPeople); // output : 70 = 100 - 30
    }

    private class AddQueueWorker implements Runnable{
        private CountDownLatch countDownLatch;
        private Event event;

        public AddQueueWorker(CountDownLatch countDownLatch, Event event) {
            this.countDownLatch = countDownLatch;
            this.event = event;
        }

        @Override
        public void run() {
            gifticonService.addQueue(event);
            countDownLatch.countDown();
        }
    }</code></pre>
<ul>
<li>대기열에 사람들을 추가하는 <code>AddQueueWorker</code> 생성</li>
<li>1초마다 도는 기프티콘 발급, 남은 대기열 동기화 스케줄러의 작업을 위해 <code>Thread.sleep(5000)</code> 설정</li>
<li>결과 : 100명의 사용자 요청, 30개의 기프티콘을 제외하면 70명이 기프티콘을 받지 못한 것을 확인할 수 있다.</li>
</ul>
<h4 id="✅-테스트-통과">✅ 테스트 통과</h4>
<h4 id="100명이-대기열-참여-후-기프티콘-발급">100명이 대기열 참여 후 기프티콘 발급</h4>
<blockquote>
<p>가장 빨리 참여한 <strong>&quot;Thread-2&quot;</strong>, <strong>&quot;Thread-3&quot;</strong>가 치킨 기프티콘을 먼저 받는 것을 확인할 수 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/37af0a7d-d22d-439f-af4e-d89635792930/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/12773836-ec25-4290-ae41-dc0730dbbe9d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/178ddadc-5317-4558-bb21-4d00d01d4be6/image.png" alt=""></p>
<h4 id="대기열-순번-노출">대기열 순번 노출</h4>
<blockquote>
<p>치킨을 받지 못한 사람들의 화면에 표출될 순번을 표출하고 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/b84188b4-598b-4e32-9edb-a2fd5df0e12e/image.png" alt=""></p>
<h3 id="선착순-이벤트-종료">선착순 이벤트 종료</h3>
<blockquote>
<p>100명의 사람중 30명이 치킨 기프티콘을 받아서 이벤트가 종료된 것을 확인할 수 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/7ae8ad26-1b75-4cbc-9c13-bd21098f2f87/image.png" alt=""></p>
<h3 id="주의사항">주의사항</h3>
<blockquote>
<p>Sorted Set 명령어는 이번 글에선 설명하지 않고 <strong>기프티콘 선착순 이벤트 구현</strong>에 초점을 맞춰 포스팅했습니다. 제 글만 접하고 <code>명령어(add,range,rank 등)</code>에 대한 이해가 쉽지 않을 수 있습니다. Sorted Set 명령어는 <a href="http://redisgate.kr/redis/command/zsets.php">해당 사이트</a>에서 추가적으로 확인해보면 좋을 것 같습니다.</p>
</blockquote>
<h3 id="느낀-점">느낀 점</h3>
<h3 id="💡">💡</h3>
<p>대용량 트래픽에 관해 영상을 보던 중 우연히 해당 <a href="https://www.youtube.com/watch?v=MTSn93rNPPE&amp;t=710s">참고 영상</a>을 접해서 레디스 SortedSet이라는 자료구조를 알게 되었습니다. 그래서 언젠간 한 번 나도 구현해봐야겠다 생각하다가 이번에 마침 구현하게 되었습니다!</p>
<p>많은 요청이 바로 DB로 이어지지 않고 중간 정재 단계를 레디스로 거칠 수 있는 것을 해당 글을 쓰게 되면서 터득할 수 있었습니다. 추후에 팀 내에서 선착순 이벤트를 진행하거나 동일 시간에 민감한 데이터를 다루고, 대기열을 표출해야할 때 사용할 수 있는 하나의 수단이 될 것 같습니다!</p>
<h4 id="github-repository">GitHub Repository</h4>
<ul>
<li><a href="https://github.com/hgs-study/redis-sorted-set-practice">https://github.com/hgs-study/redis-sorted-set-practice</a></li>
</ul>
<h4 id="출처">출처</h4>
<ul>
<li><a href="http://redisgate.kr/redis/command/zsets.php">http://redisgate.kr/redis/command/zsets.php</a></li>
<li><a href="https://www.youtube.com/watch?v=MTSn93rNPPE&amp;t=710s">https://www.youtube.com/watch?v=MTSn93rNPPE&amp;t=710s</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redisson 분산락을 이용한 동시성 제어]]></title>
            <link>https://velog.io/@hgs-study/redisson-distributed-lock</link>
            <guid>https://velog.io/@hgs-study/redisson-distributed-lock</guid>
            <pubDate>Tue, 26 Apr 2022 12:42:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>동시성 문제를 해결하는 여러 방법이 있지만, 여기선 Redis 클라이언트인 <strong>Redisson 분산락을 이용해서 예제를 통한 동시성을 제어</strong>하는 포스팅을 진행하겠습니다.</p>
</blockquote>
<h2 id="1-분산-서버-동시성concurrency-제어">1. 분산 서버 동시성(Concurrency) 제어</h2>
<h3 id="❓-span-stylecolor-lightcoral-왜-필요한가span">❓ <span style="color :LightCoral"> 왜 필요한가</span></h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/7ff0ca6a-4919-4694-8739-cb834bd2fec7/image.png" alt=""></p>
<ul>
<li>여러 요청들이 한 자원에 대해서 공유할 때, 각 분산 DB의 동기화가 여러 요청의 동기화 속도를 못 따라 가는 상황이 발생합니다.</li>
<li>이에 대해 데이터 정합성은 깨지게 되고, 데이터 동시성 문제가 발생하게 됩니다.</li>
<li>예를 들어, 위와 같이 한 번에 여러 구매 요청이 들어왔을 경우 <strong>수량</strong>이라는 자원을 동시에 사용할 경우 여러 수량의 커밋되거나 롤백되는 수량의 동기화가 다른 서버가 따라가지 못해서 정합성이 깨지고, 동시성 문제가 발생할 수 있습니다.</li>
</ul>
<h3 id="💡-span-stylecolor-lightcoral-해결-방안span">💡 <span style="color :LightCoral"> 해결 방안</span></h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/d5cc9ef8-5da1-45d6-85e6-38bba619e169/image.png" alt=""></p>
<ul>
<li>단적인 예를 들어, 공유 자원인 <strong>수량</strong>을 레디스에 올려놓고 분산락(Distributed Lock)을 활용해서 데이터 동시성 문제를 해결할 수 있습니다.</li>
<li>여러 요청마다 락을 점유하고 데이터 업데이트 하기 때문에 각 서버는 각 DB의 동기화를 기다리지 않아도 되며, 동시성 문제도 해결할 수 있습니다.</li>
</ul>
<br>

<h2 id="2-동시성-제어-전-문제점">2. 동시성 제어 전 문제점</h2>
<h3 id="❗span-stylecolor-lightcoral-100개의-땅콩한정된-자원을-100명의-사람이-2개씩-구매했을-경우span">❗<span style="color :LightCoral"> 100개의 땅콩(한정된 자원)을 100명의 사람이 2개씩 구매했을 경우</span></h3>
<ul>
<li>분산락 걸지 않고 테스트 
<img src="https://velog.velcdn.com/images/hgs-study/post/e3482cad-158c-4e0f-aacf-3e95cd3d8441/image.png" alt=""></li>
<li>200개의 땅콩을 원하면서, 100개의 땅콩이 솔드아웃(재고 0개)가 되길 바랐지만, 94개의 땅콩이 남아있었다.
<img src="https://velog.velcdn.com/images/hgs-study/post/5c9446fb-ea35-4587-81e5-15a3196c481f/image.png" alt=""></li>
</ul>
<p>❌ <strong>테스트 실패</strong></p>
<ul>
<li>100개의 땅콩 재고에 200개의 구매 요청이 와서 솔드아웃(재고 0개) 되길 예상했지만, 여러 요청이 한 번에 몰리면서 땅콩 수량을 업데이트하면서 <strong>100개였던 땅콩은 6개밖에 줄지 않았다.</strong></li>
</ul>
<h2 id="3-redisson-사용-이유">3. Redisson 사용 이유?</h2>
<blockquote>
<p>Redis 클라이언트 중에 Redisson을 사용하면 좋은 이점을 공유합니다. </p>
</blockquote>
<h3 id="🔒-span-stylecolor-lightcorallettuce의-스핀락span">🔒 <span style="color :LightCoral">Lettuce의 스핀락</span></h3>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/e4767c57-2039-4a49-8fa9-1093b6f67b22/image.png" alt=""></p>
<ul>
<li>Lettuce에서도 락을 제공하고 있습니다. 하지만 Redisson의 락과는 성격이 다릅니다.</li>
<li>Lettuce의 락은 <code>setnx</code>메서드를 이용해 사용자가 직접 <code>스핀락</code>형태로 구성하게 됩니다. 락이 점유 시도를 실패했을 경우 계속 락 점유 시도를 하게 됩니다. 이로 인해 레디스는 계속 부하를 받게 되며, 응답시간이 지연됩니다.</li>
<li>추가적으로, 만료시간을 제공하고 있지 않아서 락을 점유한 서버가 장애가 생기면 다른 서버들도 해당 락을 점유할 수 없는 상황이 연출됩니다.</li>
</ul>
<h3 id="🔒-span-stylecolor-lightcoralredisson의-분산락span">🔒 <span style="color :LightCoral">Redisson의 분산락</span></h3>
<blockquote>
<p>Distributed locks are a very useful primitive in many environments where different processes must operate with shared resources in a mutually exclusive way.</p>
</blockquote>
<ul>
<li>레디스 공식 홈페이지를 보면 분산락은 <strong>서로 다른 프로세스가 상호 배타적인 방식으로 공유 리소스로 작동해야 하는 많은 환경에서 매우 유용한 기본 요소</strong>라고 설명하고 있습니다.</li>
</ul>
<h4 id="자체-ttl-적용">자체 TTL 적용</h4>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/70538ab0-65e7-49e0-b576-1e22e2d03006/image.png" alt=""></p>
<ul>
<li>RedissonLock.java의 <code>tryLockInnerAsync</code>메서드를 확인해보면 Lua Script를 사용해서 자체 TTL을 적용하는 것을 확인할 수 있습니다</li>
<li><code>hincrby</code> 명령어는 해당 field가 없으면 increment 값을 설정합니다.</li>
<li><code>pexpire</code> 명령어는 <strong>지정된 시간(milliseconds) 후 key 자동 삭제</strong>합니다. </li>
</ul>
<br>

<h2 id="4-redisson-분산락-적용">4. Redisson 분산락 적용</h2>
<h4 id="의존성-설정">의존성 설정</h4>
<pre><code class="language-java">dependencies {
    implementation &#39;org.redisson:redisson-spring-boot-starter:3.16.3&#39;
}</code></pre>
<ul>
<li>Redisson 의존성 추가 (버전 확인 : <a href="https://github.com/redisson/redisson">https://github.com/redisson/redisson</a>)<br>
#### application.yml 설정
```yml
spring:
redis:
  host: localhost
  port: 6379

</li>
</ul>
<p>redis:
  stock:
    prefix: stocks</p>
<pre><code>+ 레디스 기본 설정 host와 port 설정
+ 키설정과 락이름을 설정하기 위한 재고(stock)의 prefix 설정

&lt;br&gt;

#### StockService.java
```java
    public void decrease(final String key, final int count){
        final String lockName = key + &quot;:lock&quot;;
        final RLock lock = redissonClient.getLock(lockName);
        final String worker = Thread.currentThread().getName();

        try {
            if(!lock.tryLock(1, 3, TimeUnit.SECONDS))
                return;

            final int stock = currentStock(key);
            if(stock &lt;= EMPTY){
                log.info(&quot;[{}] 현재 남은 재고가 없습니다. ({}개)&quot;, worker, stock);
                return;
            }

            log.info(&quot;현재 진행중인 사람 : {} &amp; 현재 남은 재고 : {}개&quot;, worker, stock);
            setStock(key, stock - count);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(lock != null &amp;&amp; lock.isLocked()) {
                lock.unlock();
            }
        }
    }</code></pre><ul>
<li>주요 기능인 RLock의 <code>tryLock</code> 메서드를 살펴보면,
<img src="https://velog.velcdn.com/images/hgs-study/post/25677a39-4be8-4db1-afba-2130da0ef20e/image.png" alt=""></li>
<li>파라미터로 들어오는 <code>leaseTime</code> 시간 동안 락을 점유하는 시도합니다. </li>
<li>락을 사용할 수 있을 때까지 <code>waitTime</code> 시간까지 기다립니다.</li>
<li><code>leaseTime</code>시간이 지나면 자동으로 락이 해제됩니다.</li>
<li>정리하자면 선행 락 점유 스레드가 존재하면 <code>waitTime</code>동안 락 점유를 기다리며 <code>leaseTime</code> 시간 이후로는 자동으로 락이 해제되기 때문에 다른 스레드도 일정 시간이 지난 후 락을 점유할 수 있습니다.</li>
</ul>
<br>


<h4 id="테스트-목록">테스트 목록</h4>
<img src="https://velog.velcdn.com/images/hgs-study/post/ab5ff9f8-7a75-48b6-a9ed-9f3e0e4eacd5/image.png">

<h4 id="상품-재고-정보와-상품키-세팅">상품 재고 정보와 상품키 세팅</h4>
<pre><code class="language-java">    @BeforeEach
    void 재고_키_세팅(){
        final String name = &quot;peanut&quot;;
        final String keyId = &quot;001&quot;;
        final int amount = 100;
        final Stock peenut = new Stock(name, keyId, amount);

        this.stockKey = stockService.keyResolver(peenut.getName(), peenut.getKeyId());
        this.peenut = peenut;
        stockService.setStock(this.stockKey, amount);
    }</code></pre>
<ul>
<li>테스트 하기 전에 미리 땅콩 재고를 미리 100개로 설정합니다.</li>
</ul>
<h4 id="상품-수량-확인-카운트만큼-재고-감소-확인">상품 수량 확인&amp; 카운트만큼 재고 감소 확인</h4>
<pre><code class="language-java">    @Test
    @Order(1)
    void 상품_수량_확인(){
        final int amount = this.peenut.getAmount();

        final int currentCount = stockService.currentStock(stockKey);

        assertEquals(amount, currentCount);
    }

    @Test
    @Order(2)
    void 상품_재고_카운트만큼_감소(){
        final int amount = this.peenut.getAmount();
        final int count = 2;

        stockService.decrease(this.stockKey, count);

        final int currentCount = stockService.currentStock(stockKey);
        assertEquals(amount - count, currentCount);
    }</code></pre>
<ul>
<li><code>상품_수량_확인</code> 테스트에서 땅콩이 100개가 들어가있음을 확인</li>
<li><code>상품_재고_카운트만큼_감소</code> 테스트에서 땅콩 100개에서 2개를 뺀 수량을 확인<br>

</li>
</ul>
<h4 id="분산락-o-땅콩-100개를-100명의-사람이-2개씩-구매">(분산락 O) 땅콩 100개를 100명의 사람이 2개씩 구매</h4>
<pre><code class="language-java">    @Test
    @Order(4)
    void 락O_땅콩_100개를_사람_100명이_2개씩_구매() throws InterruptedException {
        final int people = 100;
        final int count = 2;
        final int soldOut = 0;
        final CountDownLatch countDownLatch = new CountDownLatch(people);

        List&lt;Thread&gt; workers = Stream
                                .generate(() -&gt; new Thread(new BuyWorker(this.stockKey, count, countDownLatch)))
                                .limit(people)
                                .collect(Collectors.toList());
        workers.forEach(Thread::start);
        countDownLatch.await();

        final int currentCount = stockService.currentStock(this.stockKey);
        assertEquals(soldOut, currentCount);
    }


    private class BuyWorker implements Runnable{
        private String stockKey;
        private int count;
        private CountDownLatch countDownLatch;

        public BuyWorker(String stockKey, int count, CountDownLatch countDownLatch) {
            this.stockKey = stockKey;
            this.count = count;
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            stockService.decrease(this.stockKey, count);
            countDownLatch.countDown();
        }
    }</code></pre>
<ul>
<li>땅콩의 재고는 미리 100개로 세팅했고, 100명의 사람(쓰레드)을 생성하고 땅콩을 2개씩(count) 구매하도록 세팅했습니다.</li>
<li>예상하는 결과는 땅콩 200개에 대한 구매 요청으로 인해, <strong>100개였던 땅콩 재고는 0개가 되고 나머지 100명에겐 남아있는 재고가 없다고 메시지</strong>를 표출하는 것입니다.</li>
</ul>
<h4 id="✅-테스트-통과">✅ 테스트 통과</h4>
<p><img src="https://velog.velcdn.com/images/hgs-study/post/e315e720-d02d-45e5-83fc-1e8af4c5d818/image.png" alt=""></p>
<ul>
<li>땅콩 100개는 모두 소진돼서 남은 재고가 0개가 됐으며, 나머지 100명에겐 남은 재고가 없다는 메시지를 표출하였습니다.</li>
<li>분산락으로 인해 <strong>컨커런시 세잎</strong>한 모습을 보이고 있으며, 땅콩 재고가 0개 이하로 내려가지 않는 모습을 볼 수 있습니다.</li>
</ul>
<br>

<h2 id="느낀-점">느낀 점</h2>
<h3 id="💡">💡</h3>
<p>요새 동시성에 대해 관심이 가던 도중, 분산 아키텍처 환경에서는 레디스로 동시성을 제어할 수 있다는 말을 듣고 &#39;나도 한 번 구현해봐야겠다&#39; 생각하고 레퍼런스를 찾아보고 바로 예제를 만들게 되었습니다.
하는 내내 재밌게 공부하고 다양한 레벨에서 동시성을 제어할 수 있구나 느꼈습니다.</p>
<p>회사에 당장 적용할 수는 없겠지만, 추후에 선착순 이벤트 등 공유 자원을 사용하는 곳에 의견을 내서 팀원들과 같이 동시성을 제어해볼 수 있을 것 같습니다. </p>
<h4 id="github-repository">GitHub Repository</h4>
<ul>
<li><a href="https://github.com/hgs-study/distributed-lock-practice">https://github.com/hgs-study/distributed-lock-practice</a></li>
</ul>
<h4 id="출처">출처</h4>
<ul>
<li><a href="https://redis.io/docs/reference/patterns/distributed-locks/">https://redis.io/docs/reference/patterns/distributed-locks/</a></li>
<li><a href="http://redisgate.kr/redis/command/hincrby.php">http://redisgate.kr/redis/command/hincrby.php</a></li>
<li><a href="http://redisgate.kr/redis/command/pexpire.php">http://redisgate.kr/redis/command/pexpire.php</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CircuitBreaker를 이용한 외부 API 장애 관리]]></title>
            <link>https://velog.io/@hgs-study/CircuitBreaker</link>
            <guid>https://velog.io/@hgs-study/CircuitBreaker</guid>
            <pubDate>Sun, 03 Apr 2022 13:17:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>CircuitBreaker는 서비스메시의 쿠버네티스 Istio를 이용해서 인프라 레벨에서 적용가능하나, 이번 포스팅에선 Resilience4j를 이용한 어플리케이션 레벨에서 적용하겠습니다.</p>
</blockquote>
<h2 id="1-circuitbreaker가-필요한-이유">1. CircuitBreaker가 필요한 이유?</h2>
<h3 id="🔍-span-stylecolor-lightcoral개요span">🔍 <span style="color :LightCoral">개요</span></h3>
<ul>
<li>어플리케이션의 각각의 도메인이나 기능들을 모듈로 나누어 분산 서버로 아키텍처링하는 서버 구성이 점차 늘어나고 있습니다.</li>
<li>이처럼 구성하면, 새로운 서비스 추가와 변경에도 용이하고 트래픽이 늘어나도 유연하게 대응할 수 있게됩니다.</li>
<li>MSA(Micro Service Architecture)에선 서로의 모듈이 의존함에 따라 <strong>한 모듈이 장애가 나면 다른 모듈에도 장애로 이어지는 것을 막기 위해 MSA 회복성 패턴 중 하나인 CircuitBreaker</strong>를 사용합니다.</li>
</ul>
<h3 id="🍀-span-stylecolor-lightcoral기존-서버-구성span">🍀 <span style="color :LightCoral">기존 서버 구성</span></h3>
<ul>
<li>아래와 같이 결제 서비스를 위한 아키텍처라고 가정하고, 정산 서버가 장애가 났을 경우 Pir 서버는 타 서버와의 통신이 영향을 받을까요?</li>
</ul>
<p><img src="https://media.vlpt.us/images/hgs-study/post/3dd01f75-673d-4f6e-87d7-60912b14f982/circuitbreaker-before-Page-1.drawio.png" alt=""></p>
<ul>
<li>정답은 <strong>Yes</strong>입니다.</li>
<li>Pir 서버는 주문, 정산, 결제, 상품 서버와 모두 통신을 해야하는데, 아래 그림과 같이 정산 서버에서 장애가 남으로써 Connection Time, Read Time 등 Latency가 증가하기 때문에 Pir 서버가 가지고 있는 쓰레드는 정산 서버와 통신하는데에 많이 사용되는 것을 볼 수 있습니다.</li>
<li>최종적으로, <strong>모든 쓰레드는 정산 서버와의 통신에 몰리게 될 것이고, 정산 서버의 장애는 모든 서버와의 장애</strong>로 이어지게 됩니다.</li>
</ul>
<p><img src="https://media.vlpt.us/images/hgs-study/post/28e14305-d273-425a-bef4-a79a189e3e97/circuitbreaker-before-02-Page-2.drawio.png" alt=""></p>
<h2 id="2-circuitbreaker란">2. CircuitBreaker란?</h2>
<h3 id="✏️span-stylecolor-lightcoral-circuitbreaker란span">✏️<span style="color :LightCoral"> CircuitBreaker란?</span></h3>
<ul>
<li>서킷브레이커는 해석 그대로 <strong>누전 차단기</strong>라는 뜻을 지닙니다. 누전 차단기는 <strong>전기 회로에서 과부하가 걸리거나 단락으로 인한 피해를 막기 위해 자동으로 회로를 정지시키는 장치</strong>라고 위키백과에서 표현하고 있습니다.</li>
<li>서버에서 사용하는 서킷브레이커도 <strong>외부 API 통신의 장애 전파를 막기 위해 장애를 탐지하면 외부와의 통신을 차단하는 역할</strong>을 합니다.</li>
<li>서킷브레이커가 실행(오픈)되면 <strong>Fail Fast 함으로써 외부 서비스가 장애가 나더라도 빠르게 에러를 응답</strong> 받을 수 있는 장점이 있으며 개발자가 지정한 행위를 리턴 받을 수 있습니다. </li>
</ul>
<h3 id="⭐span-stylecolor-lightcoral-circuitbreaker-구조도span">⭐<span style="color :LightCoral"> CircuitBreaker 구조도</span></h3>
<ul>
<li>Pir 서버에 서킷브레이커를 구성하여 주문&amp;정산&amp;결제&amp;상품 서버와의 장애 여부를 확인하여 해당 서버와의 통신을 차단할 수 있습니다.</li>
<li>아래와 같이 정산 서버가 장애일 경우, 그림에서는 &quot;X&quot; 표시를 해두었지만 실제로는 서킷 브레이커가 &quot;오픈&quot;한다 라는 표현을 사용합니다. 정산서버쪽의 서킷브레이커가 오픈함으로써 주문,결제,상품 서버와의 통신이 원활하게 진행될 수 있습니다.</li>
</ul>
<p><img src="https://media.vlpt.us/images/hgs-study/post/628f5711-a9cc-4061-bfa2-662058243a8f/circuitbreaker-before-%EC%84%9C%ED%82%B7%20%EA%B5%AC%EC%84%B1.drawio.png" alt=""></p>
<h3 id="⭐span-stylecolor-lightcoral-circuitbreaker-구성span">⭐<span style="color :LightCoral"> CircuitBreaker 구성</span></h3>
<ol>
<li>외부 API 통신 시도</li>
<li>외부 통신이 실패함으로써 서킷브레이커 Open</li>
<li>Open과 동시에 외부 서버에 요청을 날리지 않고, Fail Fast로 빠른응답 리턴</li>
<li>서킷브레이커가 오픈하면 일정 시간 후에 반오픈(Half-Open) 상태</li>
<li>반오픈 상태에서 다시 외부 서비스를 호출해서 장애를 확인하면 Open, 정상 응답이면 닫힘</li>
</ol>
<ul>
<li>위에서 <strong>&quot;장애 확인&quot;</strong>이라고 표현한 것은, 총(n)번 통신 중 실패율(n%)를 지정할 수 있습니다.</li>
<li>ex) 10번 중 50% =&gt; 10번 중 6번이 에러 발생하면 서킷브레이커 Open</li>
</ul>
<p><img src="https://media.vlpt.us/images/hgs-study/post/f14ab402-3357-42f2-a439-16128f7b831a/circuitbreaker-before-%EC%84%9C%ED%82%B7%EB%B8%8C%EB%A0%88%EC%9D%B4%EC%BB%A4%20%EA%B5%AC%EC%84%B1.drawio%20(1).png" alt=""></p>
<h2 id="3-resilience4j를-이용한-circuitbreaker-적용">3. Resilience4j를 이용한 CircuitBreaker 적용</h2>
<h3 id="❓span-stylecolor-lightcoral-resilience4j-선택-이유span">❓<span style="color :LightCoral"> Resilience4j 선택 이유?</span></h3>
<ul>
<li>CircuitBreaker를 제공하는 라이브러리 중에 Netflix Hystrix와 Resilience4j를 찾아볼 수 있었습니다.</li>
<li>Netflix Hystrix는 공식적으로 2018년 앞으로 개발을 중단하고 유지보수 상태라는 글이 명시되어 있으며</li>
<li>Hystrix는 Java 6을 기반으로 만들어졌지만 Resilience4j는 Java 8을 기반이며, Hystrix와는 다르게 다른 라이브러의 의존성이 없어서 가볍습니다.<blockquote>
<p>Hystrix is no longer in active development, and is currently in maintenance mode.</p>
</blockquote>
</li>
</ul>
<h3 id="⭐span-stylecolor-lightcoral-circuitbreaker-적용span">⭐<span style="color :LightCoral"> CircuitBreaker 적용</span></h3>
<h4 id="💡-의존성-설정">💡 의존성 설정</h4>
<pre><code class="language-java">ext{
    resilience4jVersion = &#39;1.7.1&#39;
}

dependencies {
    compile(&#39;org.springframework.boot:spring-boot-starter-webflux&#39;)
    compile(&#39;org.springframework.boot:spring-boot-starter-actuator&#39;)
    compile(&#39;org.springframework.boot:spring-boot-starter-aop&#39;)

    compile(&quot;io.github.resilience4j:resilience4j-spring-boot2:${resilience4jVersion}&quot;)
    compile(&quot;io.github.resilience4j:resilience4j-all:${resilience4jVersion}&quot;) 
    compile(&quot;io.github.resilience4j:resilience4j-reactor:${resilience4jVersion}&quot;)
}</code></pre>
<ul>
<li>AOP 기반으로 사용되는 resilience4j의 어노테이션 때문에 AOP를 추가</li>
<li>리액터 환경과 웹플러스환경에서 사용할 경우, 위와 같이 추가적으로 의존성 추가</li>
<li><a href="https://github.com/resilience4j/resilience4j">https://github.com/resilience4j/resilience4j</a></li>
<li><a href="https://github.com/resilience4j/resilience4j-spring-boot2-demo">https://github.com/resilience4j/resilience4j-spring-boot2-demo</a></li>
</ul>
<br>

<h4 id="💡-applicationyml-설정">💡 application.yml 설정</h4>
<pre><code class="language-java">resilience4j:
  circuitbreaker:
    configs:
      default:
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState : 10s
    instances:
      hgsssss:
        baseConfig: default
</code></pre>
<ul>
<li>resilience4j에 관한 기능들을 application.yml에 선언형으로 설정할 수 있습니다.</li>
<li>&#39;hgsssss&#39;라는 인스턴스 이름 지정</li>
<li><code>slidingWindowSize</code> : 서킷브레이커가 닫힌 상태에서 기록할 sliding window 크기 설정</li>
<li><code>failureRateThreshold</code> : 실패 비율 임계치를 백분율로 설정</li>
<li><code>waitDurationInOpenState</code> : 서킷브레이커가 Open후 waitDurationInOpenState 시간만큼 지난 후 Half-Open 상태도 전환</li>
<li>정리 : <strong>10번 요청에서 실패율이 50%가 넘으면 서킷브레이커가 Open하고 Open 10초 후에 다시 Half-Open 상태로 전환</strong></li>
</ul>
<br>

<h4 id="💡-service-설정">💡 Service 설정</h4>
<pre><code class="language-java">@Slf4j
@Service
@RequiredArgsConstructor
public class CircuitService {
    private final Call call;
    private static final String DEFAULT_NAME = &quot;hgsssss&quot;;
    private static final String FALLBACK_DEFAULT = &quot;helloFallback&quot;;

    @CircuitBreaker(name = DEFAULT_NAME, fallbackMethod = FALLBACK_DEFAULT)
    public Mono&lt;String&gt; getHello(String name){
        return call.getApiHello(name);
    }

    private Mono&lt;String&gt; helloFallback(String name, Throwable t){
        log.error(&quot;Fallback : &quot;+ t.getMessage());
        return Mono.just(&quot;fallback data&quot;);
    }
}</code></pre>
<ul>
<li>외부 API를 호출하는 메서드 위에 <code>@CircuitBreaker</code> 어노테이션을 작성하여 application.yml에서 선언한 &quot;hgsssss&quot; 인스턴스 명 삽입합니다.</li>
<li>서킷브레이커가 Open하면 실행하는 메서드를 fallbackMethod로 선언합니다.</li>
<li>fallbackMethod는 해당하는 메서드 파리미터도 fallbackMethod 파라미터로 같이 지정해줘야합니다.</li>
<li>ex) getHello(String name), helloFallback(String name)</li>
</ul>
<h4 id="💡-결과">💡 결과</h4>
<p><img src="https://media.vlpt.us/images/hgs-study/post/583f782a-bbe5-48cf-bd21-0710c6767b9c/image.png" alt=""></p>
<ul>
<li>외부 서비스를 호출하다가 <strong>10번 중 50%가 넘는 실패 비율 임계치를 초과했기 때문에 6번째 요청엔 서킷브레이커가 Open하고 제가 지정한 fallbackMethod를 호출</strong>하는 것을 확인할 수 있습니다.</li>
<li>위 캡처에 나오진 않았지만 서킷브레이커는 <strong>10초 후에 다시 Half-Open상태로 localhost:8081/hello/fail 요청</strong>을 수행하게 됩니다.</li>
</ul>
<br>

<h4 id="⭐-spring-actuator를-활용한-서킷브레이커-모니터링">⭐ spring-actuator를 활용한 서킷브레이커 모니터링</h4>
<ul>
<li><p>서킷브레이커가 닫혀있는 평소 상태
<img src="https://velog.velcdn.com/cloudflare/hgs-study/7b4a6fc8-45da-4088-a9e2-351fefcb25ab/1111111.png" alt=""></p>
</li>
<li><p>서킷브레이커가 오픈된 상태</p>
<blockquote>
<p>failedCalls : 외부 API 호출을 실패하여 fallbackMethod가 실행된 횟수
notPermittedCalls : 메서드 호출은 됐지만 서킷브레이커가 Open된 상태기 때문에 외부 API 호출을 하지 않은 횟수</p>
</blockquote>
</li>
</ul>
<p><img src="https://velog.velcdn.com/cloudflare/hgs-study/3735bba1-fb86-4ba0-9667-8eb6ceced82f/222222222.png" alt=""></p>
<hr>
<h2 id="느낀-점">느낀 점</h2>
<h3 id="💡">💡</h3>
<p>요즘은 개인 공부보다 회사 서비스를 더 효율적으로 개선하고 관리하는 데에 관심을 두고 있어서 재미있는 나날들을 보내고 있습니다.</p>
<p>각 모듈마다 장애 전파를 막을 수 있기 때문에 분산 서버를 운영한다면 꼭 필요한 기술이라고 생각합니다. 서킷브레이커를 공부하면서 저희팀에도 꼭 필요할 것 같아서, 공부 후에 팀원들에게 공유하고 리뷰해서 추후에 도입하기로 결정하였습니다. </p>
<p>서킷브레이커를 도입하려면 서비스의 외부 API를 호출하는 모든 기능을 정의 후에 어떤 메서드에서 사용하고, 어떠한 fallbackMethod를 구성할지 벌써부터 매우 흥미롭습니다. 아직 프로젝트에 적용해보지 않았기 때문에 실제 프로젝트에 적용 후 시행착오와 매니징 프로세스 구성 후 서킷브레이커를 어떻게 모니터링하고, 어떻게 관리할 지 추가적으로 포스팅할 예정입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네트워크 원리 - 웹 브라우저가 메시지를 만든다]]></title>
            <link>https://velog.io/@hgs-study/network-01</link>
            <guid>https://velog.io/@hgs-study/network-01</guid>
            <pubDate>Sun, 13 Feb 2022 16:20:14 GMT</pubDate>
            <description><![CDATA[<h2 id="1-네트워크란">1. 네트워크란?</h2>
<h3 id="❓-span-stylecolor-lightcoral네트워크란span">❓ <span style="color :LightCoral">네트워크란?</span></h3>
<ul>
<li>네트워크를 한 마디로 요약하자면 브라우저의 요청으로 웹서버에 액세스하여 해당 응답을 브라우저에 표현하는 <strong>브라우저와 웹서버가 대화하는 동작</strong>이라고 정의할 수 있습니다.
<img src="https://images.velog.io/images/hgs-study/post/66b62eb6-b9cd-440b-9723-edad7e2f6074/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC%20%EC%98%88%EC%A0%9C.png" alt=""></li>
</ul>
<h3 id="⭐-span-stylecolor-lightcoral-네트워크-전체-모습span">⭐ <span style="color :LightCoral"> 네트워크 전체 모습</span></h3>
<ul>
<li>네트워크는 사용자측(브라우저)부터의 요구(리퀘스트)를 서버에 보냅니다.</li>
<li>웹서버는 요구에 따라 움직이고 결과(응답)을 브라우저에 돌려보냅니다.</li>
<li>웹 브라우저, 프로토콜 스택(TCP/IP), LAN 드라이버부터 시작해 허브를 포함한 서브넷부터 라우터, 액세스 회선, 라우터, 통신사를 통해 웹서버에 도착하게 됩니다.</li>
<li>이번 포스팅은 <strong>웹브라우저에서 메시지를 만드는 것을 위주로 포스팅하겠습니다.</strong> 
<img src="https://images.velog.io/images/hgs-study/post/2dafed13-4b98-4b00-bd3c-8094f2a97481/network_01-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC%20%EC%A0%84%EC%B2%B4%20%EB%AA%A8%EC%8A%B5.drawio.png" alt=""></li>
</ul>
<h2 id="2-웹-브라우저가-메시지를-만든다">2. 웹 브라우저가 메시지를 만든다</h2>
<h3 id="✏️span-stylecolor-lightcoral-2-1-http-리퀘스트-메세지를-작성한다span">✏️<span style="color :LightCoral"> 2-1. HTTP 리퀘스트 메세지를 작성한다.</span></h3>
<ul>
<li>🍀 <strong>1. HTTP 탐험 여행은 URL 입력부터 시작한다.</strong><ul>
<li>URL은 http:// 뿐만 아니라 ** ftp:, file:, mailto:, news:**  등의 url이 있다.</li>
<li>URL은 http와 ftp를 보면 프로토콜을 나타내는구나 생각할 수 있는데 <code>file 같이 네트워크를 사용하지 않는 것도 있으므로 프로토콜이라고 단언할 수는 없다.</code> </li>
<li><strong>&#39;액세스 방법&#39;</strong>이라는 식으로 생각하는 것이좋다.</li>
</ul>
</li>
</ul>
<br>


<ul>
<li>🍀 <strong>2. 브라우저는 먼저 URL을 해독한다.</strong><ul>
<li>URL 해독<ul>
<li>http: + // + (웹서버명) + / + (디렉토리명) + / + ... + (파일명)</li>
</ul>
</li>
</ul>
</li>
</ul>
 <br>

<ul>
<li>🍀 <strong>3. 파일 명을 생략한 경우</strong><ul>
<li>파일명 생략<ul>
<li>EX) <a href="http://www.lab.cyber.co.kr/dir/">http://www.lab.cyber.co.kr/dir/</a></li>
<li>하지만 <strong>파일명을 쓰지 않으면 어느 파일에 액세스해야 할지 모른다.</strong></li>
<li>이럴 경우 보통, 대부분의 서버가 /dir/index.html, /dir/default.html로 설정해둔다</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<ul>
<li>🍀 <strong>4. HTTP의 기본 개념</strong><ul>
<li>HTTP 프로토콜 <ul>
<li>클라이언트와 서버가 주고 받는 메시지의 내용이나 순서를 정한 것</li>
<li>보통 클라이언트가 서버에게 &quot;무엇을&quot; &quot;어떻게 해서&quot; 하겠다는 내용을 요청한다.</li>
<li><strong>URI : &quot;무엇을&quot; / 메서드 : &quot;어떻게 해서&quot;</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<h3 id="✏️span-stylecolor-lightcoral-2-2-웹서버의-ip주소를-dns서버에-조회한다span">✏️<span style="color :LightCoral"> 2-2. 웹서버의 IP주소를 DNS서버에 조회한다.</span></h3>
<pre><code>- HTTP의 메세지를 만들면 다음에는 이것을 OS에 의뢰하여 액세스 대상의 웹서버에게 송신한다.
- HTTP의 메세지를 만들기 위해 HTTP의 많은 헤더 필드 종류가 있으니 확인해 보시길 바랍니다.</code></pre><ul>
<li><h3 id="📌-서브넷">📌 <strong>서브넷</strong></h3>
<ul>
<li>TCP/IP는 <code>서브넷이라는 작은 네트워크를 라우터로 접속해서 전체 네트워크</code>가 만들어진다고 생각할 수 있다.</li>
<li><code>허브에 몇대의 PC가 접속된 것</code>이라고 생각하고 이것을 한 개의 단위로 생각하여 <strong>서브넷</strong>이라고 부르고, 라우터에서 연결하면 네트워크 전체가 완성된다.
<img src="https://images.velog.io/images/hgs-study/post/df39f4e7-c70d-4d4f-a6ae-c7fade277fda/network_01-%EC%84%9C%EB%B8%8C%EB%84%B7.drawio.png" alt=""></li>
</ul>
</li>
</ul>
<br>

<ul>
<li><h3 id="📌-ip">📌 IP</h3>
<ul>
<li><p>🍀 <strong>IP란?</strong></p>
<ul>
<li>집주소처럼 &#39;ㅇㅇ동 ㅇㅇ번지&#39;라는 형태로 네트워크의 주소를 할당한다.</li>
<li>네트워크 번호 : 동에 해당하는 번호를 서브넷에 할당</li>
<li>호스트 번호 : 번지에 해당하는 번호를 컴퓨터에 할당한 것이 네트워크 주소</li>
<li>IP 주소 : 네트워크 번호 + 호스트 번호<br>
</li>
</ul>
</li>
<li><p>🍀 <strong>IP 주소 본체의 표기 방법</strong>
  <code>10.11.12.13</code></p>
<ul>
<li>IP주소는 32비트의 디지털 데이터로, 8비트(1바이트)씩 점으로 구분하여 10진수로 표기한다.<br></li>
</ul>
</li>
<li><p>🍀 <strong>IP 주소 본체와 같은 방법으로 네트워크를 표기하는 방법</strong>
  <code>10.11.12.13/255.255.255.0</code></p>
<ul>
<li>IP만으로 어느 부분이 네트워크 번호인지, 호스트 부분인지 알 수 없다.</li>
<li>그렇기 때문에 IP주소 뒤에 <code>255.255.255.0</code>을 붙이게 되는데 이것을 <strong>넷마스크</strong>라고 부른다.</li>
<li>넷마스크는 앞 3자리 <code>255.255.255</code>는 <strong>네트워크 번호</strong>를 나타내고, 뒤 <code>.0</code>은 <strong>호스트 번호</strong>를 나타낸다.<br>
</li>
</ul>
</li>
<li><p>🍀 <strong>네트워크 번호의 비트 수로 넷마스크 표기하는 방법</strong>
  <code>10.11.12.13/24</code></p>
<ul>
<li><code>10.11.12.13/255.255.255.0</code>처럼 표기하면 너무 길어지기 때문에 이를 10진수로 나타낸 표기법<br>
</li>
</ul>
</li>
<li><p>🍀 <strong>서브넷을 나타내는 주소</strong>
  <code>10.11.12.0/24</code></p>
<ul>
<li>호스트 번호의 부분의 비트가 모두 0인 것은 각 컴퓨터가 아니라 <strong>서브넷</strong> 자체를 나타낸다.</li>
</ul>
<br></li>
<li><p>🍀 <strong>서브넷의 브로드캐스트를 나타내는 주소</strong>
  <code>10.11.12.255/24</code></p>
<ul>
<li>호스트 번호의 부분의 비트가 모두 1인 것은 서브넷 전체에 대한 <strong>브로드캐스트</strong>를 나타낸다.</li>
<li><strong>브로드캐스트</strong> : 서브넷에 있는 기기 전체에 패킷을 보냄</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<ul>
<li><h3 id="📌-dns">📌 DNS</h3>
<ul>
<li><p>🍀 <strong>DNS &amp; DNS 리졸버란?</strong>
<img src="https://images.velog.io/images/hgs-study/post/4b135c35-c990-4897-86e7-5796a89f419c/network_01-DNS.drawio.png" alt=""></p>
<ul>
<li>사용자가 브라우저에 <code>www.google.com</code>을 치면 브라우저는 DNS서버에서 <a href="http://www.google.com%EC%9D%98">www.google.com의</a> IP주소를 찾아서 요청하고 응답 받을 수 있다.</li>
<li>사용자 브라우저측은 DNS 서버의 클라이언트로 동작한다고 말할 수 있고, 이는 <strong>DNS 리졸버</strong> 또는 <strong>리졸버</strong>라고 부른다.</li>
<li>DNS의 원리를 사용하여 IP주소를 조사하는 것을 <strong>네임 리졸루션</strong>(name resolution, 이름확인)이라고 부른다.</li>
<li>이 리졸루션을 사용하는 것이 <strong>리졸버</strong>이다.</li>
</ul>
<br></li>
<li><p>🍀 <strong>DNS 설정</strong></p>
<ul>
<li>DNS 서버에 메세지를 송신할 때도 <code>DNS 서버의 IP주소가 필요</code>하다.</li>
<li>단, 이것은 TCP/IP 설정 항목의 하나로, 초기에 <code>컴퓨터에 미리 설정</code>되어 있으므로 다시 조사할 필요는 없다.</li>
<li>사용자가 다시 DNS서버 IP를 변경할 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<ul>
<li><h3 id="📌-socket-라이브러리">📌 Socket 라이브러리</h3>
<ul>
<li><p>🍀 <strong>Socket 라이브러리란?</strong></p>
<ul>
<li><p>OS에 포함되어 있는 <strong>네트워크 기능을 애플리케이션에서 호출</strong>하기 위한 라이브러리</p>
</li>
<li><p>도메인명에서 IP 주소를 조사할 때 브라우저는 <strong>Socket 라이브러리의 리졸버</strong>를 사용</p>
</li>
<li><p>🍀 <strong>Socket 리졸버 동작</strong>
<img src="https://images.velog.io/images/hgs-study/post/0c8c52f3-6ead-43c1-8423-bcb82884827f/network_01-socket.drawio.png" alt=""></p>
<ol>
<li>[애플리케이션(브라우저)] gethostbyname(URL) 메서드 호출<ul>
<li>애플리케이션에서 <strong>Socket 라이브러리의 IP가져오는 메서드 호출</strong></li>
</ul>
</li>
<li>[Socket] 조회 메세지(URL)을 OS 내부의 프로토콜 스택에 보낸다<ul>
<li>URL을 해당 메서드를 타서 OS의 프로토콜 스택으로 전송</li>
</ul>
</li>
<li>[프로토콜 스택] UDP 메시지 송신<ul>
<li>프로토콜 스택은 UDP 메세지를 송신하여 LAN 어댑터로 전송</li>
</ul>
</li>
<li>[프로토콜 스택] LAN 어댑터<ul>
<li>LAN 어댑터는 DNS 서버로 IP 요청</li>
</ul>
</li>
<li>[프로토콜 스택] DNS 서버<ul>
<li>요청 받은 URL의 IP를 응답</li>
</ul>
</li>
<li>[프로토콜 스택] LAN 어댑터<ul>
<li>IP를 다시 프로토콜 스택에 전송</li>
</ul>
</li>
<li>[프로토콜 스택] UDP메세지 수신<ul>
<li>IP를 Socket에 전송</li>
</ul>
</li>
<li>[Socket] <strong>응답 메세지를 메모리(변수)에 저장</strong><ul>
<li>IP를 메모리에 저장</li>
</ul>
</li>
<li>[애플리케이션(브라우저)] 도착<ul>
<li>제어가 다시 애플리케이션으로 돌아간다.</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="✏️span-stylecolor-lightcoral-2-3-전-세계의-dns-서버가-연대한다span">✏️<span style="color :LightCoral"> 2-3. 전 세계의 DNS 서버가 연대한다.</span></h3>
<ul>
<li><h3 id="📌-dns-서버의-기본-동작">📌 <strong>DNS 서버의 기본 동작</strong></h3>
<ul>
<li>클라이언트에서 조회 메세지를 받고 조회의 내용에 응답하는 형태로 정보를 회답한다.</li>
<li>조회 메세지에는 세 가지 정보가 포함되어있다.<ul>
<li><code>이름</code><ul>
<li>서버나 메일 배송 목적지(메일주소에서 @ 뒷부분의 이름)와 같은 이름</li>
</ul>
</li>
<li><code>클래스</code><ul>
<li>DNS의 구조를 고안했을 때 인터넷 이외에도 네트워크에서의 이용까지 검토하여 이것을 클래스라는 정보를 준비했지만 현재는 인터넷 외의 네트워크는 없기 때문에 클래스는 항상 인터넷을 나타내는 <strong>&#39;IN&#39;</strong>을 사용한다.</li>
</ul>
</li>
<li><code>타입</code><ul>
<li>이름에 어떤 타입(종류)가 지원되는지 나타낸다. 예를 들어 타입이 <code>A이면 이름에 IP주소</code>가 지원되는 것을 나타내며, <code>MX이면 이름에 메일 배송 목적지</code>가 지원된다는 것</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><h3 id="📌-도메인의-계층--여러-데이터-분산">📌 <strong>도메인의 계층 + 여러 데이터 분산</strong></h3>
<p><code>정보를 분산시켜서 다수의 DNS 서버에 등록하고, 다수의 DNS 서버가 연대하여 어디에 정봅가 등록되어 있는지 찾아내는 구조</code></p>
<ul>
<li>🍀 <strong>계층적</strong>으로 도메인을 DNS서버에 저장한다<ul>
<li><code>www.hgsssss.co.kr일 경우</code><ul>
<li>최상위 kr : 대한민국</li>
<li>co : 회사</li>
<li>cyber : 회사 도메인명</li>
<li>kr -&gt; co -&gt; cyber로 갈수록 하위도메인</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<ul>
<li><p>🍀 <strong>하위 도메인을 담당하는 DNS서버의 IP주소를 그 상위의 DNS서버에 등록한다.</strong>
<img src="https://images.velog.io/images/hgs-study/post/f673b690-bac9-455e-9e58-52e23327a344/network_01-DNS%20%EA%B3%84%EC%B8%B5%EA%B5%AC%EC%A1%B0.drawio.png" alt=""></p>
<ul>
<li><strong><a href="http://www.zum.hgsssss.com">www.zum.hgsssss.com</a></strong>을 찾아야하는 경우<ol>
<li>가장 가까운 DNS서버를 통해 <code>루트 도메인</code>으로 올라간다.</li>
<li>루트 도메인 서버에 <a href="http://www.zum.hgsssss.com%EC%9D%98">www.zum.hgsssss.com의</a> IP주소를 물어보면, com도메인(123.123.123.123)가서 물어보라고 한다.</li>
<li>com 서버(123.123.123.123)에 물어보면, hgsssss도메인(234.234.234.234)가서 물어보라고 한다.</li>
<li>hgsssss 서버(234.234.234.234)에 물어보면, zum도메인(456.456.456.456)가서 물어보라고 한다.</li>
<li>zum 서버(456.456.456.456)에 물어보면, <a href="http://www.zum.hgsssss.com%EC%9D%98">www.zum.hgsssss.com의</a> IP주소는 789.789.789.789라고 응답한다.</li>
<li>사용자(브라우저는) <a href="http://www.zum.hgsssss.com(789.789.789.789)%EC%97%90">www.zum.hgsssss.com(789.789.789.789)에</a> 응답 요청한다.<br>

</li>
</ol>
</li>
</ul>
<p>⭐ 루트 도메인부터 <strong>상위 도메인이 하위 도메인의 IP주소를 저장</strong>하기 때문에 <code>com -&gt; hgssss -&gt; zum -&gt; www</code> 하위 도메인서버로 계층적으로 DNS서버부터 원하는 서버 IP를 찾아갈 수 있다.</p>
<br>
</li>
<li><p>🍀 <strong>DNS 서버 캐시 사용</strong></p>
<ul>
<li>DNS 서버는 한 번 조사한 이름을 캐시에 기록할 수 있는데, 조회한 이름에 해당하는 정보가 캐시에 있으면 그 정보를 회답하기 때문</li>
<li><strong>캐시한 위치에서 계층 구조를 아래로 향하여 찾을 수 있다.</strong></li>
<li>❗️ 주의점 : 캐시에 정보를 저장한 후 등록 정보가 변경되는 경우도 있으므로 <code>캐시 안에 저장된 정보는 올바르다고 단언할 수 없다.</code></li>
</ul>
</li>
</ul>
<br>
### ✏️<span style="color :LightCoral"> 2-4. 프로토콜 스택에 메세지 송신을 의뢰한다.</span></li>
<li><h3 id="📌-데이터-송-수신-동작">📌 <strong>데이터 송 수신 동작</strong></h3>
<ul>
<li>OS 내부의 프로토콜 스택에 메세지 송신 동작을 의뢰할 때는 Socket 라이브러리 프로그램 부품을 결정된 순번대로 호출한다.</li>
<li>실제로는 먼저 서버측에서 소켓을 만들고, 소켓에 클라이언트가 파이프를 연결하기 기다립니다.</li>
</ul>
<p><img src="https://images.velog.io/images/hgs-study/post/ab90a9b5-ab8d-444e-a587-756c72dc3e68/network_01-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%20%EB%8D%B0%EC%9D%B4%ED%84%B0%20%ED%86%B5%EC%8B%A0.drawio.png" alt=""></p>
<ul>
<li><p>🍀 <strong>OS 내부의 프로토콜 스택이 하는 역할</strong></p>
<ol>
<li>소켓을 만듭니다 (소켓 작성 단계)</li>
<li>서버측의 소켓에 파이프를 연결합니다. (접속 단계)</li>
<li>데이터 송수신합니다. (송수신단계)</li>
<li>파이프를 분리하고 소켓을 말소합니다 (연결 끊기 단계)<br></li>
</ol>
</li>
<li><p>💡 <strong>1. 소켓 작성 단계, 디스크립터</strong></p>
<ul>
<li>소켓은 애플리케이션과 프로토콜 스택 사이에서 네트워크 송수신을 호출합니다.</li>
<li>소켓이 생기면 <strong>디스크립터</strong>라는 것이 돌아오므로 애플리케이션은 이것을 받아서 메모리에 기록합니다.</li>
<li>예를 들어 창을 2개 열어서 웹서버에 동시에 액세스하면 소켓이 2개가 생기고 이 2개의 소켓을 식별해야하는데 <code>이 소켓 하나하나에 할당한 번호</code>를 <strong>디스크립터</strong>라고 합니다.</li>
<li>소켓을 만든 후 디스크립터를 이용하여 접속 동작이나 데이터 송수신 동작을 실행하는데 <strong>이때 디스크립터를 보여주면 프로토콜 스택이 어느 소켓을 사용하여 접속할지 또는 데이터를 송수신할지 금방 알 수 있습니다.</strong><br></li>
</ul>
</li>
<li><p>💡 <strong>2. 파이프를 연결하는 접속 단계</strong></p>
<ul>
<li>소켓의 connect를 호출하면 프로토콜 스택이 접속하는 동작 실행</li>
<li>접속할떄 디스크립터(소켓), ip주소, 포트번호가 필요하다</li>
</ul>
<br></li>
<li><p>💡 <strong>3. 메세지를 주고 받는 송 수신 단계</strong></p>
<ul>
<li>소켓이 상대측과 연결되면 데이터를 쏟아부으면 상대측 소켓에 데이터가 도착한다.</li>
<li><code>socket의 write 호출</code><ul>
<li>write를 호출할 때는 디스크립터와 송신 데이터를 지정 -&gt; 프로토콜 스택이 송신 데이터를 서버에 송신</li>
</ul>
</li>
<li><code>socket의 read 호출</code><ul>
<li>메세지가 돌아오면 수신한 응답 메세지를 저장하기 위한 메모리 영역을 지정하는데 이 메모리 영역을 <strong>수신 버퍼</strong>라고 부른다.</li>
<li>그러면 read가 받아서 수신버퍼에 응답 메세지를 저장한다.</li>
</ul>
</li>
</ul>
<br></li>
<li><p>💡 <strong>4. 연결 끊기 단계에서 송수신이 종료된다.</strong></p>
<ul>
<li><code>socket의 close</code> 호출</li>
<li>브라우저가 데이터 송수신을 완료하면 socket의 close를 호출</li>
<li>소켓 사이를 <strong>연결한 파이프와 같은 것은 분리되고 소켓도 말소</strong>된다.</li>
<li>HTTP프로토콜에서 응답 메세지의 송신을 완료했을 때 <strong>웹서버측에서 연결 끊기 동작을 실행하므로 먼저</strong> 웹서버측에서 close를 호출하여 끊는다.<ul>
<li>HTTP 프로토콜은 HTML문서나 영상 데이터를 별도의 것으로 취급하여 하나하나 데이터를 읽을 때마다 <code>&quot;접속&quot;-&gt; &quot;리퀘스트 메세지 송신&quot; -&gt; &quot;응답 메세지 수신&quot; -&gt; &quot;연결끊기&quot;라는 동작을 반복</code>한다.</li>
</ul>
</li>
<li>하지만 이것은 비효율이기 때문에 <strong>HTTP 1.1에서는 리퀘스트해야할 데이터가 없어진 상태에서 연결 끊기</strong>를 사용할 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h2 id="정리">정리</h2>
<h3 id="💡-wwwzumhgssssscom으로-가는-법">💡 <strong><a href="http://www.zum.hgsssss.com%EC%9C%BC%EB%A1%9C">www.zum.hgsssss.com으로</a> 가는 법</strong></h3>
<ol>
<li>웹브라우저에서 <code>www.zum.hgsssss.com</code>을 친다.</li>
<li><code>socket 라이브러리</code>에서 DNS에 보내는 조회 메세지를 만든다.</li>
<li>OS 내부의 <code>프로토콜 스택</code>에 메세지를 보낸다.</li>
<li>프로토콜 스택은 <code>LAN 어댑터</code>로 메세지를 준다.</li>
<li>LAN 어댑터는 <code>가장 가까운 DNS서버</code>에 메세지를 준다.</li>
<li>가장 가까운 DNS 서버는 메세지를 <code>루트 도메인 서버</code>에 전송한다.</li>
<li>루트 도메인 서버는 <code>com DNS서버(하위)</code> IP를 알려준다.</li>
<li>com DNS서버는 <code>hgsssss DNS서버(하위)</code> IP를 알려준다.</li>
<li>hgsssss DNS서버는 <code>zum DNS서버(하위)</code> IP를 알려준다.</li>
<li>zum DNS서버는 <code>www.zum.hgsssss.com IP</code>를 알려준다.</li>
<li><a href="http://www.zum.hgsssss.com">www.zum.hgsssss.com</a> 의 IP주소를 가장 가까운 DNS서버에 전송하여 <code>클라이언트에게 회답</code>한다.</li>
<li><code>클라이언트는 www.zum.hgsssss.com에 요청</code>한다.</li>
</ol>
<hr>
<h2 id="느낀-점">느낀 점</h2>
<h3 id="💡">💡</h3>
<p>계속 미루고 미뤘던 CS공부, 네트워크를 처음 공부해봤는데 평소에 웹을 개발한다고 하는 웹개발자로서, API요청할 때 어떤 흐름인지도 모르고 개발했다는 것이 부끄러웠고 많이 부족하다고 느꼈습니다. 네트워크 공부를 이제 시작했지만 공부하는 내내 재밌었고 &#39;소켓과 OS 프로토콜 스택&#39;이 존재하기 때문에 애플리케이션에서 네트워크를 통해 요청/응답 받을 수 있구나를 알 수 있었습니다. 앞으로 더 공부하면서 어떤 디테일한 요소들이 숨어있을지 기대됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[jOOQ 동시성(Concurrency) 제어 - Optimistic Lock / Pessimistic Lock]]></title>
            <link>https://velog.io/@hgs-study/jOOQ-Concurrency-Controll</link>
            <guid>https://velog.io/@hgs-study/jOOQ-Concurrency-Controll</guid>
            <pubDate>Sun, 23 Jan 2022 11:22:52 GMT</pubDate>
            <description><![CDATA[<h2 id="1-동시성-제어concurrency-control">1. 동시성 제어(Concurrency Control)</h2>
<h3 id="❓-span-stylecolor-lightcoral-왜-필요한가span">❓ <span style="color :LightCoral"> 왜 필요한가</span></h3>
<ul>
<li>스프링 프레임워크는 기본적으로 멀티 쓰레드 환경이기 때문에 각기 다른 요청들에 의해 개발자가 원하지 않는 결과를 얻을 수 있다.</li>
</ul>
<h3 id="💡-span-stylecolor-lightcoral-해결-방안span">💡 <span style="color :LightCoral"> 해결 방안</span></h3>
<ul>
<li>어플리케이션 사이드에서 동시성(쓰레드 기반)을 제어할 수도 있지만 이번 게시글에서는 Java 네이티브 쿼리빌더인 <strong>jOOQ를 활용하여 디비 사이드에서 동시성(트랜잭션 기반)을 제어</strong>하려고 합니다.</li>
</ul>
<h2 id="2-동시성-제어-전-문제점">2. 동시성 제어 전 문제점</h2>
<h3 id="🔱span-stylecolor-lightcoral-50개-쓰레드로-1개의-글-1번씩-조회span">🔱<span style="color :LightCoral"> 50개 쓰레드로 1개의 글 1번씩 조회</span></h3>
<ul>
<li><p>락 걸지 않고 테스트 
<img src="https://images.velog.io/images/hgs-study/post/7f5fa5f4-a024-4dfe-a1e0-d039c7da805f/image.png" alt=""></p>
<pre><code class="language-java">  public class NoLockWorker implements Runnable{
      private CountDownLatch countDownLatch;

      public NoLockWorker(CountDownLatch countDownLatch){
          this.countDownLatch = countDownLatch;
      }

      @Override
      public void run() {
          postService.plusHitById(BASE_ID);
          countDownLatch.countDown();
      }
  }

  ------------------------------------------------------------------

  public void plusHitById(Long id){
      context.transaction(configuration -&gt;{
          final Post post = DSL.using(configuration)
                              .selectFrom(POST)
                              .where(POST.ID.eq(id))
                              .fetchOneInto(Post.class);

          final Long hit = post.getHit();

          DSL.using(configuration)
                  .update(POST)
                  .set(POST.HIT, hit + 1L)
                  .where(POST.ID.eq(id))
                  .execute();
      });
  }</code></pre>
</li>
<li><p>결과
<img src="https://images.velog.io/images/hgs-study/post/a2aa18fd-4e6d-4690-8a08-555b76e96591/image.png" alt="">
❌ <strong>테스트 실패</strong>
  =&gt; hit가 50개가 되는 것을 예상했지만, 50개의 멀티 쓰레드의 여러 트랜잭션이 동시에 한 ROW에 몰렸기 때문에 개발자가 원하지 않는 hit가 5라는 결과 도출
<img src="https://images.velog.io/images/hgs-study/post/8bcb0d2d-c306-4e5b-8278-d9d20ea85f42/image.png" alt=""></p>
</li>
</ul>
<h2 id="3-optimistmic-lock--pessimistic-lock">3. Optimistmic Lock / Pessimistic Lock</h2>
<h3 id="🔒-span-stylecolor-lightcoraloptimistic-lock이란span">🔒 <span style="color :LightCoral">Optimistic Lock이란?</span></h3>
<ul>
<li>트랜잭션이 충돌을 하지 않는다고 가정하고 가정합니다. (낙관적 접근)</li>
<li>version, timestamp 등 컬럼을 두어 해당 version에서 다른 트랜잭션이 업데이트했을 경우 예외 발생 (업데이트가 있을 경우 버전이 1씩 증가)</li>
<li>업데이트 시점만에 락을 걸기 때문에 실시간 데이터를 보여줘야하는 비즈니스에 유리</li>
</ul>
<h3 id="🔏-span-stylecolor-lightcoralpessimistic-lock이란span">🔏 <span style="color :LightCoral">Pessimistic Lock이란?</span></h3>
<ul>
<li>트랜잭션이 충돌이 많이 발생한다고 가정하고 먼저 락을 겁니다. (비관적 접근)</li>
<li>데이터베이스에서 제공하는 락을 사용합니다. Pessimistic Lock은 많은 종류가 있기 때문에 각 비즈니스마다 선택해서 사용 권장</li>
<li>Optimistic Lock과 비교하면 더 안정적인 방식이지만 실시간 데이터를 보여주는 비즈니스의 동시성과는 어울리지 않는다.</li>
</ul>
<h2 id="4-optimistic-lock으로-동시성-제어">4. Optimistic Lock으로 동시성 제어</h2>
<h3 id="⭐-span-stylecolor-lightcoralversion-컬럼-추가-span">⭐ <span style="color :LightCoral">Version 컬럼 추가 </span></h3>
<ul>
<li><p>Database (Post 테이블)
<img src="https://images.velog.io/images/hgs-study/post/3f7b4919-b2d3-471d-b41b-a046964eb31c/image.png" alt=""></p>
</li>
<li><p>Gradle
  =&gt; jOOQ 설정 시 Gradle에서 사용할 version 컬럼을 지정할 수 있습니다.
<img src="https://images.velog.io/images/hgs-study/post/358001e9-1dbe-4a0d-9c2d-aad8c9c8684b/image.png" alt=""></p>
</li>
</ul>
<blockquote>
<p>DslContext 세팅시 <strong>withExecuteWithOptimisticLocking(true) 옵션을 추가</strong>한다.
또한, OptimisticLock이 충돌 때문에 예외를 던지면 커넥션을 계속 물고 있는 상황이 생기는데, <strong>try with resources</strong>를 사용하여 충돌 시 바로 <strong>커넥션을 종료</strong>시킨다.</p>
</blockquote>
<pre><code class="language-java">    public void plusHitOptimisticById(Long id){
        try(Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD)){
            DSL.using(connection, SQLDialect.MYSQL, new Settings().withExecuteWithOptimisticLocking(true))
                    .transaction(configuration -&gt; {
                        PostRecord postRecord = DSL.using(configuration)
                                                  .fetchOne(POST, POST.ID.eq(id));

                        postRecord.setHit(postRecord.getHit() + 1L);
                        postRecord.store();
                    });
        } catch (SQLException e) {
            log.info(&quot;error code : {}, error : {}&quot;, e.getErrorCode(), e);
        }
    }</code></pre>
<ul>
<li>결과
<img src="https://images.velog.io/images/hgs-study/post/d307323f-2a9b-42fa-bbbd-70adfe4b14cb/image.png" alt="">
✅ <strong>테스트 통과</strong>
=&gt; 같은 버전에 2개 이상의 트랜잭션이 충돌하면 선행 트랜잭션의 업데이트만 성공하고 나머지 트랜잭션은 예외를 발생시킵니다. 해당 예외를 잡고 리트라이해서 동시성을 제어합니다.</li>
</ul>
<h2 id="5-pessimistic-lock으로-동시성-제어">5. Pessimistic Lock으로 동시성 제어</h2>
<h3 id="⭐-span-stylecolor-lightcoralforupdate를-활용한-pessimistic-lock-span">⭐ <span style="color :LightCoral">forUpdate를 활용한 Pessimistic Lock </span></h3>
<blockquote>
<p>forUpdate는 다른 트랜잭션의 CRUD 모두 허용하지 않는 특징이 있다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/hgs-study/post/c164b4b3-f0aa-416a-a29b-01905695ce9d/image.png" alt=""></p>
<ul>
<li>DslContext 트랜잭션의 해당 데이터 조회 부분에 <strong>forUpdate</strong>라는 메서드를 추가한다.
<img src="https://images.velog.io/images/hgs-study/post/ac2fdb26-1ee3-4118-8a75-beb846a96399/image.png" alt=""></li>
<li>결과
<img src="https://images.velog.io/images/hgs-study/post/ce6ccbca-bce9-4ec4-b2b8-4a9c7cf908f4/image.png" alt="">
✅ <strong>테스트 통과</strong>
=&gt; 한 쓰레드 당 하나의 트랜잭션을 가지고 ROW에 접근하여 <span style="color :LightCoral"><strong>순서대로</strong></span> 조회 -&gt; 업데이트하여 hit가 50이라는 결과를 도출할 수 있다.</li>
</ul>
<h2 id="느낀-점">느낀 점</h2>
<h3 id="💡">💡</h3>
<p>앞으로 동시성을 제어해야하 하는 상황에서, 어플리케이션 레벨에서 제어를 할지 데이터베이스 레벨에서 제어를 할지 조금은 더 고민해볼 수 있는 시간이 될 것 같습니다.
데이터베이스 레벨에서 제어한다고 하면 어떤 Lock이 더 비즈니스에 맞을 지 생각하고 선택할 수 있고, Optimistic Lock을 사용하면 어떤 식으로 구성해야할지 고민해볼 것 같습니다.
이번에 개인적으로 공부하면서 저희 파트에도 공유하고 회사 프로젝트에 적용했던 기회였기 때문에 더 재밌게 진행할 수 있었던 것 같습니다.</p>
<h3 id="출처">출처</h3>
<p><a href="https://www.jooq.org/doc/latest/manual/sql-execution/crud-with-updatablerecords/optimistic-locking/">https://www.jooq.org/doc/latest/manual/sql-execution/crud-with-updatablerecords/optimistic-locking/</a></p>
]]></description>
        </item>
    </channel>
</rss>