<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>낙서장</title>
    <link>https://s7won.tistory.com/</link>
    <description>s7won의 개발일지</description>
    <language>ko</language>
    <pubDate>Mon, 13 Apr 2026 00:28:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>s7won</managingEditor>
    <item>
      <title>애매한 사람의 2년간의 개발자 취업 일대기</title>
      <link>https://s7won.tistory.com/15</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 개요&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막으로 글을 쓴지 어느새 7개월이 지났네요. 블로그를 방치하려고 한건 아니었는데... 너무 바빴습니다.(핑계입니다)&lt;br /&gt;오늘 이렇게 새로운 글을 쓰는 이유는, 사실 취업을 했습니다.&lt;br /&gt;&lt;br /&gt;취업했으니까 취업후기나 써야지~ 이런건 아니고.. 본전공도 컴공이 아니었고, 졸업 후 2년간 취업준비를 하면서 내가 해온 일들을 지금 작성하지 않으면 스스로 까먹을 것 같기도 하고, 혹시 나의 이런 경험들이 누군가에게 도움이 될 수 있지 않을까 해서 글을 작성해보려고 해요. 이런 목적이다 보니 이번 글은 편하게, 또 두서없이 작성할 것 같습니다. 혹시 이 글을 읽으실분이 있을 진 모르겠지만, 매우 가독성이 좋지 않을 예정이니 미리 사과드립니다..&amp;gt;&amp;lt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;저는 은행 디지털/IT부서에 취직하게 되었는데요, 사실 개발 공부를 시작하면서 이런 금융권에서 일은 안해야지.. 라고 생각했었는데 ㅎㅎ... 왜 생각이 바뀌었는지도 아래에 후술해 보겠습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;저는 2년간 이런 노력을 해왔어요.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbYC3S/btsHmYl1NpU/BvnBEyRocRxVW0gup7LipK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbYC3S/btsHmYl1NpU/BvnBEyRocRxVW0gup7LipK/img.png&quot; data-origin-width=&quot;344&quot; data-origin-height=&quot;412&quot; data-is-animation=&quot;false&quot; style=&quot;width: 18.3321%; margin-right: 10px;&quot; data-widthpercent=&quot;18.55&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbYC3S/btsHmYl1NpU/BvnBEyRocRxVW0gup7LipK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbYC3S%2FbtsHmYl1NpU%2FBvnBEyRocRxVW0gup7LipK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;344&quot; height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xxuCs/btsHmSzEpQu/Pshuk9M6rkhP7UeAWjivKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xxuCs/btsHmSzEpQu/Pshuk9M6rkhP7UeAWjivKk/img.png&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;213&quot; data-is-animation=&quot;false&quot; style=&quot;width: 80.5051%;&quot; data-widthpercent=&quot;81.45&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xxuCs/btsHmSzEpQu/Pshuk9M6rkhP7UeAWjivKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxxuCs%2FbtsHmSzEpQu%2FPshuk9M6rkhP7UeAWjivKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;781&quot; height=&quot;213&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이런 스트릭이나, 커밋 내역이 노력의 척도라고는 생각하지 않습니다만 회고하는김에 넣어봤습니다 ㅎㅎ..&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그럼, 이제 졸업 전의 저의 상황과, 졸업 후에 어떤 과정을 거쳐왔는지 기억을 정리하면서 작성해보겠습니다.본격적인 회고에 앞서서, 부끄럽지만 졸업 당시의 저의 스펙을 작성해보고 시작할게요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;학력 : 인서울 중? 중하?전공 : 경영대학의 한 전공 / 컴퓨터공학 융합전공학점 : 3.95 / 4.5자격증 : 정보처리기사, SQLD프로젝트, 대외활동, 동아리, 대외활동 등등... : 경험 無&lt;br /&gt;&lt;br /&gt;간단히 설명을 해보자면, 경영대학의 한 과를 졸업했고 융합전공으로 24학점으로 공학사를 취득했어요.&lt;br /&gt;SQLD는 취득하면 한 수업에서 A+을 주신다고 하셔서 취득했고, 정보처리기사는 4학년 때 너무 한게 없어서 이거라도 해야겠다... 라는 생각으로 취득했던 것 같습니다. 그 외에 다른 어떤 경험은 단 한개도 없었어요.&lt;br /&gt;컴퓨터공학을 융합전공했지만, 대부분 이론 수업들을 위주로 들었던 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 왜 개발자?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;생각해보니 왜 개발자로의 길을 걷기로 했는지부터 간단하게 얘기해볼까 해요.&lt;br /&gt;저는 수능영어가 재미있다는 이유로 영문과로 대학교에 입학했어요. 근데, 막상 입학해보니 뭔가 제가 생각했던 것과는 다른 부분도 있고, 또 영문학을 해석해서 어떤 답을 맞추는 것에 대한 회의감이 들었어요. 물론 점수를 내야 하는 부분도 십분 이해하고 있지만, 작가의 뜻이 이렇지 않을수도 있지 않나..? 라는 생각을 많이 했던 것 같습니다.&lt;br /&gt;&lt;br /&gt;그러던 와중에 코딩 수업을 듣게 되었어요. 저희 학교는 1학년 학생들에게 코딩 과목의 의무배정이 되었었거든요. 스크래치나 간단한 파이썬을 배우는 수준이었지만, 같은 문제에 대해서도 답을 다양하게 낼 수 있다는 것이 저는 많이 흥미로웠던 것 같아요.&lt;br /&gt;얘를 들면, 1부터 100까지 더해보세요! 라는 문제에 누군가는 1부터 100까지 진짜 더할수도 있고, 누군가는 등차수열 공식을 사용할 수도 있고, 누군가는 반복문을 사용할수도 있겠죠? 이런 다양한 방식으로 문제를 풀어나가고 개선해나가는 과정이 신선했던 것 같습니다.&lt;br /&gt;&lt;br /&gt;그래서, 군대에 갔다온 뒤로 전과를 하게 되었습니다. 재미있는건 컴공으로 한게 아니라 경영대학 내에서 경영과목 + IT과목을 같이 배우는 과로 전과했어요. 이 때 당시에는 뭔가 갑자기 진로를 확 바꾼다는게 두려웠었나봅니다 ㅎㅎ&lt;br /&gt;&lt;br /&gt;이렇게 전과도 하고, 컴공 융합전공도 하면서 대학을 졸업했지만 사실 저는 제가 뭘 해야할지 하나도 모르겠더라구요. 그래서 결국엔 개발 실력도 없고, 뭐 하나 아는게 없는 상태로 대학교를 졸업하게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 첫 시즌(2022 상반기)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이렇게 아무런 대책없이 대학교를 졸업하게 되었습니다. 이 때 가장 큰 문제는 제가 아직도 뭘 해야하고, 뭘 잘하는지를 몰랐던 것 같아요. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그래서 내가 대학시절 제일 재밌게 했던게 뭘까? 했을 때 코딩이 떠올랐었고, 개발자로 취직해야겠다! 라는 생각을 하게 됩니다. 그래서 개발자로의 취직을 알아봤는데... 정말 저는 답이 없는 상태였어요. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;자기소개서에 작성 할 경험이나 이력이 단 한가지도 없었고, 개발을 잘 알지도 못했습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 때 정말 무슨생각이었는지 모르겠어요. 그냥 막연하게 자바의 정석을 구입해서 공부를 시작하고, 매일매일 알고리즘 문제를 풀면서 개발을 습관화 하기 위해 노력했어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;서류합격률 : 0% (지원불가)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. 두번 째 시즌(2022 하반기)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이렇게 대책없이 혼자 공부를 해나가던 중... 이대로는 안되겠다고 느꼈습니다. 내가 뭘 공부하는지도 잘 모르겠고, 또 팀 프로젝트 경험도 해보고 싶었어요. 그러던 와중 '멋쟁이 사자처럼 백엔드 스쿨' 이라는 부트캠프를 알게 되었습니다. 커리큘럼도 괜찮아 보이고, 멋쟁이 사자처럼이라는 이름도 어디선가 들어본 것 같고..ㅎㅎ 그래서 지원을 했었습니다. 이 때는 슬슬 제 인생이 산으로 가고있다는 걸 체감할때라 정말 간절하게 지원했던 것 같아요. 아마 1차 때 과제 테스트가 있고, 2차 때 영상을 찍어서 질문에 대답하는 형식이었던 것 같은데 진짜 과제도 열심히 하고 영상도 정장입고 20번은 넘게 찍었던 것 같습니다.&lt;br /&gt;&lt;br /&gt;이런 간절함 때문이었는지? 붙게 되었고 수업을 듣게 되었어요.&lt;br /&gt;강사님, 수업 커리큘럼, 팀원들, 프로젝트 경험까지 정말 성장을 많이 한 과정이었습니다.&lt;br /&gt;온라인으로 5개월을 진행하다보니 솔직히 후반부에는 조금 많이 지쳤었는데, 새로운 걸 배우는게 너무 재미있어서 잠도 줄이면서 공부했던 것 같습니다. 이 때는 뭔가 성능을 고려한다거나, 하기보단 만들기에 급급했던 때였어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;5개월간의 과정을 수료하니 11월 11일이었던 것 같아요. 하반기 공고가 거의 말라버린 때였지만, 그래도 공고들이 종종 올라오던 때였습니다. 부트캠프에서&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;내가 정말 뭔가를 만들다보니 뭔가 잘 되어가는 느낌을 받았었고, 자신감도 있었기에 올라왔던 공고들에는 다 지원을 했던 것 같아요. &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그리고 SSAFY에도 지원을 했었어요. 왜냐하면, 뭔가 저는 다양한 사람들과 함께 공부하는 것 보다 혼자 공부하는게 항상 효율적이라고 생각을 했었는데, 의지도 있고 의욕도 있는 동료들이랑 함께하니까 정말 효율이 좋다는 걸 느껴서 이런 기회가 한번 더 주어지면 더 잘 해낼 수 있다고 생각을 했었거든요.&lt;br /&gt;&lt;br /&gt;각설하고, 이 때도 서류는 사실 한개도 붙지 못했습니다ㅎㅎ.. 사실 저 위에 나열한 스펙에서 그냥 부트캠프 경험이랑 한 번의 프로젝트가 더 생긴거였는데, 이런걸로는 별로 메리트가 없던 것 같습니다.&lt;br /&gt;그리고, 사실 대기업 위주로만 지원해서 다 떨어진것도 있는 것 같습니다. 이 때까지만 해도 뭔가 꼭 대기업에 가고싶다는 근거없는 생각이 매우 컸거든요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;새로 생긴 경험 : 교육경험 1회, 프로젝트 1회&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;서류합격률 : 0%&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;5. 세번 째 시즌(2023 상반기)&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;두번 째 시즌에서도 모든 기업에 다 떨어졌는데, SSAFY에는 운좋게 붙게 되어서 입과를 하게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;첫 번째 교육을 들으면서 자바랑 스프링은 어느정도 아는 상태였기 때문에 SSAFY 1학기 과정은 생각보단? 수월하게 진행했던 것 같습니다.&lt;/span&gt; &lt;br /&gt;SSAFY 커리큘럼을 잘 따라가면서, 코딩테스트 준비도 하고 CS 스터디도 하고.. 정말 바쁘게 살았던 것 같아요.&lt;br /&gt;이 때 부터는 단순히 코드를 작성하는 것이 아니라, 조금 더 효율적으로 작성하기 위해서 학습하는데에 초점을 두고 공부했어요. 또 대기업 위주로 서류도 꾸준히 넣었었구요. 주어진 과제나 학습에 대해서 너무 지치지 않을 정도로 열심히 했던 것 같습니다. 그러다 보니 SSAFY 1학기 관통프로젝트에서 1등을 해보기도 하고, 많지는 않지만 슬슬 대기업 서류들도 붙었던 것 같습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;새로 생긴 경험 : 프로젝트 1회, SSAFY 1학기 수료, 수상 1회(SSAFY 1학기 관통프로젝트 최우수상)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;서류합격률 : 20%&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;6. 네번 째 시즌(2023 하반기)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;슬슬 서류를 붙는 기업들이 생겼지만, 결과가 좋지는 못했어요. 종합적으로 채용 과정에서 저는 모든 부분에 문제가 있었습니다.&lt;br /&gt;서류 합격이 정말 소중한 기회였지만, 코딩 테스트에서 떨어지는 경우도 있었고, 면접에 대한 대비가 부족했던 것 같아요.&lt;br /&gt;그래서 부족한 부분에 대해서 보완하기 위해 이것저것 했던 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;우선, 개발 실력을 향상시키기 위해 노력했어요. SSAFY의 2학기는 3번의 프로젝트로 구성되기 때문에 이 프로젝트들을 정말 잘 해보려고 노력했던 것 같아요. 팀장도 해보고, 새로운 기술도 사용해보고, 코드를 잘 작성하기 위해 노력했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고, 서류 합격률을 높이기 위해 어학을 취득했어요. 오픽IH를 취득했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또, 힘들게 서류를 붙어도 코딩테스트에서 떨어지는 걸 최소화 하기위해 알고리즘을 조금 더 신경써서 공부했던 것 같습니다. 그리고, 코틀린으로 알고리즘 문제를 풀기 시작했습니다. 알고리즘 문제를 매일매일 풀고는 있었지만 뭔가 슬슬 흥미도 떨어지고, 이게 의미가 있나 싶을 때가 많았거든요. 왜냐하면, 바쁜 일상을 보내다 보니 매일 어려운 문제를 풀지는 못했었고 그러다보니 의미없는 백준 브론즈 문제를 그냥 풀고 마는 날이 많았어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이럴거면 새로운 언어에 익숙해지기라도 하자라는 마음으로 코틀린으로 문제를 풀기 시작했었고, 이게 저한테는 굉장히 좋은 선택이었습니다.&lt;br /&gt;&lt;br /&gt;코틀린으로 알고리즘 문제를 풀면서, 코틀린에 익숙해졌고 또 새로운 언어를 하는게 너무 재미있어서 알고리즘 문제도 찾아서 풀었던 것 같아요. 또 생각보다 최근에는 코테에서 코틀린을 지원하는 기업들이 많더라구요. 자바로 풀다가 코틀린으로 풀면 편한 부분들이 있어서 기업 코테에서도 많이 재미를 본 것 같아요.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이때부터는 대기업 뿐만 아니라 괜찮아 보이는 기업에는 대부분 지원을 했던 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이렇게 다양한 노력들을 하다보니, 최종면접에도 종종 가게 되었고, 최종 합격을 하는 기업도 생기기 시작했어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;새로 생긴 경험 : 프로젝트 3회(팀장경험), SSAFY 2학기 수료, 어학(오픽IH)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;서류합격률 : 30%&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;7. 다섯번 째 시즌(2024 상반기)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;최종합격을 한 기업도 나왔는데 왜 이번시즌까지 글을 쓰냐면, 사실 입사하지 않았습니다. 두 기업에 최종 합격을 했는데, 내가 목표하던 기업들은 아니었고 또 입사하게 되면 제가 정말 가고싶었던 기업 최종면접에 갈 수가 없었거든요. 결과적으로는 가고싶었던 기업에서 최종탈을 하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;딱 하루만 좌절하고 이번시즌에는 뭘 더 보완할 수 있을까 생각해봤어요. 우선, 서류합격률을 더 높이고 싶었고 면접을 더 잘 봐야겠다고 느꼈습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;서류합격을 위해 지금까지의 저를 진단해봤을 때 뭔가 필살기라고 할만한게 없더라구요. 사실 요즘 부트캠프를 듣고 취준하시는 분들이 너무 많아서, 저만의 필살기를 만들어야겠다고 생각했습니다. 그래서 IT 대외활동을 하나 하게 되었어요. 서비스 빌딩부터 구현까지 해나가면서 저만의 경험을 만들기 위해 노력했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음으로는, 면접에 관한 부분을 보완하려고 사색하는 시간을 많이 가졌어요. 이게 무슨 말인가 싶으시겠지만, 기술면접은 사실 알면 알고 모르면 모르는건데, 인성면접이 대비도 어렵고 준비에 시간이 꽤 많이 들더라구요. 그래서, 아무생각없이 카페에 가서 나라는 사람에 대해 탐구하는대에 시간을 많이 썼던 것 같습니다. 내가 뭘 좋아하고 싫어하는지, 나의 강점이 뭐고 약점이 뭔지, 나는 이런 상황에서 어떻게 행동하는지...&amp;nbsp; 실제로 이런 부분이 많은 도움이 됐었어요. 이후에 인성면접에서는 크게 대비를 하지 않고도 결과가 괜찮았던 것 같습니다. (몇 번 없지만..)&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;새로 생긴 경험 : 프로젝트 1회(대외활동)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;서류합격률 : 50%&amp;uarr; (시즌이 끝나기 전에 취업해서 애매한 부분이 있습니다)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;8. 마무리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이렇게 취준을 해나가던 와중, 얼마전에 은행에 최종합격을 하였고 입사를 결정하게 되었습니다. 아직은 연수원 신분이라 완전히 취직한건 아니지만, 열심히 해보려고해요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2년이 넘는 기간동안 취업을 준비하는게 사실 엄청 힘들진 않았어요. 제가 스트레스를 받아도 금방 잊는 성격이기도 하고, 개발이 너무 재미있었거든요.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그런데 제 취업 소식을 들으신 부모님이나 주위 친구들이 너무 좋아하는 모습을 보면서 너무 저만 생각한 취업준비기간이 아니었나 하는 생각이 들었습니다. 앞으로는 제 할일도 열심히 하고 주위 사람들도 잘 챙기면서 다양한 방면에서 성숙한 사람이 되어가기 위해 노력해야겠어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;9. 왜 은행?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;취직을 했기 때문에 글이 끝날거라 예상하셨겠지만, 왜 은행에 지원했고 입사를 결정했는지도 작성해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사실, 개발자로 취업을 준비하면서 금융권 서류를 쓰고 준비한건 네번 째 시즌(SSAFY전형) 한 번밖에 없었어요. 왜냐하면, 조금 보수적이라는 이미지와 핀테크 기업에 비하면 기술력도 많이 부족하지 않을까? 라는 거만한 생각을 했었거든요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그런데, 왜 지원하고 입사하게 되었냐면 생각이 많이 바뀌었어요. AI가 정말 빠르게 발전하면서 개발자라는 직업, 특히 주니어 레벨의 개발자에 대한 고용 안정성에 대해서도 생각을 많이 하게되었어요. 또 은행 앱들을 이리저리 사용해보면서 내 생각보다 정말 잘 만들어져 있다고 느꼈습니다. 오래된 시스템에 이것저것 붙여가면서 새로운 서비스들을 제공하는 것 일텐데, 이정도면 정말 훌륭하다는 생각을 했습니다. 그리고 최근에 금융권에서도 망분리와 같은 이슈들, 또 디지털에 투자를 아끼지 않는 모습을 보면서 이곳에서 정말 많은 경험을 하고, 내가 많은걸 개선하는데 기여할 수 있지 않을까? 라는 생각도 하게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 대외활동 중 서비스 빌딩을 하면서 도메인에 대한 지식이 정말정말 중요하다고 많이 느꼈었는데, 행원으로 근무하면서 이런것들을 빠르게 배워나갈 수 있는 좋은 기회가 될 수 있다고도 생각했어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;10. 앞으로&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;저는 앞으로 약 2년간의 기간동안 행원을 한 뒤, 디지털 분야의 일을 하게 될 것 같아요. 그래서, 당장 일이 익숙해질 때 까지는 행원 일에 집중을 하고, 그 뒤에는 다시 개발 공부를 하지 않을까 싶습니다. 블로그 글을 취준할 때 보다 더 열심히 쓰게 될 것 같은 느낌이에요 ㅎㅎ..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;뭔가 글을 이렇게 마무리하려니까 애매하게 마무리 하는 것 같네요. 여기까지 읽으신 분이 있을까요?ㅎㅎ&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;저도 이제 막 취업을 했고, 오랜 시간이 걸린 입장이라 뭔가 조언을 드릴만한 위치가 아니란 것을 잘 알고 있습니다.&lt;br /&gt;2년간 이것저것 뭔가를 했지만 사실 막 대단한 스펙이 있는 거나 머리가 좋은것도 아니구요.그렇기 때문에 혹시, 취업 준비가 너무 힘이 드신 분이 계시다면 그때 마다 나를 응원해주고 지지해주는 사람들을 생각하면서 조금 더 힘내보자는 말로 글을 마무리해볼까 합니다. 길고 두서없는 글 읽어주셔서 감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>잡담</category>
      <category>SSAFY</category>
      <category>멋쟁이사자처럼 백엔드스쿨</category>
      <category>취업후기</category>
      <author>s7won</author>
      <guid isPermaLink="true">https://s7won.tistory.com/15</guid>
      <comments>https://s7won.tistory.com/15#entry15comment</comments>
      <pubDate>Sun, 12 May 2024 01:20:53 +0900</pubDate>
    </item>
    <item>
      <title>파티셔닝(Partitioning) 샤딩(Sharding) 레플리케이션(Replication)</title>
      <link>https://s7won.tistory.com/13</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;0. 시작&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;파티셔닝, 샤딩, 레플리케이션은 모두 대규모 데이터베이스 관리, 성능 최적화, 그리고 시스템의 안정성 및 가용성을 향상시키기 위한 전략이라고 할 수 있습니다. CS공부도 조금 해야하고, 마침 새로 시작하는 프로젝트를 MSA로 진행할 것 같아서, 한번 이 개념들에 대해서 알아볼게요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 파티셔닝(Partitioning)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;파티셔닝이란 기본적으로 Database Table을 &lt;b&gt;더 작은 Table&lt;/b&gt;로 나누는 것을 의미합니다. 파티셔닝은 크게 두 가지 종류로 나눌 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Vertical Partitioning&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Column을 기준으로 table을 나누는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Horizontal Partitioning&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Row를 기준으로 table을 나누는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저, Vertical Partitioning부터 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. Vertical Partitioning&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;게시판을 의미하는 Article이라는 테이블이 이렇게 정의되어 있고, 사용되고 있다고 가정해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lx9gc/btsyJx5bJys/yj6qQKMZyoWE3l37zrPy8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lx9gc/btsyJx5bJys/yj6qQKMZyoWE3l37zrPy8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lx9gc/btsyJx5bJys/yj6qQKMZyoWE3l37zrPy8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLx9gc%2FbtsyJx5bJys%2Fyj6qQKMZyoWE3l37zrPy8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1101&quot; height=&quot;84&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 테이블을 기준으로, 어떠한 게시판이 만들어져 있다고 생각해볼게요. 게시판의 목록의 형태를 생각해보면, 보통 내용을 제외한 목록을 보여주고 있을 거에요.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697642370057&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT id, title , ... , comment_cnt
FROM article
WHERE ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;아마 이러한 형식의 쿼리문으로 목록을 불러올 것 같습니다. 이 쿼리문은, article이라는 테이블에서 특정 조건을 만족하는 게시글들을 불러오겠네요. 여기서 어떤 조건은, 시간이 될 수도 있고 작성자 기준이 될 수도 있겠죠?&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어떠한 조건이던간에, 목록을 불러올 때는 content라는 항목은 필요 없으니 이를 제외한 쿼리를 작성할 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기서 중요한 점은, 테이블에 있는 이런 데이터들은 HDD, SDD와 같은 저장공간에 저장이 되어있을거에요. 이 쿼리문이 실제로 어떻게 동작하는지 간략하게 설명하면, 저장공간에서 저 쿼리문을 불러올 때 관심있는 항목들인 id, title... 만 불러오는 것이 아니라 일반적으로는 행 전체를 불러온 뒤, 쿼리문을 기준으로 필터링을 해서 원하는 정보만을 가져옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기서 중요한 점은, content라는 항목은 다른 항목들에 비해 차지하는 크기가 매우 클 가능성이 높지만, 보여주려고 하는 데이터가 아니라는 점이에요. 즉, 실제 필요한 항목이 아니어서 쿼리문을 저렇게 작성했는데, &lt;b&gt;사실은 모든 행을 저장공간에서 불러와서 메모리에 올린 다음에, 원하는 항목만 필터링 해서 가져온다는 것입니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;설명이 복잡했는데요, 요약하면 content라는 관심 없는 항목에 대해서 &lt;b&gt;I/O 작업에 대한 부담&lt;/b&gt;이 생긴다는 것이에요. 이 화면에서 사용되지 않는 content까지도 일단 메모리에 올려야 하는데, 하필이면 사이즈가 큰 항목인거죠.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Where절에 Index가 잘 걸려있다면 체감하지 못할 수도 있지만, Full-Scan이 일어날때에는 체감이 될 정도로 성능에 영향을 끼칠 수 있는 부분입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이런 경우에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;칼럼을 기준으로 테이블을 분리하는 방법인&lt;/span&gt; Vertical Partitioning을 활용할 수 있어요.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;80&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNCA0F/btsyHzCGzLn/0tSf9K6WXdQk9z8ob3j3Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNCA0F/btsyHzCGzLn/0tSf9K6WXdQk9z8ob3j3Rk/img.png&quot; data-alt=&quot;수직 분할&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNCA0F/btsyHzCGzLn/0tSf9K6WXdQk9z8ob3j3Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNCA0F%2FbtsyHzCGzLn%2F0tSf9K6WXdQk9z8ob3j3Rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1270&quot; height=&quot;80&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;80&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;수직 분할&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이렇게 분리하면, 무거운 content는 다른 테이블로 이동했으므로 게시글 목록을 불러올 때 빠른 Select가 가능하게 되었습니다. 만약에 전체 게시글 정보가 필요하면, join을 적절히 활용하면 되겠죠?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Vertical Partitioning은 정규화가 되어있는 테이블에도 사용 가능하고, 또 보안에 민감한 정보들을 따로 모아두는 용도로도 활용할 수 있습니다. 또한, 자주 사용되는 혹은 자주 사용되지 않는 항목들로 구성하여 활용할수도 있겠네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. Horizontal Partitioning&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번에는 row 기준으로 table을 나누는 방식인 Horizontal Partitioning이에요. Vertical과 달리, 이 방식은 테이블의 스키마에는 변화가 없어요. row 기준으로 나누니 당연하겠죠? 예제를 통해서 자세히 알아볼게요.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6TGro/btsyIGafbcA/vRzmIJRbmu5x7CX2jnDcrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6TGro/btsyIGafbcA/vRzmIJRbmu5x7CX2jnDcrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6TGro/btsyIGafbcA/vRzmIJRbmu5x7CX2jnDcrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6TGro%2FbtsyIGafbcA%2FvRzmIJRbmu5x7CX2jnDcrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;266&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Subscription이라는 테이블의 예시입니다. 유튜브에서 사용자의 구독 정보를 포함하는 형태의 간단한 예시에요. 이 테이블은 어느 정도의 데이터를 최대치로 가질 수 있을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사용자 수를 N으로, 채널의 수를 M으로 가정한다면 이 테이블의 최대 행의 수(모든 사용자가 모든 채널을 구독)는 N*M이 될거에요. 따라서, 사용자가 백만명이고 채널 수가 천개라면 최대 행의 수는 10억개가 되겠네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기서 주의깊게 생각해봐야 할 부분이 있어요. 우선, 테이블의 크기가 커질수록 인덱스의 크기도 커질거에요. 따라서, 테이블에 읽기/쓰기 작업이 있을 때 마다 인덱스에서 처리되는 시간도 조금씩 늘어날겁니다. 인덱스를 타고 효율적으로 검색한다고 해도, 인덱스의 갯수 자체가 많아지면 시간이 조금 더 걸릴거에요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이런 상황에서 사용될 수 있는 방법이 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Horizontal Partitioning 입니다. 이것을 수행하는 여러가지 방법론이 있는데, 가장 많이 사용되는 hash-based의 방식을 설명해 보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이런 큰 테이블을 만들 때, Hash Function도 하나 만듭니다. 이 함수는 user_id를 입력했을 때 0, 1, 2, 3중 한 값을 반환한다고 가정해보겠습니다. 이제, Subscription과 같은 스키마 구조를 가진 Subscription_0, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Subscription_1, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Subscription_2, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Subscription_3 라는 테이블을 만들고, 이 해시 함수가 반환하는 숫자에 해당하는 테이블에 값을 배정해주는거에요. 테이블의 갯수는 프로젝트의 요구사항, 여러 상황에 따라서 조정해서 구성하면 되겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;위에서 user_id를 입력으로 넣는다고 했는데요, 이러한 값을 partition key라고 합니다. 이 때, a라는 유저가 구독한 모든 채널을 조회하려면 어떻게 해야할까요? &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;단순하게, 해시 함수에 a값을 넣고, 반환하는 번호에 해당하는 테이블로 가서 조회하면 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그렇다면, user_id가 아닌 특정한 채널을 구독한 모든 사용자를 불러오려면 어떻게 해야할까요? channel_id는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;partition key가 아니기 때문에, 모든 테이블(0, 1, 2, 3)을 순회하면서 조회를 해봐야해요. 따라서, partition key는 가장 많이 사용될 패턴에 따라서 정해주는게 정말 중요해요. 그래야, 이렇게 테이블을 나눈 이점을 최대한 활용할 수 있습니다. 또한, 데이터가 균등하게 분배될 수 있도록 해시 함수를 정의하는 것도 매우 중요해요. 이렇게 테이블을 나눠놓았는데, 특정 테이블에 레코드들이 몰리면 테이블을 나눈 이점을 많이 활용할 수 없겠죠?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;위에서 설명한 해시 기반의 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Horizontal Partitioning은 한번 partition이 나눠지면, 이후에 partition을 추가하기가 매우 까다로워서 신중하게 설계해야합니다. 이미 저장되어 있는 레코드들도 새로운 partition으로 옮겨줘야 할 가능성이 생기기 때문이에요. 실제 서비스를 운영하면서 이러한 작업을 하는건 매우 부담스러운 작업일 것이니깐요.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;해시 기반 외에도, 순차적인 값(날짜, 시간, 숫자 범위...)의 범위를 기준으로 나누는 Range-based, 특정 카테고리(지역, 국가, 부서...) 기반으로 나누는 List-based 등의 방법들도 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. Sharding&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;샤딩은 앞에서 보았던 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Horizontal Partitioning과 굉장히 유사해요. 실제로 동작 자체도 동일하게 이루어집니다. 즉, row를 기준으로 테이블을 나누게 됩니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Horizontal Partitioning과의 큰 차이점은 각각의 Partition들이 &lt;b&gt;독립된 DB 서버&lt;/b&gt;에 저장된다는 점입니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;위에서 보았던, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Subscription_0,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Subscription_1,&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Subscription_2,&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Subscription_3들은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Horizontal Partitioning에서는 같은 DB 서버에 저장됩니다. 이러한 방식은, 하나의 컴퓨터(서버)에 저장이 되기 때문에 백엔드로부터 많은 요청이 발생하면 어떤 Partition에 대한 요청이던 한 서버의 cpu와 memory를 사용해서 요청을 처리하게 됩니다. 즉, 하드웨어 자원이 한정되어 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이를 샤딩에서는 각각 다른 서버에 저장하는 거에요. 이 경우에는, 백엔드로부터 많은 요청이 들어와도, 각각 Partition에 해당하는 DB 서버가 존재하기 때문에 &lt;b&gt;부하를 분산시키는 효과&lt;/b&gt;를 가질 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;요약하면, 샤딩은 각 Partition을 서로 다른 DB 서버에 저장해서 부하를 분산시키고자 하는 방식입니다. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;규모가 큰 서비스, 데이터가 많이 쌓이는 테이블, 트래픽이 많이 몰리는 테이블은 이런 식으로 샤딩을 활용하여 각 Partition 마다 독립된 DB 서버를 할당하고, 이를 통해 트래픽을 분산시켜 DB 서버의 부하를 낮출 수 있습니다. 이러한 샤딩의 경우에는, 위에서 Partition key라고 부르던 것을 Shard key라고 부르고, 각 Partition을 Shard라고 부릅니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;서버에 여유만 있다면, 샤딩은 매우 좋은 방식 같아보이고 실제로도 그렇습니다. 하지만 고려해야 할 부분은 트랜잭션에 관한 부분이에요. 다른 DB 서버에 데이터들이 존재하기 때문에, 트랜잭션 관리가 실제로 많이 복잡해집니다. 이를 위해 2PC, SAGA 패턴 등 다양한 방법들이 존재합니다. 이 부분에 대해서는 MSA를 적용해보면서 조금 더 학습하고 작성해 보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;5. Replication&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;어떠한 테이블이 DB 서버에 저장되어 있고, 백엔드 서버에서 DB 서버로 read/write 요청을 보내는 경우를 가정해 보겠습니다. 그런데 이 때, DB 서버에 어떠한 문제가 생긴다면 정상적으로 동작할 수 없기 때문에, 사용자들은 피해를 볼거에요. 이런 상황이 실제 서비스에서는 발생하면 안되기 때문에, 어떻게든 해결을 해줘야 하는데 이럴 때 사용하는 방식 중 하나가 레플리케이션입니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;레플리케이션이란, 메인 DB 서버와 &lt;b&gt;동기화된 보조 DB 서버&lt;/b&gt;를 운영하는 것을 의미합니다. 즉, 메인 DB 서버에서 write 작업이 발생하면, 보조 DB 서버에도 같은 작업이 발생해요. 즉, 위의 상황에서 메인 DB 서버에 문제가 발생했을 시, 보조 DB 서버를 통해 문제를 해결하는 방식이 레플리케이션입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이러한 방식을 failover(장애 극복 기능) 이라고도 부르고, 이런 식의 장애 상황이 발생했음에도 서비스를 운영할 수 있게 하는 이러한 구조, 특성을 High Availability(HA, 고 가용성) 이라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;레플리케이션은 이렇게 사용할 수 있을 뿐만 아니라, 동기화된 보조 DB를 활용하여 서버의 트래픽이 몰릴 시 read 작업을 보조 DB로 분산 시켜 부하를 적절히 완화하는데 이용할수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;레플리케이션에서는, 메인 DB 서버와 보조 DB 서버를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(master - slave, primary - secondary, leader - replica) 이러한 형태로 호칭합니다. 사실, 저도 master-slave 형태의 말들을 많이 들어왔고 사용해왔는데요 이러한 호칭 방식이 구시대적인 방식이니 더이상 이렇게 쓰지 말자 라는 의견들도 있다고 하네요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;참고 자료 : &lt;a href=&quot;https://www.youtube.com/watch?v=P7LqaEO-nGU&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=P7LqaEO-nGU&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>DB</category>
      <category>Database</category>
      <category>DB</category>
      <category>partitioning</category>
      <category>Replication</category>
      <category>sharding</category>
      <category>레플리케이션</category>
      <category>샤딩</category>
      <category>파티셔닝</category>
      <author>s7won</author>
      <guid isPermaLink="true">https://s7won.tistory.com/13</guid>
      <comments>https://s7won.tistory.com/13#entry13comment</comments>
      <pubDate>Thu, 19 Oct 2023 01:25:18 +0900</pubDate>
    </item>
    <item>
      <title>스핀락(Spinlock) 뮤텍스(Mutex) 세마포어(Semaphore)</title>
      <link>https://s7won.tistory.com/12</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;0. 기본 개념&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;경쟁 조건 (Race Condition)&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여러 프로세스/스레드가 동시에 같은 데이터를 조작할 때 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 상황&lt;/span&gt;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;동기화(Synchronization)&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여러 프로세스/스레드를 동시에 실행해도 공유 데이터의 일관성을 유지하는 것&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;임계 영역(Critical Section)&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;공유 데이터의 일관성을 보장하기 위해 하나의 프로세스/스레드만 진입해서 실행 가능한 영역&lt;/span&gt;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;상호 배제(Mutual Exclusion)&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 임계 영역에서 설명한, 하나의 프로세스/스레드만 진입해서 실행한다는 것 또는 동시 프로그래밍에서 공유 불가능한 자원의 동시 사용을 피하기 위해 사용되는 알고리즘&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇다면, 어떻게 이 상호 배제를 달성할 수 있을까요? &lt;b&gt;락(Lock)&lt;/b&gt;을 사용해서 이를 달성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693920482304&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;do {
    acquire lock // 여러 프로세스/스레드가 lock을 획득하기 위해 경합
    	[critical section] // lock을 획득한 프로세스/스레드만 임계 영역에서 실행함
    release lock // 작업을 끝내고, lock을 반환함
    	remainder section
} while(true)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. Spinlock&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 예제를, 스레드 예제로 다시한번 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693920760277&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;volatile int lock = 0; // global

void critical() {
  while(test_and_set(&amp;amp;lock) == 1); // lock을 획득하려는 시도를 함
  [... critical section] // lock을 얻었다면, 임계 영역으로 진입
  lock = 0; // 작업이 끝난 후, lock을 반환
}

int TestAndSet(int* lockPtr) {
    int oldLock = *lockPtr;
    *lockPtr = 1; // 반환하기 직전에 lock의 값을 1로 바꿔줌
    return oldLock;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2개의 스레드(&lt;b&gt;T1, T2&lt;/b&gt;)인 상황을 가정해 보겠습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;T1이 실행되면서 while문 조건을 검사함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 lock은 0이기 때문에, test_and_set(&amp;amp;lock)은 0을 반환하고 이는 1이 아니므로 test_and_set(&amp;amp;lock) == 1은 false가 되어 while문을 빠져나옴&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;while문을 빠져나오기 직전에, lock을 1로 바꿔줌&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;임계 영역으로 진입하여 작업함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 때, T2가 시작하면서 while문 조건을 검사함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 lock값은 1이기 때문에, test_and_set(&amp;amp;lock) == 1은 true가 되어 while문을 빠져나오지 못함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어느 정도 시간이 지난 후, T1이 작업을 마치고 lock을 반환함. (lock = 0)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;계속해서 조건을 확인하고 있던 T2가 lock을 획득하여 임계 영역으로 진입함.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이러한 Flow로 진행이 되게 됩니다. test_and_set(&amp;amp;lock)을 통해 임계 영역에서의 동시 작업을 방어하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그런데, 동시에 T1와 T2가 작업을 시작하여 while문으로 진입하면 둘다 test_and_set을 통해 lock을 획득하여 임계 영역에 도달할 수 있지 않을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇게 생각하실 수도 있지만, 사실 TestAndSet은 &lt;b&gt;CPU&lt;/b&gt;의 &lt;b&gt;atomic&lt;/b&gt; 명령어입니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;TestAndSet 명령어는 동시성을 제어하기 위한 동기화 명령어 중 하나로서, 하드웨어의 도움을 받아 수행됩니다. 이것을 활용하면 상호 배제 등을 편리하게 구현할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Atomic 명령어의 특징&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 실행 중간에 간섭받거나 중단되지 않습니다&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 같은 메모리 영역에 대해 동시에 실행되지 않습니다, 즉 동시에 호출이 일어나도 CPU level에서 이를 방어합니다&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;따라서 앞의 예제에서 test_and_set(&amp;amp;lock)은 T1이 실행하고 있다가 문맥 교환으로 인해 T2가 실행될 일이 없습니다. 또 멀티 코어 환경에서 2개의 스레드가 이 명령어를 동시에 실행시켰다 하더라도 CPU level에서 동기화를 시켜서 동시에 실행시키지 않습니다.(뭐가 우선적으로 실행될지는 알 수 없음)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 예제에서 봤던, while loop를 빠져나가기 위해 계속해서 lock을 확인하여 획득하는 방식을 &lt;b&gt;Spinlock(스핀락)&lt;/b&gt; 이라고 합니다. 이해하기 쉽게 정리하면 '락을 가질 수 있을 때 까지 반복해서 시도하는 방식' 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 방식은 &lt;b&gt;쉽게 Mutual Exclusion을 구현할 수 있다는 장점&lt;/b&gt;이 존재하지만, 락이 존재하는지를 계속해서 확인해야 하므로 &lt;b&gt;CPU를 낭비한다는 단점&lt;/b&gt;이 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;단점에 대해서 조금 자세히 설명해보자면, 이런 방식은 락이 금방 해제되는 상황에서는 크게 문제가 되지 않을수도 있지만 락을 획득하는데 오랜 시간이 걸리는 경우에는 많은 CPU 자원을 불필요하게 소모하게 됩니다. 특히, 디스크 I/O나 네트워크 통신과 같은 상대적으로 느린 작업을 수행하는 임계 영역이 존재한다면 그 동안 다른 스레드는 락을 획득하려고 계속해서 CPU를 점유하게 되겠죠? 이렇게 되면, 전체 시스템의 성능이 저하될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그래서, Lock이 준비될 때 까지 기다리고 있는 Mutex가 등장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;** 위에서의 TestAndSet은 이해를 위해 작성된 코드이며, 실제와 다르게 작성되어 있습니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. Mutex&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번에도 코드를 먼저 보고 가겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693922615208&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mutex-&amp;gt;lock(); // mutex의 lock을 가지기 위해서 경합
[critical section] // lock을 획득했다면, 임계 영역에 진입
mutex-&amp;gt;unlock(); // 작업이 끝났다면, lock을 반환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;임계 영역에 진입하기 위해 Lock을 획득하고, 작업이 끝난 후 Lock을 반환해야 하는 Flow는 동일합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693922746728&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Mutex{
    int value = 1; // 임계 영역에 진입하기 위해 이 value를 취득해야 함
    int guard = 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;임계 영역에 진입하기 위해, 각 프로세스나 스레드들은 이 Mutex의 value를 획득해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693922826544&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Mutex::lock() {
    while(test_and_set(&amp;amp;guard));
    if(value == 0) { // 이미 누가 value를 취득한 상태라면 (lock을 획득할 수 없음)
        ... 현재 스레드를 큐에 넣음; // lock을 획득할 수 있을 때 깨워주기 위해 선입선출 구조인 Queue에 넣음
        guard = 0; &amp;amp; go to sleep
    } else { // lock을 획득할 수 있다면
        value = 0; // lock을 취득함
        guard = 0;
    }
}

Mutex::unlock() {
    while(test_and_set(&amp;amp;guard));
    if(큐에 하나라도 대기중이라면) { // ex) queue.isNotEmpty()
    	그 중에 하나를 깨운다; // ex) queue.poll()
    } else {
    	value = 1;
    }
    guard = 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위에서의 의사코드와 주석으로 어느정도 이해가 되셨으리라 생각합니다. 그래도 짧게나마 다시 한번 설명해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; lock에서는 당장 lock을 획득할 수 없다면, FIFO구조의 queue에 현재 스레드를 넣고, &lt;b&gt;쉬러갑니다(sleep)&lt;/b&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;unlock에서는 queue에 대기중인 스레드가 존재한다면, 스레드를 깨워서 lock을 부여합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇다면, &lt;b&gt;guard&lt;/b&gt;는 뭘까요? 위에서 Mutex의 value라는 값도 결국은 여러 프로세스/스레드가 서로 취득하기 위해 경쟁하는 공유 데이터입니다. 그렇다면, value라는 값 자체도 그 값을 바꿔줄 때 마다 임계 영역에서 안전하게 바꿔주어야 합니다. 그렇지 않으면, 경쟁 상태가 발생할 수 있기 때문입니다. 그래서, 이 value값을 임계 영역에서 안전하게 바꿔주기 위한 장치가 필요한데 그것이 바로 guard입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위에서 test_and_set의 인자부분이 value(lock)가 아니라 guard인 것을 혹시 눈치채셨나요? value 값을 바꿔주는 로직을 보호하기 위해 guard라는 장치를 통해 보호하고 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이러한 &lt;b&gt;Mut&lt;/b&gt;ual &lt;b&gt;Ex&lt;/b&gt;clusion 방식을 &lt;b&gt;Mutex&lt;/b&gt;라고 부르며, 락을 가질 수 있을 때 까지 휴식하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1번에서 보았던 &lt;b&gt;Spinlock&lt;/b&gt;과 다르게 &lt;b&gt;Mutex&lt;/b&gt;는 lock을 획득할 수 없으면 스레드가 sleep 상태가 되는데요, 이로 인해 Cpu Cycle이 낭비되는 것을 최소화할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이러한 이유로 '보통' 의 경우에는 Spinlock보다 Mutex Lock이 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;** 위에서의 의사코드는 Queue로 구현되어 있는데, 실제 구현은 다를 수 있습니다.(반드시 Queue만 사용되는 것은 아닙니다)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. Mutex vs Spinlock&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위에서 제가 언급한 내용들을 종합하면, Mutex가 항상 Spinlock보다 좋은 것 처럼 느껴지는데 진짜 그럴까요? 대부분에 상황에서는 어느 정도 맞는 말이지만, 꼭 그렇지만은 않습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;멀티 코어&lt;/b&gt; 환경이고, &lt;b&gt;임계 영역&lt;/b&gt;에서의 작업이 &lt;b&gt;문맥 교환&lt;/b&gt;보다 더 빨리 끝난다면 &lt;b&gt;Spinlock&lt;/b&gt;이 &lt;b&gt;Mutex&lt;/b&gt;보다 더 이점이 있습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;앞선 Mutex는 lock을 획득할 수 없다면 스레드들이 sleep 상태로 대기하다가 lock을 획득할 수 있다면 깨어나서 lock을 획득하여 작업을 이어가는 방식이었습니다. 반대로 Spinlock은 계속해서 lock을 획득할 수 있는지 확인하는 작업이었죠? 임계 영역에서의 작업이 이러한 잠들고 깨는(문맥교환) 시간보다 짧다면 Spinlock이 더 우세할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그러면 왜 멀티 코어라는 조건이 붙는걸까요? 싱글 코어 환경에서는 Spinlock을 사용한다고 하더라도, lock을 취득하려면 누군가가 쥐고있는 lock을 해제해야 하는데 이러한 과정이 결국 문맥교환을 필요로 하기 때문입니다. 반면 다른 코어에서 다른 코어의 락을 획득하기 위해 Spinlock 방식으로 확인하고 있다면, lock을 획득하는 과정에서 문맥교환이 발생하지 않기 때문에 이러한 상황에서는 성능 상 이점이 존재하게 되겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. Semaphore&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Signal Mechanism을 가진, 하나 이상의 프로세스/스레드가 임계 영역에 접근하도록 하는 장치&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;간단한 정의는 이러한데요, 이번에도 코드로 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693924887337&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;semaphore -&amp;gt; wait(); // mutex에서의 lock
[critical section]
semaphore -&amp;gt; signal(); // mutex에서의 unlock&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1693924977136&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Semaphore {
    int value = 1; // mutex와 달리 0, 1, 2... 의 값을 가질 수 있음
    int guard = 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1693925086164&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Semaphore::wait() {
    while(test_and_set(&amp;amp;guard));
    if(value == 0) { 
        ... 현재 스레드를 큐에 넣음;
        guard = 0; &amp;amp; go to sleep
    } else {
        value -= 1; // mutex에서는 0으로 바꾸었지만, semaphore에서는 1씩 차감하는 형태
        guard = 0;
    }
}

Semaphore::signal() {
    while(test_and_set(&amp;amp;guard));
    if(큐에 하나라도 대기중이라면) { 
    	그 중에 하나를 깨운다;
    } else {
    	value += 1; // mutex에서는 1로 바꾸었지만, semaphore에서는 1씩 증가하는 형태
    }
    guard = 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;전체적으로 Mutex의 코드와 비슷합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;차이점으로는 value(lock)을 Semaphore는 &lt;b&gt;1 이상 가질 수 있는 부분&lt;/b&gt;과 락을 획득했을 때, 해제했을 때 단순히 value를 0, 1로 바꾸는 것이 아니라 현재 값에서 &lt;b&gt;차감하고 증가하는 형태로 바뀐 부분&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;왜 이러한 형태로 값을 변경하냐면, Semaphore는 임계 영역에 프로세스/스레드가 하나 이상 들어가게 하기 위함입니다. 예를 들어서, 3개의 좌석이 있는 식당이라면 이런 경우에 3개까지 사용할 수 있겠죠? 이러한 상황에 사용할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;물론, value 값을 1로 지정하여 Mutex와 같이 Semaphore도 하나의 프로세스/스레드만 임계 영역에 진입하게 할 수도 있습니다. Semaphore 클래스의 value가 1로 설정되어 있다면, 누군가 lock을 획득하는 순간 value가 0이 될 것이고, 그렇다면 if문 조건에 걸려서 다른 프로세스/스레드는 lock을 획득할 수 없게 될테니까요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이렇게 value값으로 1을 가지는 Semaphore를 &lt;b&gt;binary Semaphore&lt;/b&gt;, 혹은 &lt;b&gt;이진 세마포어&lt;/b&gt;라고 하고, value값으로 1이 아닌 값을 가지는 Semaphore를 &lt;b&gt;Counting Semaphore&lt;/b&gt;라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위에서 Semaphore 정의에 Signal mechanism라는 말을 했었는데요, 이 말은 Semaphore의 값이 변경될 때 다른 프로세스/스레드에 그 사실을 알리는 방법을 의미합니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2개의 프로세스가 있다(P1, P2)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;각 프로세스가 공유 자원을 사용하려고 함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;P1이 먼저 자원을 사용하기 위해 세마포어를 획득함(lock)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;P2는 lock을 얻을 때 까지 기다림&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;P1이 작업을 마치면, Lock을 해제함(Signal Mechanism)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;P2가 그 신호를 받고 자원을 사용함&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1693926082572&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  P1      ---&amp;gt;  Lock  ---&amp;gt;  Work  ---&amp;gt;  Unlock  ---&amp;gt;
          
  P2                  ---&amp;gt;  Wait  ------------&amp;gt;  Work&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 &lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;Signal Mechanism을 통해 실행 순서도 정해줄 수 있게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;그런데, 생각해보면 조금 이상한 부분이 있습니다. 위에서의 Mutex 예시는 Queue로 구현되었고, 이도 결국 순서를 정해줄 수 있는 것이 아닌가? 라는 생각인데요. 그렇다면 Mutex와 Binary Semaphore는 같은게 아닐까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;5. Binary Semaphore 와 Mutex는 같은것일까?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;결론부터 말하자면 똑같지 않습니다. 몇 가지 차이점을 얘기해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. Mutex는 lock을 가진 자만 lock을 해제 할 수 있지만, Semaphore는 그렇지 않다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;앞의 Semaphore 예제에서 보면, Semaphore는 wait를 호출하는 존재와 signal을 호출하는 존재가 다를 수 있습니다. 하지만 Mutex는 lock을 가진 프로세스/스레드만이 lock을 해제할 수 있기 때문에, 누가 lock을 해제할지 어느정도 예상할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. Mutex는 Priority Inheritance 속성을 가지지만, Semaphore는 그렇지 않다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;우선, &lt;b&gt;Priority Inheritance&lt;/b&gt;를 간단하게 설명해보겠습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여러 프로세스/스레드가 동시에 실행을 하게 되면 CPU에서 문맥교환이 발생해서 누구를 먼저 실행시킬지 정해야 하는데, 이를 스케줄링(Scheduling)이라 합니다. 이 스케줄링 방식에는 여러가지가 있는데, 그 중 하나가 프로세스/스레드 우선순위에 따라서 &lt;b&gt;우선순위가 높은&lt;/b&gt; 프로세스/스레드를 먼저 실행시키는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;간단한 기본 개념을 말씀드렸으니, 이 스케줄링 방식 이해를 위해 상황을 통해 설명해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2개의 프로세스 P1(High Priority), P2(Low Priority)가 있는데 P1의 우선순위가 더 높은 상황을 가정해 보겠습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;P2가 먼저 lock을 획득하여 임계 영역에서 작업하고 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;스케줄링이 진행되어서, P1이 lock을 획득하려 하고 있지만 P2가 점유하고 있어 작업을 진행하지 못합니다. (P1은 P2가 lock을 반환하기 전까지 작업을 할 수 없게 됨, P1이 P2에 의존함)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 때, P2는 우선순위가 낮은 작업이므로 이 스케줄링 방식에서 언제 작업이 끝날지 모름(임계 영역 점유가 길어질 수 있음)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;따라서, P1은 우선순위가 높음에도 불구하고 아무 작업도 못하고 기다리고 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span style=&quot;&quot;&gt;이러한 우선순위가 높은 작업이, 우선순위가 낮은 작업에 의해 실행되지 못하는 상황을 Mutex에서는 이렇게 해결합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;P1이 P2에 의존하고 있는 이 상황에서, P2의 우선순위를 P1만큼 올려버립니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;따라서, P2의 우선순위가 올라갔으므로 스케줄러가 P2를 실행시킵니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;&quot;&gt;이에 P2가 빠르게 임계 영역을 빠져나올 수 있게 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span style=&quot;&quot;&gt;이렇게 P2의 우선순위를 P1만큼 올려주는 이것을 &lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;Priority Inheritance&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;라고 부릅니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;Semaphore는 누가 lock을 해제할지(signal)알 수 없기 때문에 이러한 속성이 존재하지 않습니다. 따라서,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Mutual Exclusion(상호 배제)만 필요하다면 Mutex를, 작업 간의 실행 순서 동기화가 필요하다면 Semaphore가 권장됩니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;오늘 다뤘던 Spinlock, Mutex, Semaphore의 구체적인 동작 방식은 OS와 프로그래밍 언어에 따라 조금씩 다를 수 있습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>운영체제</category>
      <category>criticalsection</category>
      <category>RaceCondition</category>
      <category>Synchronization</category>
      <category>경쟁조건</category>
      <category>동기화</category>
      <category>임계영역</category>
      <author>s7won</author>
      <guid isPermaLink="true">https://s7won.tistory.com/12</guid>
      <comments>https://s7won.tistory.com/12#entry12comment</comments>
      <pubDate>Wed, 6 Sep 2023 00:31:56 +0900</pubDate>
    </item>
    <item>
      <title>컨텍스트 스위칭(Context Switching)</title>
      <link>https://s7won.tistory.com/11</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 컨텍스트 스위칭(Context Switching)&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컨텍스트 스위칭의 정의&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;지난 글에서 프로세스와 스레드를 얘기하면서 컨텍스트 스위칭(문맥 교환)에 대해서 짧게 얘기했었는데요, 오늘은 이 컨텍스트 스위칭에 대해서 자세히 알아보려 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;&quot;&gt;컨텍스트 스위칭이란, 'CPU/코어에서 실행 중이던 프로세스/스레드가 다른 프로세스/스레드로 교체되는 것'입니다.&lt;/span&gt;&lt;span style=&quot;&quot;&gt;그렇다면, 여기서 말하는 컨텍스트란 무엇일까요? 컨텍스트란 프로세스/스레드의 &lt;b&gt;상태&lt;/b&gt;를 의미합니다. 또 이 &lt;b&gt;상태&lt;/b&gt;라는 것은 CPU, 메모리에서의 상태를 의미합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컨텍스트 스위칭이 필요한 이유&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇다면 컨텍스트 스위칭이 왜 필요할까요? 여러 이유들이 있지만, 대표적으로&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여러 프로세스와 스레드들을 동시에 실행시키기 위해(그렇게 보이기 위해)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여러 프로세스와 스레드들이 공정하게 CPU 시간을 나눠 갖기 위해&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;높은 우선순위의 작업이 빠르게 처리될 수 있게&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;&quot;&gt;와 같은 이유가 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컨텍스트 스위칭은 언제 발생하는가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컨텍스트 스위칭은&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;주어진 Time Slice(Time Quantum)를 다 사용함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;I/O 작업을 해야 함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다른 리소스를 기다려야 함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;인터럽트(Interrupt)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 대표적인 트리거와 더불어 다른 여러 이유로도 발생될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjsUVo/btssVVplUNC/UgwYHzvEPFnC2zZ7Z6271K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjsUVo/btssVVplUNC/UgwYHzvEPFnC2zZ7Z6271K/img.png&quot; data-alt=&quot;멀티 태스킹에서의 컨텍스트 스위칭&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjsUVo/btssVVplUNC/UgwYHzvEPFnC2zZ7Z6271K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjsUVo%2FbtssVVplUNC%2FUgwYHzvEPFnC2zZ7Z6271K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;544&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;멀티 태스킹에서의 컨텍스트 스위칭&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 사진에서 Time Quantum동안 프로세스 1,2가 번갈아가면서 실행되고 있습니다. 매우 짧은 시간 동안 번갈아가면서 실행되면서 동시에 실행되는 것처럼 보이게 됩니다. 이렇게 프로세스가 바뀌는 것을 '컨텍스트 스위칭' 이라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컨텍스트 스위칭은 누구에 의해 실행되는가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇다면 컨텍스트 스위칭은 누구에 의해서 실행될까요? OS의&lt;b&gt; 커널(Kernel)&lt;/b&gt;에 의해서 수행이 되게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;커널이란, 운영체제에서도 핵심적인 기능을 담당하는 존재인데요 짧게 설명하면 각종 리소스를 관리/감독하는 역할을 하는 존재라고 생각하시면 되겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 컨텍스트 스위칭이 일어나는 과정&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;&quot;&gt;컨텍스트 스위칭이 구체적으로 일어나는 과정은, &lt;b&gt;다른 프로세스끼리의 스위칭&lt;/b&gt;(Process Context Switching)인지 &lt;b&gt;같은 프로세스의 스레드들끼리의 스위칭&lt;/b&gt;(Thread Context Switching)인지에 따라 다릅니다. 이 두 가지의 공통점과 차이점을 바탕으로 설명해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;둘의 공통점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 커널 모드에서 실행된다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;두 스위칭은 모두 &lt;b&gt;커널 모드&lt;/b&gt;에서 실행됩니다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;커널 모드라는 말은, 어느 프로세스가 실행되다가 하드웨어와 밀접한 일들 혹은 컴퓨터에 있는 여러 리소스들을 다뤄야 하는 상황이 발생하면 프로세스가 직접 컴퓨터의 리소스에 접근하는 것이 아니라 운영체제를 통해 접근하게 되는데, 특히 운영체제에서도 커널을 통해서 접근하게 됩니다. 이때, 이 프로세스에서 커널로 통제권이 넘어가서 커널에 의해서 실행되는 것을 '커널 모드'라고 합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;이 컨텍스트 스위칭은 커널 모드에서 실행되기 때문에 커널로 통제권이 넘어갑니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. CPU의 레지스터 상태를 교체&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;두 스위칭은 또한 모두 CPU의 &lt;b&gt;레지스터&lt;/b&gt; 상태를 교체합니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;레지스터에 대해서 간단하게 설명하고 가자면, 레지스터란 CPU에서 각종 명령어들을 수행하기 위해 필요한 여러 데이터들을 저장하는 곳입니다. 많은 레지스터들이 있지만, 대표적으로 PC(Program Counter), Stack Pointer 등이 있습니다.&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어떤 프로세스가 CPU에서 실행되고 있을 동안에, 레지스터에 있는 여러 값들이 계속 바뀌면서 실행되고 있었을 겁니다. 이때, 다른 프로세스가 실행되면 실행되고 있던 프로세스의 레지스터 상태들을 어딘가에 저장을 하고 다른 프로세스가 실행되는 것입니다. 그래야, 다시 다른 프로세스가 실행됐을 때 그때의 상태 정보들을 알고 있어야 이어서 실행할 수 있기 때문이겠죠?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;둘의 차이점&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 다른 프로세스끼리의 스위칭인 프로세스 컨텍스트 스위칭은 가상 메모리 주소 관련 처리를 추가로 수행함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;지난 글에서 같은 프로세스에 속한 스레드들끼리는 &lt;b&gt;공유하는 메모리 지역&lt;/b&gt;이 있다고 했었습니다. 조금 설명을 덧붙이자면, 스레드들끼리는&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코드(Code) 영역 : 프로세스가 실행할 코드&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;데이터(Data) 영역 : 전역 변수가 정적 변수들이 저장되는 영역&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;힙(Heap) 영역 : 동적으로 할당된 메모리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;의 영역을 공유합니다. 따라서, 스레드들끼리의 컨텍스트 스위칭은 같은 프로세스에 속하기 때문에 스위칭이 일어나도 메모리와 관련해서는 챙겨줘야 할 부분이 없습니다. 하지만, 프로세스간의 컨텍스트 스위칭이 발생했을 때에는 메모리 주소 체계가 다르기 때문에 이 때는 메모리 주소 관련된 처리를 &lt;b&gt;추가로 수행&lt;/b&gt;해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇기 때문에, &lt;b&gt;MMU&lt;/b&gt;(Memory Management Unit)와 &lt;b&gt;TLB&lt;/b&gt;(Translation Lookaside Buffer)도 관리를 해줘야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;MMU는 가상 메모리와 물리 메모리 사이의 주소 변환을 담당합니다. 프로세스가 물리 메모리에서 할당되는 위치를 추상화하여 각 프로세스가 독립적인 주소 공간을 가지고 있는 것처럼 만듭니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;TLB는 MMU 내에 존재하는 캐시 메모리입니다. 가상 주소를 물리 주소로 변환하는 과정을 상대적으로 느릴 수 있기 때문에, 이러한 변환의 결과를 TLB에 저장해 두고 빠르게 사용하는 데에 목적이 있습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;스레드 간의 스위칭에서는 실행되고 있던 스레드의 상태를 저장하고, 새로 실행될 스레드의 상태를 로딩하는 것으로 해결이 되지만 프로세스간의 스위칭에서는, 위의 작업에 더불어서 MMU가 실행 될 작업의 메모리를 보도록 해야 하고 캐시 역할을 하는 TLB를 완전히 비워줘야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;요약하자면 '다른 프로세스끼리의 스위칭은 주소 체계와 관련된 일들도 추가적으로 처리를 해야 한다'가 되겠고요, 이러한 추가적인 작업이 필요하기 때문에 프로세스 컨텍스트 스위칭보다 스레드 컨텍스트 스위칭이 더 빠릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 컨텍스트 스위칭이 미치는 영향&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컨텍스트 스위칭이 발생하면서, &lt;b&gt;캐시 오염(Cache Pollution)&lt;/b&gt;이라는 간접적인 영향이 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;캐시란 데이터나 값을 임시 저장해 두는 임시 장소입니다. CPU가 자주 사용하는 것들에 대해서 매번 메모리에 접근하는 것이 아니라, 캐시에 저장해 두고 빠르게 데이터를 가져오기 위해 사용합니다.&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;span style=&quot;&quot;&gt;컨텍스트 스위칭이 일어나면, 또 사용할 것이라고 예상을 하고 캐시에 데이터나 값을 저장한 캐시가 의미가 없는 데이터가 되어버립니다. 컨텍스트 스위칭이 일어난 직후에 캐시에 가봤자, 이전에 프로세스에서 실행되었던 정보들을 저장해놓고 있을 가능성이 매우 높기 때문에, &lt;b&gt;내가 필요한 정보는 없을 가능성이 매우 높아지기 때문&lt;/b&gt;입니다. 이러하다는 것은 성능적으로 손해를 보는 부분이 발생한다는 거겠죠?&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;캐시 메모리는 메인 메모리에 비해 매우 작기 때문에 프로세스끼리 나눠서 쓰지 않고 다 같이 사용하는 공간이라고 합니다. 그래서 위와 같은 문제가 발생합니다!&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어플리케이션 관점에서는, 컨텍스트 스위칭은 순수한 &lt;b&gt;오버헤드(Overhead, 간접비용)&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, 내가 실행한 이 프로그램의 동작과는 아무 상관없는 순수한 CPU 작업을 필요로 하는 간접비용입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>운영체제</category>
      <category>context switching</category>
      <category>컨텍스트 스위칭</category>
      <author>s7won</author>
      <guid isPermaLink="true">https://s7won.tistory.com/11</guid>
      <comments>https://s7won.tistory.com/11#entry11comment</comments>
      <pubDate>Sat, 2 Sep 2023 21:56:10 +0900</pubDate>
    </item>
    <item>
      <title>프로세스, 스레드, 멀티태스킹, 멀티프로세싱....??</title>
      <link>https://s7won.tistory.com/10</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;0. 개요&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요. 오늘은 여러 프로그래밍이 동시에 실행될 수 있는 원리에 대해서 알아보고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, 프로세스, 스레드, 멀티태스킹, 멀티스레딩, 멀티프로세싱, 멀티프로그래밍...이라는 헷갈리는 것들에 대해서 알아볼게요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 사전 지식&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로그램(Program) : 컴퓨터가 실행할 수 있는 &lt;b&gt;명령어&lt;/b&gt;들의 집합&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로세스(Process) : 컴퓨터에서 &lt;b&gt;실행 중&lt;/b&gt;인 프로그램. 각각의 프로세스는 &lt;b&gt;독립된 메모리 공간&lt;/b&gt;을 할당 받음. 명령어들과 데이터를 가짐&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;CPU(central processing unit) : 명령어를 실행하는 연산 장치&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;메인 메모리(Main memory) : 프로세스가 CPU에서 실행되기 위해 대기하는 곳&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;IO(input / output) : 파일을 읽고 쓰거나 네트워크의 어딘가와 데이터를 주고받는 곳. 또한 입출력 장치와 데이터를 주거나 받는 것을 의미하기도 함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 단일 프로세스 시스템&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;한 번에 하나의 프로그램만 실행 되는 것을 의미&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;초창기의 시스템은 단일 프로세스 시스템이었습니다. 즉 한 번에 하나의 프로그램만 실행됩니다. 그래서, 다른 프로그램을 실행하려면 기존의 실행 중인 프로그램을 종료하고 실행시켰어야 합니다. 이러한 시스템의 단점은 CPU 사용률이 좋지 않다는 점이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUBU2W/btssNuS3s2I/btq3K6qIF9XaFPA2wOxk6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUBU2W/btssNuS3s2I/btq3K6qIF9XaFPA2wOxk6K/img.png&quot; data-alt=&quot;Single Process&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUBU2W/btssNuS3s2I/btq3K6qIF9XaFPA2wOxk6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUBU2W%2FbtssNuS3s2I%2Fbtq3K6qIF9XaFPA2wOxk6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1154&quot; height=&quot;515&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Single Process&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 사진과 같이, 프로세스가 실행되다가 I/O 작업이 발생하면 이 작업이 수행되는 동안 CPU는 놀고 있습니다. I/O 작업을 마친 뒤, 어느 순간 프로세스가 잠깐 실행되다가 또 CPU가 작업을 멈추고 I/O 작업이 발생하고.. 이러한 과정이 반복됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, I/O 작업이 일어나는 동안 CPU는 쉬게되고 이는 CPU 사용률을 좋지 못하게 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 멀티 프로그래밍&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;단일 프로세스의 CPU 사용률 문제를 해결하기 위해, 여러 개의 프로그램을 메모리에 올려놓고 동시에 실행시키는 아이디어가 나오게 됩니다. I/O 작업이 발생하면 다른 프로세스가 CPU에서 실행되게 하는 거에요. 이를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;멀티프로그래밍이라고&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LNt6z/btssGhnhszP/xqUH5zKZawKZBgM5eipsnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LNt6z/btssGhnhszP/xqUH5zKZawKZBgM5eipsnk/img.png&quot; data-alt=&quot;Multi Programming&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LNt6z/btssGhnhszP/xqUH5zKZawKZBgM5eipsnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLNt6z%2FbtssGhnhszP%2FxqUH5zKZawKZBgM5eipsnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1124&quot; height=&quot;533&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Multi Programming&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 사진을 설명해 보겠습니다. 먼저, P1이 실행됩니다. 그러다 I/O 작업이 발생하여 CPU에서의 P1 작업이 종료되고 P2가 실행이 됩니다. P2도 I/O 작업이 발생하였지만 P1의 I/O 작업이 끝나지 않아서 CPU는 기다리고 있습니다. 그 뒤에, P1의 I/O가 작업이 종료되어서 P1이 다시 실행됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 멀티프로그래밍 방식은 CPU 사용률을 극대화하기 위해 고안되었고, 어느 정도 단일 프로세스 방식을 보완하였지만 단점이 존재합니다. CPU 사용 시간이 길어지면, 다른 프로세스는 &lt;b&gt;계속해서 대기해야&lt;/b&gt; 한다는 점이에요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 멀티 태스킹&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티프로그래밍의 단점을 보완하기 위해서, 프로세스는 한 번 CPU를 사용할 때, 아주 짧은 시간(Quantum)만 CPU에서 실행되도록 하자는 &lt;b&gt;멀티 태스킹&lt;/b&gt;이라는 개념이 등장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rdzEt/btssMQPBSyy/2sry6pW2fJMePUSqcialSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rdzEt/btssMQPBSyy/2sry6pW2fJMePUSqcialSk/img.png&quot; data-alt=&quot;Multi Tasking&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rdzEt/btssMQPBSyy/2sry6pW2fJMePUSqcialSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrdzEt%2FbtssMQPBSyy%2F2sry6pW2fJMePUSqcialSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1164&quot; height=&quot;533&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Multi Tasking&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 사진처럼, P1과 P2가 번갈아가면서 짧은 시간만 CPU를 점유하는 방식입니다. 이렇게 하여 여러 프로세스가 동시에 실행되는 것처럼 보이게 함, 즉 프로세스의 응답 시간을 최소화시키는데 목적이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만, 여전히 아쉬운 부분이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. 하나의 프로세스가 동시에 여러 작업을 수행하지는 못합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. 프로세스의 문맥 교환(Context Switching, CPU에서 실행되기 위해 어느 프로세스에서 다른 프로세스로 교체하는 작업) 은 무거운 작업입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3. 프로세스는 서로 다른 메모리 공간을 가지고 있으므로 프로세스 간의 데이터 공유가 까다롭습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;4. 하나의 CPU 성능을 발전시키는 것이 발열 등의 이슈로 어려워져서, 듀얼 코어라는 개념이 등장했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;4. 스레드&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이런 멀티 태스킹의 아쉬운 부분을 해결하기 위해서, &lt;b&gt;스레드&lt;/b&gt;(Thread)라는 개념이 등장합니다. 스레드는 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 말합니다. 이러한 스레드의 특징으로는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. 프로세스는 한 개 이상의 스레드를 가질 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. CPU에서 실행되는 단위입니다. (unit of execution)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3. 스레드들은 자신들이 속한 프로세스의 메모리 영역을 공유합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;4. 따라서, 같은 프로세스의 스레드들끼리 문맥 교환(Context Switching)은 가볍고 데이터 공유가 쉽습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1145&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxCt6d/btssIntHkYH/S0m2LYanwUvDIIkffQ3ufk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxCt6d/btssIntHkYH/S0m2LYanwUvDIIkffQ3ufk/img.png&quot; data-alt=&quot;https://slidetodoc.com/carnegie-mellon-threads-some-of-the-slides-are/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxCt6d/btssIntHkYH/S0m2LYanwUvDIIkffQ3ufk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxCt6d%2FbtssIntHkYH%2FS0m2LYanwUvDIIkffQ3ufk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1145&quot; height=&quot;384&quot; data-origin-width=&quot;1145&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://slidetodoc.com/carnegie-mellon-threads-some-of-the-slides-are/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;왼쪽이 Single Thread(혹은, Thread가 등장하기 전 process의 메모리 구조), 오른쪽이 Multi Thread일 때의 메모리 구조입니다. 사진은 각각의 프로세스가 할당받은 메모리 영역입니다. 각각 스택과 힙이 존재하는데, 멀티 스레드 구조에서 힙 지역은 공유하고 스택 지역은 각각 할당받는다는 점만 알고 넘어가겠습니다. 스택처럼 PC(Program Counter, 다음에 실행할 명령어의 주소)도 스레드 별로 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 사진에서 말씀드리고 싶은 부분은 '같은 프로세스에 속한 스레드들은 그 프로세스의 메모리 영역을 공유한다'와 그렇더라도 스레드 간의 고유한 영역도 있다입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;5. 멀티 스레딩&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/52hZg/btssN2hNIEO/IDaaIzFPa2Yip7UkrzbG4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/52hZg/btssN2hNIEO/IDaaIzFPa2Yip7UkrzbG4K/img.png&quot; data-alt=&quot;슬슬 그리기가 힘들다..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/52hZg/btssN2hNIEO/IDaaIzFPa2Yip7UkrzbG4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F52hZg%2FbtssN2hNIEO%2FIDaaIzFPa2Yip7UkrzbG4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;571&quot; height=&quot;549&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;549&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;슬슬 그리기가 힘들다..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 스레드 2개를 가진 프로세스가, 2개의 코어를 가진 CPU에서 실행되면 각각 실행되게 됩니다. 이렇게 병렬적으로 실행되는 구조를 &lt;b&gt;멀티스레딩(MultiThreading)이라고&lt;/b&gt; 하며, 이는 하나의 프로세스가 동시에 여러 작업을 실행하는데 목적을 두고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이런 멀티스레딩이 등장하면서, 위에서 언급한 &lt;b&gt;멀티태스킹&lt;/b&gt;의 개념도 '여러 프로세스와 여러 스레드가 아주 짧게 쪼개진 CPU time을 나눠 갖는 것'으로 확장되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또, 두 개 이상의 프로세서나 코어를 활용하는 시스템을 &lt;b&gt;멀티프로세싱(MultiProcessing)이라고&lt;/b&gt; 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;6. 예제&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예제 1) 싱글코어 CPU에 싱글스레드 프로세스 2개&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;571&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBPOZ9/btssBYuYGth/l7KfqrlGF96hTVEP06eC4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBPOZ9/btssBYuYGth/l7KfqrlGF96hTVEP06eC4k/img.png&quot; data-alt=&quot;싱글코어 CPU에 싱글스레드 프로세스 2개&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBPOZ9/btssBYuYGth/l7KfqrlGF96hTVEP06eC4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBPOZ9%2FbtssBYuYGth%2Fl7KfqrlGF96hTVEP06eC4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;306&quot; height=&quot;253&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;571&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;싱글코어 CPU에 싱글스레드 프로세스 2개&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 프로세스들이 번갈아 가면서 작업을 할 것이므로 멀티태스킹이지만, 싱글 스레드에 싱글 코어 CPU이므로 멀티스레딩과 멀티 프로세싱은 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;멀티태스킹 : O&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;멀티스레딩:&amp;nbsp; X&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;멀티프로세싱 : X&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예제 2) 싱글코어 CPU에 듀얼스레드 프로세스 1개&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;455&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fg3C3/btssHfCEz0h/0TM0SbkGcRBFD1N0IPLk4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fg3C3/btssHfCEz0h/0TM0SbkGcRBFD1N0IPLk4K/img.png&quot; data-alt=&quot;싱글코어 CPU에 듀얼스레드 프로세스 1개&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fg3C3/btssHfCEz0h/0TM0SbkGcRBFD1N0IPLk4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFg3C3%2FbtssHfCEz0h%2F0TM0SbkGcRBFD1N0IPLk4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;306&quot; height=&quot;259&quot; data-origin-width=&quot;455&quot; data-origin-height=&quot;385&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;싱글코어 CPU에 듀얼스레드 프로세스 1개&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;CPU 하나를 가지고 스레드들이 경합을 해야 하므로 멀티태스킹이고, 듀얼스레드이기 때문에 멀티스레드도 맞습니다. 하지만 싱글코어 CPU이므로 멀티프로세싱은 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티태스킹 : O&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티스레딩 : O&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티프로세싱 : X&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;예제 3) 듀얼코어 CPU에 싱글스레드 프로세스 2개&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/24aOR/btssNmgwY9q/e96H5t1E9Rtl68RnByKktk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/24aOR/btssNmgwY9q/e96H5t1E9Rtl68RnByKktk/img.png&quot; data-alt=&quot;듀얼코어 CPU에 싱글스레드 프로세스 2개&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/24aOR/btssNmgwY9q/e96H5t1E9Rtl68RnByKktk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F24aOR%2FbtssNmgwY9q%2Fe96H5t1E9Rtl68RnByKktk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;306&quot; height=&quot;211&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;듀얼코어 CPU에 싱글스레드 프로세스 2개&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코어를 가지고 경합하지 않으므로 멀티태스킹은 아닙니다. 싱글스레드 프로세스이기 때문에 멀티스레딩도 아닙니다. 단, 코어가 2개이기 때문에 멀티프로세싱은 맞습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티태스킹 : X&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티스레딩 : X&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티프로세싱 : O&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;예제 4) 듀얼코어 CPU에 듀얼스레드 프로세스 1개&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwhmzO/btssHANthvL/AQWwkHnG15ihkelwr6sfrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwhmzO/btssHANthvL/AQWwkHnG15ihkelwr6sfrK/img.png&quot; data-alt=&quot;듀얼코어 CPU에 듀얼-스레드 프로세스 1개&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwhmzO/btssHANthvL/AQWwkHnG15ihkelwr6sfrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwhmzO%2FbtssHANthvL%2FAQWwkHnG15ihkelwr6sfrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;306&quot; height=&quot;198&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;듀얼코어 CPU에 듀얼-스레드 프로세스 1개&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코어별 경합이 없기 때문에 멀티태스킹은 아닙니다. 듀얼스레드 프로세스이기 때문에 멀티스레딩은 맞습니다. 또한, 코어도 2개이기 때문에 멀티프로세싱입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티태스킹 : X&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티스레딩 : O&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티프로세싱 : O&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;예제 5) 듀얼 코어 CPU에 듀얼스레드 프로세스 2개&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daeHkt/btssMORPEgb/s0CF50imkYDpYuyr5TDO21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daeHkt/btssMORPEgb/s0CF50imkYDpYuyr5TDO21/img.png&quot; data-alt=&quot;듀얼 코어 CPU에 듀얼스레드 프로세스 2개&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daeHkt/btssMORPEgb/s0CF50imkYDpYuyr5TDO21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaeHkt%2FbtssMORPEgb%2Fs0CF50imkYDpYuyr5TDO21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;306&quot; height=&quot;361&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;듀얼 코어 CPU에 듀얼스레드 프로세스 2개&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;상황 1. 위 사진에서 위쪽 프로세스의 스레드들은 위의 코어에, 아래쪽 프로세스의 스레드들은 아래의 코어로 몰린 상황&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코어 간 스레드들이 경합하므로 멀티태스킹입니다. 또한 듀얼스레드 프로세스들이기 때문에 멀티스레딩입니다. 코어의 수도 2개이므로 멀티프로세싱입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티태스킹 : O&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티스레딩 : O&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티프로세싱 : O&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;상황 2. 프로세스의 각각의 스레드들이 다른 코어에 교차하듯이 배치된 상황&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코어간 스레드들이 경합하므로 멀티태스킹입니다. 듀얼스레드 프로세스들이기 때문에 멀티스레딩입니다. 코어의 수도 2개이므로 멀티프로세싱입니다. 이러한 상황이 &lt;b&gt;오늘날의 컴퓨터랑 가장 비슷한 예제&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티태스킹 : O&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티스레딩 : O&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티프로세싱 : O&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;참고자료 : https://www.youtube.com/watch?v=QmtYKZC0lMU&lt;/span&gt;&lt;/p&gt;</description>
      <category>운영체제</category>
      <category>멀티태스킹</category>
      <category>멀티프로그래밍</category>
      <category>멀티프로세싱</category>
      <category>스레드</category>
      <category>프로세스</category>
      <author>s7won</author>
      <guid isPermaLink="true">https://s7won.tistory.com/10</guid>
      <comments>https://s7won.tistory.com/10#entry10comment</comments>
      <pubDate>Thu, 31 Aug 2023 23:12:51 +0900</pubDate>
    </item>
    <item>
      <title>트랜잭션(Transaction)</title>
      <link>https://s7won.tistory.com/9</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 트랜잭션의 예시&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;본격적으로 트랜잭션에 대해 알아보기 전에, 흔한 예시를 통해 이해를 쉽게 하고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;&quot;&gt;우선, A가 B에게 100만 원을 송금하는 과정에 대해서 생각해 보겠습니다.&lt;/span&gt;&lt;span style=&quot;&quot;&gt;A-&amp;gt;B로 100만 원을 전송했다는 이야기는, 결국 A는 -1000000원, B는 +1000000원이 되었다는 이야기죠?&lt;/span&gt;&lt;span style=&quot;&quot;&gt;이를 SQL로 작성해 보면 아래와 같이 작성할 수 있겠네요.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693226158136&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UPDATE account SET balance = balance - 1000000 WHERE id = 'A';
UPDATE account SET balance = balance + 1000000 WHERE id = 'B';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기서 중요한 것은 '송금'이라는 작업이 성공하려면, 이 두 쿼리가 '&lt;b&gt;모두&lt;/b&gt;' 실행이 되어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약, 1번째 줄의 쿼리만 실행되고 2번째 줄의 쿼리는 실패한다면, A의 100만 원만 사라진 게 되는 거겠죠? 반대의 경우도 이상합니다. 따라서, 결국 이 송금은 둘 다 정상 처리돼야만 성공하는 '&lt;b&gt;단일 작업'입니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;SQL이 모두 성공을 해야만 정상적인 작업, 이러한 작업을 DB에서는 &lt;b&gt;Transaction&lt;/b&gt;이라 부릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 트랜잭션의 정의&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;단일한 논리적인 작업 단위입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;논리적인 이유로 여러 SQL문들을 단일 작업으로 묶어서 나눠질 수 없게 만든 것이 Transaction입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Transaction의 SQL문들 중에 일부만 성공해서 DB에 반영되는 일은 일어나지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. MySQL에서의 사용법&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1693226553112&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 트랜잭션을 시작한다
START TRANSACTION;

# 송금 작업(A와 B의 기존 잔고는 200만원 이라고 가정하겠습니다)
UPDATE account SET balance = balance - 1000000 WHERE id = 'A';
UPDATE account SET balance = balance + 1000000 WHERE id = 'B';

# 커밋
# 지금까지 작업한 내용을 DB에 영구적으로 저장한다는 말입니다.
# 또한, Transaction을 종료한다는 뜻도 가지고 있습니다.
COMMIT;

# 추가로, A가 B에게 50만원을 더 송금하는 과정을 진행해 보겠습니다.
START TRANSACTION;

# A의 계좌에서 50만원 차감
UPDATE account SET balance = balance - 500000 WHERE id = 'A';

# A의 계좌를 확인해 보면 총 1500000이 지출되었으므로 500000만원만 남았다고 가정하겠습니다.
SELECT * FROM account // (A -&amp;gt; 500000)

# 롤백
# 지금까지 작업들을 모두 취소하고, Transaction 이전 상태로 되돌린다
# 또한, Transaction을 종료한다는 뜻도 가지고 있습니다.
ROLLBACK;

# 이제, 다시 계좌를 조회해보면 1000000으로 원상복구가 되어있습니다.
SELECT * FROM account // (A -&amp;gt; 1000000)

---

# AUTO COMMIT
# 각각의 SQL문을 자동으로 Transaction 처리 해주는 개념입니다.
# SQL문이 성공적으로 실행하면 자동으로 COMMIT합니다.
# 실행 중에 문제가 있었다면 알아서 ROLLBACK합니다.
# MySQL 에서는 기본값으로 AUTOCOMMIT이 Enabled 되어있습니다.
# 다른 DBMS도 물론 대부분 같은 기능을 지원합니다.

SELECT @@AUTOCOMMIT; // 1 -&amp;gt; True

# AUTOCOMMIT이 활성화된 상태이기 때문에 INSERT문이 실행되면 자동으로 COMMIT하면서 영구저장 됩니다.
INSERT INTO account VALUES ('A', 1000000);

# 이번엔, AUTOCOMMIT을 비활성화 해보겠습니다.
SET AUTOCOMMIT=0;

# 앞으로는 COMMIT이 자동으로 수행되지 않으므로, 특정 작업을 확정하려면 COMMIT, 취소하려면 ROLLBACK을 이용할 수 있습니다.
DELETE FROM account WHERE id = 'A';

ROLLBACK;

# 트랜잭션 시작과 함께, AUTOCOMMIT은 OFF됩니다. COMMIT/ROLLBACK 상태와 함께 Transction이 종료되면
# 원래 설정한 AUTOCOMMIT 상태로 돌아갑니다.
START TRANSACTION;
ROLLBACK;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위에서 코드로 설명해 보았는데, 짧게 다시 Transaction의 사용 패턴에 대해서 말씀드리자면,&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Transaction을 시작한다(AUTOCOMMIT은 비활성화된다)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;SQL작업을 포함한 로직을 수행한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;일련의 작업이 문제없이 동작했다면 Transaction을 COMMIT 하고 종료한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;문제가 발생했다면, Transaction을 ROLLBACK 하여 되돌린다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3 또는 4의 작업이 끝났다면, AUTOCOMMIT의 설정이 돌아온다&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. ACID&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ACID는 트랜잭션의 핵심 속성입니다. 각각 Atomicity, Consistency, Isolation, Durability의 약자인데요, 하나씩 알아봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;&quot;&gt;Atomicity (원자성)&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;&quot;&gt;아까의 송금 작업에서, A의 계좌의 돈이 빠져나가고, B의 계좌에 돈이 들어와야 되는 작업이 모두 성공해야 의미가 있는 작업이라고 말씀드렸는데, 이런 특성을 원자성이라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ALL or NOTHING (모두 성공하거나, 모두 실패하거나)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Transaction은 논리적으로 쪼개질 수 없는 작업 단위이기 때문에 내부의 SQL문들이 모두 성공해야 합니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;중간에 SQL문이 실패하면 지금까지의 작업을 모두 취소하여 아무 일도 없었던 것처럼 ROLLBACK 합니다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 부분에서 COMMIT, ROLLABCK이 일어났을 때의 변화를 담당하는 것은 &lt;b&gt;DBMS&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;개발자가 담당해야 하는 부분은 언제 COMMIT 하거나, ROLLBACK 할지를 생각해야 합니다. 즉, 트랜잭션의 단위를 어떻게 설정할지 또는 어떤 문제가 발생했을 때 ROLLBACK 할지를 고려해야 한다는 말입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Consistency (일관성)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;A가 B에게 송금을 성공적으로 한 뒤에, 추가이체 하는 상황을 생각해 보겠습니다. 이때 SQL문에서 A의 계좌 이상의 금액을 B에게 송금한다면 어떻게 될까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693228160129&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE account (
	...
    balance INT,
    check (balance &amp;gt;= 0)
)

# A의 잔고보다 많은 금액을 이체함
UPDATE account SET balance = balance - 100000000 WHERE id = 'A';
UPDATE account SET balance = balance + 100000000 WHERE id = 'B';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;첫 번째 쿼리부터 바로 실패해 버릴 것입니다. 개발자가 설정한 제약조건을 지키지 못하였기 때문입니다. 즉, 일관성을 깨뜨렸어요. 이런 상황에서, 두 번째 쿼리를 실행하는 게 의미가 있을까요? 다시 말해서, 이런 상황에서 트랜잭션을 이어가는 게 의미가 있을까요? 없습니다. 바로 ROLLBACK을 해줘야겠죠?&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;&quot;&gt;이렇게, DB의 상태를 일관성 있게 유지시켜 주는 것이 Consistency가 의미하는 바입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Transaction은 DB 상태를 Consistent 상태에서 또 다른 Consistent 상태로 바꿔줘야 합니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Constraints, trigger 등을 통해 DB에 정의된 Rules를 Transaction이 위반했다면 ROLLBACK 해야 합니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Transaction이 DB에 정의된 Rule을 위반했는지는 &lt;b&gt;DBMS&lt;/b&gt;가 Commit 전에 확인하고 알려줍니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그 외에, Application 관점에서 Transaction이 Consistent 하게 동작하는지는 &lt;b&gt;개발자&lt;/b&gt;가 챙겨야 합니다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Isolation (격리성, 고립성)&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;&quot;&gt;A가 B에게 100만 원을 이체할 때, 동시에 B도 본인의 계좌에 100만원을 입금하는 상황을 생각해 보겠습니다. &lt;/span&gt;&lt;span style=&quot;&quot;&gt;(아래는 SQL문이 아닌 이해를 위해 작성한 부분입니다.)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693229054627&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 자세히 살펴보겠습니다
# A와 B가 모두 200만원을 가지고 있다고 해보겠습니다.
# A가 송금하는 과정입니다. (1번 Transaction 시작)

# 우선 A의 계좌를 조회하고
A.read(balance) =&amp;gt; 2000000
# 100만원을 송금하고, 이 결과를 작성하겠습니다.
A.write(balance = 1000000) // 100만원 송금하여 100만원 남음

# 이제, B에 계좌에 100만원을 더해주기 위해 조회가 일어납니다.
B.read(balance) =&amp;gt; 2000000

# 이 때, B도 본인의 계좌로 50만원을 입금합니다. (2번 Transaction 시작)
B.read(balance) =&amp;gt; 2000000 // 아직 A -&amp;gt; B 송금한 내역이 반영이 되지 않았습니다.
B.write(balance = 2500000) // B가 입금한 내역만 반영이 된 상태입니다.
# 작업이 끝났으므로, 2번 Transaction은 종료되었습니다.

# 이제, 1번 Transaction의 작업인 100만원 입금을 해 주어야 하는데,
# 2번 Transaction이 수행되기 전에, 이미 B.read(balance) =&amp;gt; 2000000 라는 값을 읽어왔기 때문에,
# 여기에 100만원을 더해줍니다.
B.write(balance = 3000000)

# B가 입금한 50만원은 사라졌습니다. 즉,
# 여러 Transaction이 함께 동작하면서 이상한 결과가 발생했습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1883&quot; data-origin-height=&quot;878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SUtia/btssvxo5aCv/3a0nzBIZ3XChxIzfJ0dtUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SUtia/btssvxo5aCv/3a0nzBIZ3XChxIzfJ0dtUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SUtia/btssvxo5aCv/3a0nzBIZ3XChxIzfJ0dtUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSUtia%2Fbtssvxo5aCv%2F3a0nzBIZ3XChxIzfJ0dtUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1883&quot; height=&quot;878&quot; data-origin-width=&quot;1883&quot; data-origin-height=&quot;878&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 주목할 부분은 결국 '&lt;b&gt;여러 Transaction이 함께 동작하면서 이상한 결과가 발생한 점'입니다.&lt;/b&gt; 이를 위해 필요한 성질이 Isolation입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여러 Transaction들이 동시에 실행될 때도 혼자 실행되는 것처럼 동작하게 만듭니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Isolation을 엄격하게 구현하면 DB의 Performance가 감소하므로 DBMS는 여러 종류의 Isolation level을 제공합니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;level을 높이면, 엄격하게 격리를 시켜 다른 트랜잭션의 영향을 받는 경우를 줄일 수 있지만 성능의 저하가 존재합니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;level을 낮추면, 여러 트랜잭션을 실행시킬 수 있는 동시성이 좋아져 Performance가 좋아지지만 다른 트랜잭션으로부터 영향을 받을 가능성이 커집니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;개발자&lt;/b&gt;는 Isolation level 중에 어떤 level로 Transaction을 동작시킬지 설정할 수 있습니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Concurrency Control의 주된 목표가 Isolation입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;Durability(영존성)&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1693229606028&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UPDATE account SET balance = balance - 1000000 WHERE id = 'A';
UPDATE account SET balance = balance + 1000000 WHERE id = 'B';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;처음부터 봤던 이 송금작업이 정상적으로 수행되어 COMMIT이 일어난 뒤에는, DB에 영구적으로 저장됩니다. 이를 Durability라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;COMMIT 된 Transaction은 DB에 영구적으로 저장합니다&lt;/li&gt;
&lt;li&gt;DB System에 문제가 생겨도 COMMIT된 Transaction은 DB에 남아 있습니다&lt;/li&gt;
&lt;li&gt;'영구적으로 저장한다'라는 의미는 일반적으로 비 휘발성 메모리(SSD, HDD,..)에 저장함을 의미합니다&lt;/li&gt;
&lt;li&gt;기본적으로 Transaction의 Durability는 DBMS가 보장합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;참고자료&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;쉬운코드&quot; href=&quot;https://www.youtube.com/watch?v=sLJ8ypeHGlM&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.youtube.com/watch?v=sLJ8ypeHGlM&lt;/a&gt;&lt;/p&gt;</description>
      <category>DB</category>
      <category>acid</category>
      <category>mysql</category>
      <category>transaction</category>
      <category>트랜잭션</category>
      <author>s7won</author>
      <guid isPermaLink="true">https://s7won.tistory.com/9</guid>
      <comments>https://s7won.tistory.com/9#entry9comment</comments>
      <pubDate>Mon, 28 Aug 2023 22:38:28 +0900</pubDate>
    </item>
    <item>
      <title>[SPRING + REDIS] 유저 정보 캐싱으로 성능 개선해보기</title>
      <link>https://s7won.tistory.com/8</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;0. 개요&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요. 지난 글에서 발생했던 문제에 대해서 얘기해보&lt;span style=&quot;color: #333333;&quot;&gt;려&lt;/span&gt;고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bme3GA/btsr0rCAQ2l/6a13sfUNk30xni9RqnxOdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bme3GA/btsr0rCAQ2l/6a13sfUNk30xni9RqnxOdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bme3GA/btsr0rCAQ2l/6a13sfUNk30xni9RqnxOdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbme3GA%2Fbtsr0rCAQ2l%2F6a13sfUNk30xni9RqnxOdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;878&quot; height=&quot;467&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;467&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;저번 글에 마지막 글에 보여드렸던 사진입니다. Spring Security를 적용해 본 분이라면 정말 익숙한 메소드죠? 메소드 명 그대로, 유저 정보를 로드하는 메소드입니다. 문제는 뒤에 있는 '병원'이 어떠한 요청을 할 때마다 2번의 조회쿼리가 발생한다는 거에요. 사실 2번의 단순한 조회쿼리가 성능에 큰 영향을 미칠까? 라고 한다면 크리티컬 한 부분은 아니라고 대답하겠지만, 사용자가 많은 서비스라면 이러한 부분에서도 성능 개선이 가능하다고 생각했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그럼, 실제로 모든 요청에 2번 조회쿼리가 나가는지 확인해볼까요? 병원으로 아무 요청이나 보내보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSKw8x/btsrUpFIyiy/Gk8xFM81czkBPEK5mwt5u1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSKw8x/btsrUpFIyiy/Gk8xFM81czkBPEK5mwt5u1/img.png&quot; data-alt=&quot;네 2번 나가네요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSKw8x/btsrUpFIyiy/Gk8xFM81czkBPEK5mwt5u1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSKw8x%2FbtsrUpFIyiy%2FGk8xFM81czkBPEK5mwt5u1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1242&quot; height=&quot;664&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;네 2번 나가네요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;제가 위에서 구현한 코드 그대로 회원을 먼저 조회하고, 병원을 조회하는 모습입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이를 어떻게 개선할 수 있을까 생각해보았는데 , 캐싱을 사용할 수 있겠다고 생각했어요. 회원들의 정보는 자주 바뀌지 않을 것이라 생각했고, 특히 병원에 대한 정보는 더더욱 그랬습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;무엇보다도, redis를 단순히 RefreshToken 저장소로만 쓰기는 너무 아깝잖아요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. Redis?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그럼, Redis가 뭔지부터 간단히 알아보고 넘어가겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Redis는 본래 &quot;Remote Dictionary Server&quot;의 약자로, &lt;b&gt;메모리 기반의 데이터 저장소&lt;/b&gt;입니다. 디스크에 저장하는 전통적인 데이터베이스와 달리, Redis는 데이터를 RAM에 저장하기 때문에 빠른 속도를 자랑해요. 주로 캐시로 사용되지만, 키-값 쌍뿐만 아니라 리스트, 셋, 해시 등 다양한 데이터 구조를 지원해 다양한 활용이 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;간단하게 말하자면, Redis는 빠르고 유연하게 데이터를 다룰 수 있는 메모리 저장소입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;설치와 관련된 부분에서는 잘 설명된 글들이 많으니 넘어가겠습니다. 다만, Window를 사용하시는 분이라면 현재의 최신 버전이 아닌 3.x 버전을 받아야 해요. '인파' 님의 티스토리에 너무 잘 설명되어 있으니 참고하시면 좋을 것 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;설치를 완료하셨다면요, 따로 설정을 변경하지 않으셨다면 C:\Program Files\Redis 주소에 redis-cli로 cmd창 실행이 가능해요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cR5D1l/btsrNmJ0G7O/OdfZvckcF11jKTo4kjr82K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cR5D1l/btsrNmJ0G7O/OdfZvckcF11jKTo4kjr82K/img.png&quot; data-alt=&quot;핑퐁&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cR5D1l/btsrNmJ0G7O/OdfZvckcF11jKTo4kjr82K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcR5D1l%2FbtsrNmJ0G7O%2FOdfZvckcF11jKTo4kjr82K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;216&quot; height=&quot;88&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;핑퐁&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;실행하셔서요, 위와 같이 동작한다면 성공적으로 설치가 되셨습니다. 위의 사진을 보니 Redis는 기본 포트로 6379를 사용한다고 짐작할 수 있겠네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. Redis 사용 설정하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 스프링에서 사용을 해봐야겠죠? 우선 Build.gradle에 redis관련 의존성을 불러오겠다고 설정해볼게요.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;85&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ASk1T/btsrS6mfuRl/McRSCd6gRs8Mo2bMzxLpv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ASk1T/btsrS6mfuRl/McRSCd6gRs8Mo2bMzxLpv0/img.png&quot; data-alt=&quot;요거 한줄 추가해주시면 됩니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ASk1T/btsrS6mfuRl/McRSCd6gRs8Mo2bMzxLpv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FASk1T%2FbtsrS6mfuRl%2FMcRSCd6gRs8Mo2bMzxLpv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;572&quot; height=&quot;85&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;85&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;요거 한줄 추가해주시면 됩니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1692715837649&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-data-redis'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제, Spring 설정파일에서 redis에 대한 정보를 입력해 볼게요. 우선 설정파일부터 작성해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;215&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wTkwE/btsrIaiFIwm/mLTNG2lrAmiYoRJM4nSlpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wTkwE/btsrIaiFIwm/mLTNG2lrAmiYoRJM4nSlpk/img.png&quot; data-alt=&quot;yml 파일에 일부에요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wTkwE/btsrIaiFIwm/mLTNG2lrAmiYoRJM4nSlpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwTkwE%2FbtsrIaiFIwm%2FmLTNG2lrAmiYoRJM4nSlpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;283&quot; height=&quot;215&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;215&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;yml 파일에 일부에요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;제 yml파일 중 일부인데요, 따로 비밀번호를 걸지 않으셨다면 password부분은 생략하셔도 되겠습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;time-to-live는&amp;nbsp;말&amp;nbsp;그대로&amp;nbsp;data의&amp;nbsp;expired&amp;nbsp;시간을&amp;nbsp;정해주는&amp;nbsp;건데요,&amp;nbsp;초&amp;nbsp;단위라는&amp;nbsp;것을&amp;nbsp;인지하시고&amp;nbsp;본인&amp;nbsp;프로젝트에&amp;nbsp;맞게&amp;nbsp;적용하시면&amp;nbsp;되겠습니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 Redis 관련 설정파일만 코드로 작성해 주면 사용을 위한 준비가 끝날 것 같습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;아래부터는 혹시 복사하실 분들도 있을 것 같아서 코드블록으로 작성할게요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;우선,&amp;nbsp;RedisConfig라는&amp;nbsp;이름의&amp;nbsp;파일을&amp;nbsp;하나&amp;nbsp;만들고&amp;nbsp;아래와&amp;nbsp;같이&amp;nbsp;작성하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692715992452&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@EnableRedisRepositories
@Configuration
public class RedisConfig {

    @Value(&quot;${spring.cache.redis.host}&quot;)
    private String host;

    @Value(&quot;${spring.cache.redis.port}&quot;)
    private int port;

    @Value(&quot;${spring.cache.redis.password}&quot;)
    private String password;

    @Bean
    public RedisConnectionFactory redisConnectionFactory(){
        RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration(host, port);
        standaloneConfiguration.setPassword(password); // 저는 비밀번호가 있기에 작성했지만, 없다면 생략하세요
        return new LettuceConnectionFactory(standaloneConfiguration);
    }

    @Bean
    public RedisTemplate&amp;lt;String,String&amp;gt; redisTemplate(){
        RedisTemplate&amp;lt;String, String&amp;gt; redisTemplate = new RedisTemplate&amp;lt;&amp;gt;();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer()); // 키-밸류 직렬화 방식은 프로젝트에 맞게 설정하세요
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위에서부터&amp;nbsp;간략하게&amp;nbsp;설명해&amp;nbsp;보겠습니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;@EnableRedisRepositories&lt;/b&gt;는&amp;nbsp;Spring&amp;nbsp;Data&amp;nbsp;Redis의&amp;nbsp;레포지토리를&amp;nbsp;활성화하는&amp;nbsp;역할을&amp;nbsp;합니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;RedisConnectionFactory&lt;/b&gt;는&amp;nbsp;Redis와의&amp;nbsp;연결을&amp;nbsp;관리하는&amp;nbsp;빈입니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;RedisStandaloneConfiguration&lt;/b&gt;는&amp;nbsp;RedisConnection을&amp;nbsp;설정하는&amp;nbsp;데&amp;nbsp;사용되는&amp;nbsp;클래스에요.&amp;nbsp;이를&amp;nbsp;통해&amp;nbsp;호스트,&amp;nbsp;포트,&amp;nbsp;패스워드&amp;nbsp;등&amp;nbsp;기본적인&amp;nbsp;설정을&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Spring에서&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;Redis의&amp;nbsp;Client는&amp;nbsp;크게&amp;nbsp;&amp;nbsp;Jedis,&amp;nbsp;Lettuce&amp;nbsp;2가지가&amp;nbsp;있는데요,&amp;nbsp;저희는&amp;nbsp;그중에서&amp;nbsp;Lettuce를&amp;nbsp;사용할게요.&amp;nbsp;Jedis보다&amp;nbsp;성능면에서&amp;nbsp;뛰어나서&amp;nbsp;Lettuce가&amp;nbsp;기본&amp;nbsp;설정이라고&amp;nbsp;합니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음으로,&amp;nbsp;&lt;b&gt;RedisTemplate&lt;/b&gt;은&amp;nbsp;Redis&amp;nbsp;연산을&amp;nbsp;위해서&amp;nbsp;필요한&amp;nbsp;빈인데요,&amp;nbsp;위에서&amp;nbsp;설정한&amp;nbsp;ConnectionFactory&amp;nbsp;빈을&amp;nbsp;주입받고,&amp;nbsp;직렬화&amp;nbsp;방식을&amp;nbsp;설정할&amp;nbsp;수&amp;nbsp;있습니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;저는&amp;nbsp;RefreshToken을&amp;nbsp;삽입하고&amp;nbsp;삭제할&amp;nbsp;때&amp;nbsp;단순한&amp;nbsp;String&amp;nbsp;키-밸류를&amp;nbsp;사용하므로&amp;nbsp;&lt;b&gt;StringRedisSerializer&lt;/b&gt;로&amp;nbsp;설정하였는데요,&amp;nbsp;이&amp;nbsp;부분도&amp;nbsp;프로젝트에&amp;nbsp;맞게&amp;nbsp;설정하시면&amp;nbsp;될&amp;nbsp;것&amp;nbsp;같습니다.&amp;nbsp;아래에&amp;nbsp;작성하겠지만,&amp;nbsp;이&amp;nbsp;글에서는&amp;nbsp;캐시를&amp;nbsp;위한&amp;nbsp;빈을&amp;nbsp;따로&amp;nbsp;설정할&amp;nbsp;것이므로&amp;nbsp;크게&amp;nbsp;중요한&amp;nbsp;부분은&amp;nbsp;아니에요. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;레디스&amp;nbsp;다운받고&amp;nbsp;간단한&amp;nbsp;설정&amp;nbsp;하는데도&amp;nbsp;벌써&amp;nbsp;지치는&amp;nbsp;느낌이네요.&amp;nbsp;만약&amp;nbsp;RefreshToken관련된&amp;nbsp;DB&amp;nbsp;연산만&amp;nbsp;하실&amp;nbsp;거라면&amp;nbsp;여기까지만&amp;nbsp;작성하시면&amp;nbsp;충분합니다.&amp;nbsp;하지만,&amp;nbsp;저희는&amp;nbsp;Cache를&amp;nbsp;사용해&amp;nbsp;볼&amp;nbsp;거&amp;nbsp;기&amp;nbsp;때문에&amp;nbsp;조금&amp;nbsp;더&amp;nbsp;작성해&amp;nbsp;볼게요.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692717196130&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    //타입 안전성 검증
    PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
        .builder()
        .allowIfSubType(Object.class)  // Object 클래스의 하위 타입을 허용
        .build();

    // 객체와 JSON 간의 변환을 관리
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new JavaTimeModule());  // Time API 지원
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);  // 알려지지 않은 프로퍼티(필드)가 있을 때 실패하지 않고 무시
    objectMapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL); // 기본 타이핑 활성화 및 다형성 타입 검증 설정
    objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);  // enum을 문자열로 쓰기
    objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);  // 문자열을 enum으로 읽기
    GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);  // 직렬화에 사용할 Serializer

    // 캐시의 기본 설정
    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                                                                             .disableCachingNullValues()  // null 값은 캐싱X
                                                                             .entryTtl(Duration.ofHours(1L))  // 캐시 유효 시간 설정(적절하게 바꿔서 사용하세요)
                                                                             .computePrefixWith(CacheKeyPrefix.simple())  // 캐시 키의 접두어를 간단하게 계산 EX) UserCacheStore::Key 의 형태로 저장
                                                                             .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))  // 키의 직렬화 방식 설정
                                                                             .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer));  // 값의 직렬화 방식 설정

    // 캐시 이름 설정 담아주기(적절하게 바꿔서 사용하세요)
    Map&amp;lt;String, RedisCacheConfiguration&amp;gt; redisCacheConfigurationMap = new HashMap&amp;lt;&amp;gt;();
    redisCacheConfigurationMap.put(&quot;UserCacheStore&quot;, redisCacheConfiguration);
    redisCacheConfigurationMap.put(&quot;HospitalInfo&quot;, redisCacheConfiguration);
    redisCacheConfigurationMap.put(&quot;HospitalHoursInfo&quot;, redisCacheConfiguration);
    redisCacheConfigurationMap.put(&quot;DoctorInfo&quot;, redisCacheConfiguration);

    // RedisCacheManager 리턴
    return RedisCacheManager.RedisCacheManagerBuilder
                            .fromConnectionFactory(redisConnectionFactory)  // Redis 연결 설정
                            .cacheDefaults(redisCacheConfiguration)  // 기본 캐시 설정
                            .withInitialCacheConfigurations(redisCacheConfigurationMap)  // 초기 캐시 설정을 맵으로 전달
                            .build();  // RedisCacheManager 생성
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;글이&amp;nbsp;너무&amp;nbsp;길어져&amp;nbsp;지루하실&amp;nbsp;것&amp;nbsp;같아&amp;nbsp;주석으로&amp;nbsp;설명을&amp;nbsp;달아봤습니다.&amp;nbsp;하나만&amp;nbsp;짚고&amp;nbsp;넘어가자면 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;GenericJackson2JsonRedisSerializer&lt;/b&gt;인데요,&amp;nbsp;이&amp;nbsp;&lt;b&gt;Serializer&lt;/b&gt;는&amp;nbsp;모든&amp;nbsp;객체를&amp;nbsp;직렬화해&amp;nbsp;줘요.&amp;nbsp; &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;간단한&amp;nbsp;&lt;b&gt;StringRedisSerializer&lt;/b&gt;와는&amp;nbsp;다르게,&amp;nbsp;&lt;b&gt;GenericJackson2JsonRedisSerializer&lt;/b&gt;는&amp;nbsp;객체의&amp;nbsp;메타데이터도&amp;nbsp;함께&amp;nbsp;저장합니다.&amp;nbsp; &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이렇게&amp;nbsp;하면&amp;nbsp;역직렬화할&amp;nbsp;때&amp;nbsp;원래의&amp;nbsp;객체&amp;nbsp;타입을&amp;nbsp;더&amp;nbsp;정확하게&amp;nbsp;복원할&amp;nbsp;수&amp;nbsp;있어요. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기까지&amp;nbsp;정말&amp;nbsp;힘들게&amp;nbsp;왔는데요,&amp;nbsp;사용은&amp;nbsp;쉽게&amp;nbsp;하실&amp;nbsp;수&amp;nbsp;있어요.&amp;nbsp;그전에&amp;nbsp;정말&amp;nbsp;마지막으로!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AasdS/btsrVufPdP8/O9jD0Ffp76JnFtz2XK2thK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AasdS/btsrVufPdP8/O9jD0Ffp76JnFtz2XK2thK/img.png&quot; data-alt=&quot;마지막이에요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AasdS/btsrVufPdP8/O9jD0Ffp76JnFtz2XK2thK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAasdS%2FbtsrVufPdP8%2FO9jD0Ffp76JnFtz2XK2thK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;259&quot; height=&quot;112&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;마지막이에요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;스프링부트&amp;nbsp;실행부분에&amp;nbsp;&lt;b&gt;@EnableCaching&lt;/b&gt;을&amp;nbsp;붙여서,&amp;nbsp;캐싱을&amp;nbsp;사용한다고&amp;nbsp;말해줍시다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 캐싱해 보기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;자, 이제 캐싱을 사용해 보겠습니다. 사용하는 건 정말 간단해요.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3VsLR/btsrRS9G32M/q8PHT7ZN8yZX8JRKu1KCi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3VsLR/btsrRS9G32M/q8PHT7ZN8yZX8JRKu1KCi0/img.png&quot; data-alt=&quot;틀린 그림 찾기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3VsLR/btsrRS9G32M/q8PHT7ZN8yZX8JRKu1KCi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3VsLR%2FbtsrRS9G32M%2Fq8PHT7ZN8yZX8JRKu1KCi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;880&quot; height=&quot;493&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;틀린 그림 찾기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제, 캐싱을 적용할 메소드에 &lt;b&gt;@Cacheable&lt;/b&gt;만 붙여주면 끝이에요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;@Cacheable&lt;/b&gt;은 클래스에 사용할 수도 있지만, 주로 메소드에 적용되는 어노테이션으로, 메소드의 실행 결과를 캐시에 저장하는 역할을 합니다. 이후에 같은 입력값으로 해당 메소드를 호출하면 실제로 메소드를 실행하는 대신에 캐시에 저장된 결과를 반환해요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다만, &lt;b&gt;@Cacheable&lt;/b&gt;의 설정에서 key-value는 키-값 쌍으로 저장하겠다! 가 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;value는 Redis에서 사용하는 캐시 그룹 즉, 제가 아까 &lt;b&gt;RedisConfig&lt;/b&gt;에서 &lt;b&gt;redisCacheConfigurationMap&lt;/b&gt;에 넣어주었던 이름을 의미하구요, key는 캐시 그룹에서 개별 데이터를 어떻게 식별할지에 대한 설정입니다. 그러니, key는 고유한 값을 넣어줘야겠죠?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;레디스에 잘 저장이 되는지 볼까요? 요청을 보내게 되면요,&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;370&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpSYUm/btsrR25sfle/y4C7YQNAMqpxESuoHnhoqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpSYUm/btsrR25sfle/y4C7YQNAMqpxESuoHnhoqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpSYUm/btsrR25sfle/y4C7YQNAMqpxESuoHnhoqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpSYUm%2FbtsrR25sfle%2Fy4C7YQNAMqpxESuoHnhoqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;370&quot; height=&quot;92&quot; data-origin-width=&quot;370&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;제가 설정한 prefix, key, value대로 잘 저장이 되어있네요. 설정에 비해 사용은 쉬웠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;잘 저장이 되었다는 말은요, 캐시 히트가 발생했을 때?&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tjG5q/btsrYLuMSjk/iwhZ9tkYbfYURgPmKcRXL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tjG5q/btsrYLuMSjk/iwhZ9tkYbfYURgPmKcRXL0/img.png&quot; data-alt=&quot;조작한거 아닙니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tjG5q/btsrYLuMSjk/iwhZ9tkYbfYURgPmKcRXL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtjG5q%2FbtsrYLuMSjk%2FiwhZ9tkYbfYURgPmKcRXL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1266&quot; height=&quot;614&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;조작한거 아닙니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;회원 조회에 대한 쿼리는 발생하지 않았습니다. 훌륭하네요!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그런데, 회원이 정보를 수정하면 어떻게 될까요? DB에서 조회해 온 것이 아니라, Redis에 저장되어 있는 정보를 사용할 것이므로 큰 문제가 될거에요. 그래서, 정보 수정과 같은 이벤트가 발생했을 때는 캐시를 제거해줘야 합니다. 이 부분도 간단하게 적용해 볼게요.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692718770720&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @CacheEvict(value = &quot;UserCacheStore&quot;, key = &quot;#loginId&quot;)
    @Transactional
    public void modifyMember(String loginId, MemberInfoUpdateRequest memberInfoUpdateRequest, MultipartFile profileImg) {

        Member member = findByLoginId(loginId);
        member.update(memberInfoUpdateRequest);

        // 기존에 등록 된 사진이 없고 새로 등록 시킬 때
        if(Objects.isNull(member.getProfileImg()) &amp;amp;&amp;amp; Objects.nonNull(profileImg)) {
            String profileAddress = amazonS3Service.uploadFile(profileImg);
            member.updateFileAddress(profileAddress);
            return;
        }
        // 기존에 등록 된 사진이 있고 수정할 때
        if(Objects.nonNull(member.getProfileImg()) &amp;amp;&amp;amp; Objects.nonNull(profileImg)) {
            String profileAddress = member.getAddress();
            amazonS3Service.deleteFile(profileAddress);

            String newProfileAddress = amazonS3Service.uploadFile(profileImg);
            member.updateFileAddress(newProfileAddress);
        }

    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;제 코드의 회원 정보 수정 부분입니다. 로직적으로 수정한 부분은 없구요, &amp;nbsp;&lt;b&gt;@CacheEvict&lt;/b&gt;(value = &quot;UserCacheStore&quot;, key = &quot;#loginId&quot;)를 통해 어떤 캐시 그룹의, 키를 가진 값을 삭제할지를 알려주면 되겠습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;@CacheEvict&lt;/b&gt;도 클래스에 붙일 수 있지만, 주로 메소드에 적용되는 어노테이션으로 지정된 캐시의 항목을 제거하는 역할을 해요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. 마무리&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 글을 보시고 다른 부분에 대해서 캐싱을 해보시려고 하면, 수많은 직렬화와 역직렬화 문제에 당면하실 거예요. 그래서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;제가 다른 정보들을 캐싱하면서 부딪힌 문제와 해결법에 대해서 작성하려 했었는데, 생각보다 글이 너무 길어져서 다음 글에 이어서 작성해야 할 것 같습니다.&amp;nbsp;글이 길어지니 지루해지고 글도 중구난방이 된 것 같네요.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;글을 작성하다 보니 글 작성과 더불어서 실력적인 부분에서도 부족한 부분이 많이 드러나는 것 같습니다. 그래도 최대한 제가 아는 선에서 자세하게 쓰려고 노력했으니 이 글이 캐싱을 처음 시작해 보시려는 분들께 도움이 되었으면 좋겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>SPRING</category>
      <category>cache</category>
      <category>JWT</category>
      <category>REDIS</category>
      <category>spring</category>
      <category>Spring Security</category>
      <author>s7won</author>
      <guid isPermaLink="true">https://s7won.tistory.com/8</guid>
      <comments>https://s7won.tistory.com/8#entry8comment</comments>
      <pubDate>Wed, 23 Aug 2023 00:59:57 +0900</pubDate>
    </item>
    <item>
      <title>[SPRING + JPA + SECURITY] 너무 다른 2종류의 회원을 어떻게 설계할까?</title>
      <link>https://s7won.tistory.com/7</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;0. 개요&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요! 오늘은 SpringSecurity, JPA 환경에서 서로 다른 2종류의 회원을 설계하면서 느꼈던 고민들에 대해서 작성해보려고 합니다. 이 글을 작성하는 이유는 '내가 이렇게 구현했으니 참고하세요!' 보다는 '다른 사람들은 이런 상황에서 어떻게 구현할까?' 가 궁금해서에요. 제가 어떠한 이유로 이렇게 구현했는지 설명해 드리고, 여러분이라면 어떤 식으로 구현할지 의견을 들어보고 싶습니다. 제가 의도한 대로 구현하긴 했지만, 이게 최선이라는 확신도 들지 않고 다른 사람이라면 어떻게 이 문제를 풀었을까? 라는 궁금증이 풀리질 않아서 이 글을 쓰게 되었네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;시작하기에 앞서, 2가지의 양해드리는 부분이 있습니다. 첫 번째로는 &lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;글을 작성하기에 제 프로젝트의 코드들은 이미 구현이 끝난 코드들이라, 글을 작성하면서 코드를 다시 작성했습니다. 정말 간단한 예시 코드의 사진이 들어가는 경우도 있어요. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;두 번째는 각 전략에 대한 자세한 개념설명은 이미 다른 블로그에 너무 잘 되어 있으니, 구현했던 것 위주로 설명하겠습니다. (아마 다른 글도 읽어보셨다면 제가 실제 사용해본 위주로 글을 작성한다는 것을 아실 거예요!) 이 점 양해해 주시면 감사하겠습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그럼 시작해 보겠습니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 너무 다른 2종류의 회원?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그래서, 제가 제목에 언급한 너무 다른 2종류의 회원이 뭘까요? 바로 '병원' 과 '회원'입니다. 둘이 가지고 있는 칼럼이 너무나도 달랐기에 이러한 고민이 시작되었어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저, 보통 다른 역할의 회원을 보통 이런식으로 구현하실 거라고 생각합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1c0Ii/btsrErK3v0a/uNMYCkmg5MDMkM1c367UT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1c0Ii/btsrErK3v0a/uNMYCkmg5MDMkM1c367UT0/img.png&quot; data-alt=&quot;이해를 위해 아래에서 사용하는 BaseEntity에 대한 정보도 넣어둘게요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1c0Ii/btsrErK3v0a/uNMYCkmg5MDMkM1c367UT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1c0Ii%2FbtsrErK3v0a%2FuNMYCkmg5MDMkM1c367UT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;428&quot; height=&quot;318&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이해를 위해 아래에서 사용하는 BaseEntity에 대한 정보도 넣어둘게요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duf5Mm/btsrUpktUmg/c7GAFkrCLJhGzO5IzgQFqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duf5Mm/btsrUpktUmg/c7GAFkrCLJhGzO5IzgQFqk/img.png&quot; data-alt=&quot;enum을 많이들 쓰시죠?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duf5Mm/btsrUpktUmg/c7GAFkrCLJhGzO5IzgQFqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fduf5Mm%2FbtsrUpktUmg%2Fc7GAFkrCLJhGzO5IzgQFqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;292&quot; height=&quot;461&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;enum을 많이들 쓰시죠?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통의 다른 '역할' 정도라면 이런 식으로 Role을 부여해서 사용하고, Security에도 이를 바탕으로 권한을 부여해서 사용해요. 그런데, 문제는 제가 구현해야 하는 '병원' 과 '회원' 이 너무 다른 칼럼을 가지고 있다는 것이었어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MpnBN/btsrH4WdcAf/D2nldqVjbWKwp3CQBK3FG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MpnBN/btsrH4WdcAf/D2nldqVjbWKwp3CQBK3FG1/img.png&quot; data-alt=&quot;ERD 초안&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MpnBN/btsrH4WdcAf/D2nldqVjbWKwp3CQBK3FG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMpnBN%2FbtsrH4WdcAf%2FD2nldqVjbWKwp3CQBK3FG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1063&quot; height=&quot;302&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ERD 초안&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 프로젝트 설계를 하면서 만들었던 회원 초안인데요, 서로 다른 필드들이 많이 보여요. 거기에 더불어서 병원은 의사, 운영 시간, 상담... 등등 여러 테이블과 연관관계를 맺어야 했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 구현, 관리를 쉽게 하기 위해 Member라는 Entity에 모든 Column을 넣어서 관리할 경우에 생기는 불만은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. '회원' '병원' 에 따라서 &lt;span style=&quot;color: #000000;&quot;&gt;null&lt;/span&gt;값을 가지는 Column이 너무 많이 생긴다 (설계 자체를 잘 못한 거라고 생각함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 실제 코드로 작성할 시 '병원', '회원'과 연관관계를 맺는 필드들도 작성해야 할 텐데, 결국 구현하면서 너무 난잡해질 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 데이터의 불안정함, 일관성, 복잡성, 확장성... 등등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 생각해도 너무 많은 불만이 떠올랐어요. 그래서 이를 추상화를 통해 해결해보자 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 사용해본 전략들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 원하는 구현의 형태는 공통된 정보들을 관리하는 &lt;b&gt;부모 클래스&lt;/b&gt;를 하나 만들어서 이에 BaseEntity와 Spring Security의&amp;nbsp; UserDetails를 상속시키고, 이를 각각의 &lt;b&gt;회원&lt;/b&gt;과 &lt;b&gt;병원&lt;/b&gt;이 상속받는 형태였습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-1. SingleTable 전략&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, JPA에서 제공하는 연관관계 전략 중 SingleTable 전략을 사용해 보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfguFR/btsrH6foRmH/CcbjNSorKWNXA6LVmlmP51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfguFR/btsrH6foRmH/CcbjNSorKWNXA6LVmlmP51/img.png&quot; data-alt=&quot;공통 된 정보를 담고있는 부모&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfguFR/btsrH6foRmH/CcbjNSorKWNXA6LVmlmP51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfguFR%2FbtsrH6foRmH%2FCcbjNSorKWNXA6LVmlmP51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;390&quot; height=&quot;490&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공통 된 정보를 담고있는 부모&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ohNIa/btsrRSnr4rT/lQw6DqKLFixUtUqFZKCogK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ohNIa/btsrRSnr4rT/lQw6DqKLFixUtUqFZKCogK/img.png&quot; data-alt=&quot;각각의 자식들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ohNIa/btsrRSnr4rT/lQw6DqKLFixUtUqFZKCogK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FohNIa%2FbtsrRSnr4rT%2FlQw6DqKLFixUtUqFZKCogK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;577&quot; height=&quot;153&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;각각의 자식들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구현하고, 실행해 보면 제가 원하는 형태의 엔티티가 생성되었을까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;173&quot; data-origin-height=&quot;189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r82C5/btsrCuIqaox/WVM6uyDRFwkCskEypcvz0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r82C5/btsrCuIqaox/WVM6uyDRFwkCskEypcvz0K/img.png&quot; data-alt=&quot;염려하던 형태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r82C5/btsrCuIqaox/WVM6uyDRFwkCskEypcvz0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr82C5%2FbtsrCuIqaox%2FWVM6uyDRFwkCskEypcvz0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;173&quot; height=&quot;189&quot; data-origin-width=&quot;173&quot; data-origin-height=&quot;189&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;염려하던 형태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 글의 처음에 우려하던 형태의 entity 하나만 만들어졌습니다. 짧게 SingleTable 전략에 대해서 설명해 보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 전략을 사용하면 부모 클래스와 모든 자식 클래스의 속성이 하나의 테이블에 모두 저장돼요. 테이블에는 구분자 컬럼이 추가되어 각 레코드가 어떤 자식 클래스에 속하는지를 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 위에서 작성한 코드와 같이 생각해보면, 부모의 entity에 자식의 속성들이 모두 포함돼서 생성됐죠? 저는 이것과 더불어 별도의 엔티티를 원했고 글 처음에 언급한 null값에 대해서도 안전하지 못하기 때문에 이 전략은 실패입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-2. Joined 전략&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 Joined 전략을 사용해 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/41CxM/btsrDF3RFaq/dK4L74KRTyAB1cArG7LhKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/41CxM/btsrDF3RFaq/dK4L74KRTyAB1cArG7LhKK/img.png&quot; data-alt=&quot;JOINED&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/41CxM/btsrDF3RFaq/dK4L74KRTyAB1cArG7LhKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F41CxM%2FbtsrDF3RFaq%2FdK4L74KRTyAB1cArG7LhKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;373&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;JOINED&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 strategy만 바꿔주었습니다.이렇게 변경하고 실행해 보면?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;170&quot; data-origin-height=&quot;249&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAUOIf/btsrR3I9LDn/yTvjIG66YJc8cVmrKiiob0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAUOIf/btsrR3I9LDn/yTvjIG66YJc8cVmrKiiob0/img.png&quot; data-alt=&quot;흠..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAUOIf/btsrR3I9LDn/yTvjIG66YJc8cVmrKiiob0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAUOIf%2FbtsrR3I9LDn%2FyTvjIG66YJc8cVmrKiiob0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;170&quot; height=&quot;249&quot; data-origin-width=&quot;170&quot; data-origin-height=&quot;249&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;흠..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 전의 SingleTable 전략과 반대로 어느 정도 제가 원하는 형태의 entity들이 만들어졌습니다. 이 전략에 대해 짧게 설명해 보자면, 부모 클래스와 자식 클래스의 별도의 데이터베이스 테이블이 생성되는 방식이에요. 이때, 자식 테이블은 부모 테이블의 pk값을 외래키로 사용하여 연결됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;entity가 실제로 만들어진 것을 보시면 바로 이해하실 수 있겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 썩 괜찮았지만, 마음에 완전히 들지는 않았어요. 왜냐하면, 저는 UserInfo라는 부모 클래스를 별도의 엔티티로 만들고 싶지 않았고, 그저 공통된 사항들을 추상화하여 반복되는 코드들을 단순화하고 유연하게 코드를 작성하고 싶었거든요. 또 부모가 가지고 있는 칼럼들을 조회해 오려면 조인을 사용해야 하는 부분도 썩 마음에 들지는 않았어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-3. 문제의 발견&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법들 외에도 TABLE_PER_CLASS 전략이 존재하지만 제가 원하는 형태의 설계는 이루어지지 않을 것이라고 생각했습니다. 위에서 2가지의 방법을 시도해 보고, 곰곰이 생각해보면서 원인은 결국 부모클래스에 붙어있는 @Entity라고 생각했거든요. 이것을 제거하지 않는 이상 부모클래스의 entity를 생성할 것이었고, 이것은 제가 원하는 형태의 구현이 아니었어요. 어떻게 할지 곰곰히 생각해 보다가, 너무 어이없게도 제가 작성한 코드에서 힌트를 찾았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힌트가 뭐냐면... 바로 BaseEntity입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-4. MappedSuperClass&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지겹도록 BaseEntity라는 것을 사용했으면서 왜 진작에 생각하지 못했을까요? UserInfo를 Entity가 아닌 MappedSuperClass로 선언하면 제가 원하는 형태의 구현이 될 것 같다고 확신했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 부모 클래스의 entity가 생성되지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 공통된 로직과 컬럼을 부모에 넣으므로써 추상화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 테이블 설계가 단순해짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. null값에 대한 문제 해결 (자식들이 다른 자식의 컬럼을 가지지 않음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 확장성 (새로운 종류의 회원이 추가된다면 이를 상속받기만 하면 됨)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;373&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biFyRk/btsrCuO89js/PKCX6iCRIn2YCeIHTdtfu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biFyRk/btsrCuO89js/PKCX6iCRIn2YCeIHTdtfu1/img.png&quot; data-alt=&quot;MappedSuperClass&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biFyRk/btsrCuO89js/PKCX6iCRIn2YCeIHTdtfu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiFyRk%2FbtsrCuO89js%2FPKCX6iCRIn2YCeIHTdtfu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;373&quot; height=&quot;340&quot; data-origin-width=&quot;373&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MappedSuperClass&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;159&quot; data-origin-height=&quot;297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xg98j/btsrUs2AF4H/kk9NrpyxnksJcvtspKxWLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xg98j/btsrUs2AF4H/kk9NrpyxnksJcvtspKxWLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xg98j/btsrUs2AF4H/kk9NrpyxnksJcvtspKxWLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxg98j%2FbtsrUs2AF4H%2Fkk9NrpyxnksJcvtspKxWLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;159&quot; height=&quot;297&quot; data-origin-width=&quot;159&quot; data-origin-height=&quot;297&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 원하는 형태의 entity가 생성되었고, 코드 상으로도 원하는 코드가 구현이 되었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 원하는 형태의 구현을 해서 너무 마음에 들지만, 하나 불안한 점이 있었습니다. 지금까지 구현해 본 것을 나타내보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tIEvQ/btsrDbV8cTw/qxJOz9HOT0P8fwAwgFhTD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tIEvQ/btsrDbV8cTw/qxJOz9HOT0P8fwAwgFhTD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tIEvQ/btsrDbV8cTw/qxJOz9HOT0P8fwAwgFhTD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtIEvQ%2FbtsrDbV8cTw%2FqxJOz9HOT0P8fwAwgFhTD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;743&quot; height=&quot;381&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 형태가 만들어지는데요, 저는 제가 구현했기에 이 코드가 어떻게 동작할지 알 수 있지만, 팀원들이나 다른 사람들이 이 구조를 보면 쉽게 이해를 하지 못할 수도 있겠다는 생각이 들었습니다. 쉽게 말하면, 복잡도가 올라간듯한 느낌이 들었어요. 이 부분에 대해서는 제가 설명을 잘하고, 가독성 있게 코드를 짜야겠다고 생각하게 됐습니다. 그런데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT를 사용한 인증 부분에서도 불만족스러운 부분이 생겼습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqmqaP/btsrIbVqCIf/BYrHLlqp0HylBnWQ89GsK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqmqaP/btsrIbVqCIf/BYrHLlqp0HylBnWQ89GsK1/img.png&quot; data-alt=&quot;실제 프로젝트의 코드에요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqmqaP/btsrIbVqCIf/BYrHLlqp0HylBnWQ89GsK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqmqaP%2FbtsrIbVqCIf%2FBYrHLlqp0HylBnWQ89GsK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;543&quot; height=&quot;185&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 프로젝트의 코드에요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 예시 코드가 아닌 제가 실제 프로젝트에서 작성한 코드인데요, 보시다시피 Spring Security를 위해 UserDetails를 구현하고 있습니다. 문제는요, JWT Token을 바탕으로 유저를 찾을 때에요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dknisx/btsrEz93MLj/IpDW6r6ackwpagklNZXQ5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dknisx/btsrEz93MLj/IpDW6r6ackwpagklNZXQ5K/img.png&quot; data-alt=&quot;2번의 조회쿼리가 발생합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dknisx/btsrEz93MLj/IpDW6r6ackwpagklNZXQ5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdknisx%2FbtsrEz93MLj%2FIpDW6r6ackwpagklNZXQ5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;878&quot; height=&quot;467&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;467&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2번의 조회쿼리가 발생합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserDetailService의 구현체입니다. loadUserByUsername의 내용을 보면요, 우선 회원을 조회해서 찾았다면 업캐스팅 후 반환합니다. 반환하지 못했다면, 병원을 조회해서 찾아서 반환하거나 찾지 못했다면 에러를 던지는 모습이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 병원이 어떠한 요청을 보낼 때마다 JWT Filter에서 병원을 찾기 위해 항상 2번의 조회쿼리가 발생하게 되는 거에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 단순 조회쿼리가 얼마나 성능에 영향을 미칠까? 라고 생각할 수도 있지만 저는 이러한 부분이 마음에 들지 않았습니다. 그래서, 캐싱을 사용해서 cache hit이 발생했다면 이 2번의 조회쿼리를 발생시키지 않는 방법을 생각해 봤어요. 이 부분은 바로 이어서 다음 글에 작성해야 할 것 같습니다. 다음 글은 캐싱을 통한 성능향상이 되겠네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 고민했던 과정을 최대한 글로 녹여보려 했는데, 쉽지 않네요.. 제가 작성한 글이 잘 이해가 되실지 모르겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 @MappedSuperClass를 이용해서 추상화를 했구요, 이렇게 설계하고 구현하면서 만족스럽게 프로젝트를 진행한 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분들은 이런 상황이라면 어떤 식으로 구현하실 것 같으신가요? 제 방법에 대한 조언이나 새로운 방식에 대한 제안과 같은 의견에 대해서 댓글로 남겨주시면 감사하게 듣겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부족한 글 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>SPRING</category>
      <category>Java</category>
      <category>joined</category>
      <category>JPA</category>
      <category>MappedSuperClass</category>
      <category>Security</category>
      <category>Security 설계</category>
      <category>SINGLE TABLE</category>
      <category>spring</category>
      <category>Spring 회원</category>
      <author>s7won</author>
      <guid isPermaLink="true">https://s7won.tistory.com/7</guid>
      <comments>https://s7won.tistory.com/7#entry7comment</comments>
      <pubDate>Mon, 21 Aug 2023 23:23:49 +0900</pubDate>
    </item>
    <item>
      <title>자바의 Record로 DTO를 만들어보자 - 2</title>
      <link>https://s7won.tistory.com/6</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 다시 글을 쓴 이유&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;안녕하세요. 오늘은 Record에 대해서 다시 한번 작성해보려고 합니다. 생각보다 Record를 보려고 찾아주시는 분들이 많더라구요. 그래서 제가 직접 프로젝트에 도입해서 적극적으로 사용하면서 느낀 편리한 점들과 &lt;span style=&quot;color: #ee2323;&quot;&gt;처음 사용하는 분들이 자주 하는 실수&lt;/span&gt;, 또 더불어서 동료들이 Record를 사용하면서 느꼈던 것 들에 대해서 공유해볼까 합니다. 아래의 작성할 코드에 대해서는, 코드 스니펫 가독성이 별로 안좋은 것 같아서 코드들은 사진으로 첨부하겠습니다. &lt;/span&gt;&lt;span&gt;바로 시작해 보겠습니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;2. 개인적인 소감&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;789&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WKamz/btsqPZ9rLEi/0dQR2dfMY6Mzu0M2VeqAvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WKamz/btsqPZ9rLEi/0dQR2dfMY6Mzu0M2VeqAvK/img.png&quot; data-alt=&quot;사용한 record들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WKamz/btsqPZ9rLEi/0dQR2dfMY6Mzu0M2VeqAvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWKamz%2FbtsqPZ9rLEi%2F0dQR2dfMY6Mzu0M2VeqAvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;617&quot; height=&quot;789&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;789&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사용한 record들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 프로젝트를 진행하면서, 제가 적극적으로 영업해서 모든 dto를 record로 구성해 보았습니다. 아직 진행중인 프로젝트인데도, 벌써 46개의 record가 만들어졌네요. 이전 글에서도 '편리해서 자주 사용할 것 같다'라고 했었는데요, 정말 정말 편했습니다 이거보다 좋은 게 있을까요? ㅎㅎ... 그래도 어떤 점이 편했는지 구체적으로 말해볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 사용하면서 느낀 장점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 롬복의 여러 어노테이션으로부터 해방된 것&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KqvGW/btsqT24V424/S6RAyySyahOzf66KbB9RWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KqvGW/btsqT24V424/S6RAyySyahOzf66KbB9RWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KqvGW/btsqT24V424/S6RAyySyahOzf66KbB9RWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKqvGW%2FbtsqT24V424%2FS6RAyySyahOzf66KbB9RWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;788&quot; height=&quot;466&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 작성한 record의 일부분이에요.가독성을 위해 validation은 한 줄만 빼고 제거했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(사용 가능하다는 걸 알려드리기 위해 한줄만 남겼습니다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 간단하지 않나요? 이렇게 컴포넌트(필드)들만 선언하면 끝이에요. 이전에 dto class를 만들 때면, 의무적으로 롬복의 &lt;span style=&quot;background-color: #dddddd; color: #ee2323;&quot;&gt;@Allargs @Noargs @Builder @Getter&lt;/span&gt;... 들을 선언하고 이후에 사용하지 않는 어노테이션들을 제거하느라 고생한적이 많았는데요, 그럴 필요가 없다는 점이 너무 좋았습니다. 또한, 그러한 부분을 신경 쓰지 않아도 되니 코드 작성하는 시간도 단축되겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 객체의 명확성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체의 구조와 목적이 명확하게 드러난다는 점이 좋았습니다. 컴포넌트(필드) 만 보고도 어떤 정보를 담고 있는지를 파악할 수 있다는 것이 좋았어요. 또한, record가 다양하게 활용될 수 있지만 이번 프로젝트의 경우 주로 dto로 활용되고 있기 때문에, '데이터 전송에 사용되는 객체'라고 명확하게 인식되는 게 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 조금 논란이 있을수도 있는 부분인데, 생성자를 만드는 부분이 좋았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ny8ds/btsqQ9D0KWS/D1JfVU8Kv2ZookvGtCxK6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ny8ds/btsqQ9D0KWS/D1JfVU8Kv2ZookvGtCxK6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ny8ds/btsqQ9D0KWS/D1JfVU8Kv2ZookvGtCxK6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fny8ds%2FbtsqQ9D0KWS%2FD1JfVU8Kv2ZookvGtCxK6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;454&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 저는 entity &amp;lt;&amp;gt; dto 간의 변환을 할 때, 정적 팩토리 메소드를 주로 이용했는데, 이번에는 생성자를 주로 이용했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 모든 컴포넌트를 가지는 생성자가 암시적으로 생성되기 때문에 작성하기가 용이한 점도 있었고요, 또 객체를 '생성' 하는 책임을 가진 함수로 적절한 게 '생성자' 만한 게 없다고 생각하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 프로젝트를 진행하면서 단일 책임 원칙과 적절한 네이밍으로 가독성 있는 코드를 작성하기 위해 노력했는데요, 정적 팩토리 메소드도 매우 매우 훌륭하지만 객체를 생성하는 것을 가장 잘 드러내는 것은 생성자라고 생각합니다. 실제로도 코드가 더 한눈에 잘 들어온다고 느꼈어요. (지극히 주관적인 관점입니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 주의할 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record를 사용하면서, 익숙하지 않은 동료분들이 하는 여러 실수를 보았는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는, record는 class의 '생김새' 보다 method의 '생김새'를 취하고 있어요. 무슨 말이냐면요,&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KqvGW/btsqT24V424/S6RAyySyahOzf66KbB9RWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KqvGW/btsqT24V424/S6RAyySyahOzf66KbB9RWK/img.png&quot; data-alt=&quot;사실은, 메소드의 생김새입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KqvGW/btsqT24V424/S6RAyySyahOzf66KbB9RWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKqvGW%2FbtsqT24V424%2FS6RAyySyahOzf66KbB9RWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;788&quot; height=&quot;466&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사실은, 메소드의 생김새입니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 보여드린 사진인데요, 컴포넌트(필드) 들이 () 안에 들어가있어요. 컴포넌트들이 많기 때문에 가독성을 위해 이런 식으로 작성했는데, 사실은&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;413&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D7rHE/btsqJT3fvuW/Ls4Wa8nTeEblIkYNp9UoC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D7rHE/btsqJT3fvuW/Ls4Wa8nTeEblIkYNp9UoC1/img.png&quot; data-alt=&quot;메소드의 생김새, 하지만 메소드는 아니에요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D7rHE/btsqJT3fvuW/Ls4Wa8nTeEblIkYNp9UoC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD7rHE%2FbtsqJT3fvuW%2FLs4Wa8nTeEblIkYNp9UoC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;413&quot; height=&quot;99&quot; data-origin-width=&quot;413&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;메소드의 생김새, 하지만 메소드는 아니에요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 형태로 구성되어 있거든요. 메소드의 생김새랑 비슷하죠? 이 부분에서 헷갈리셔서, {} 사이에 컴포넌트를 작성하시는 경우도 많더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는요 모든 컴포넌트(필드) 들은 final로 선언된다는 거예요. 이것에 대해 학습하고 코드를 작성하더라도 막상 코드를 작성하다 보면 이런 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s6uRz/btsqS8dhwmJ/TLxsbGADNceyReuhCaF77k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s6uRz/btsqS8dhwmJ/TLxsbGADNceyReuhCaF77k/img.png&quot; data-alt=&quot;객체가 생성되면 불변이에요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s6uRz/btsqS8dhwmJ/TLxsbGADNceyReuhCaF77k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs6uRz%2FbtsqS8dhwmJ%2FTLxsbGADNceyReuhCaF77k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;493&quot; height=&quot;261&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;객체가 생성되면 불변이에요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 final로 선언된다는 걸 유의하시고 코드를 유연하게 작성하시면 되겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상으로 실제로 사용하면서 느낀 점을 작성해 보았습니다. 막상 글을 작성하니 정말 별거 없네요.. 그래도 이 글을 작성하는 이유는 한 번씩 꼭 써보셨으면 하는 마음에서 입니다. 저 혼자 이렇게 편한 걸 사용하려니 너무 아쉽더라고요 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트를 같이 진행한 동료들에게도 record를 사용해 보니 어떠냐고 여쭤보았는데요, 제가 느낀 장점이랑 같은 말을 하셨습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 간결해서 좋다(lombok 관련)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. dto라는 이름을 붙이지 않아도 명확한 객체라서 좋았다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 가독성이 좋다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 의견을 다들 주셨습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java17을 사용하신다면, 꼭 한 번쯤 사용해 보셨으면 좋겠네요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 글 작성이라 너무 어색한데요, 앞으로는 종종 작성하도록 노력해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽어주셔서 감사합니다!&lt;/p&gt;</description>
      <category>JAVA</category>
      <category>DTO</category>
      <category>Java</category>
      <category>java 17</category>
      <category>record</category>
      <category>Record Dto</category>
      <category>레코드</category>
      <category>자바</category>
      <author>s7won</author>
      <guid isPermaLink="true">https://s7won.tistory.com/6</guid>
      <comments>https://s7won.tistory.com/6#entry6comment</comments>
      <pubDate>Thu, 10 Aug 2023 23:01:24 +0900</pubDate>
    </item>
    <item>
      <title>Stream API의 map에 대해서 코드로 이해해보자</title>
      <link>https://s7won.tistory.com/5</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요. 오늘은 Java의 Stream API 메소드 중에서, map에 대해서 알아보겠습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;0. 추천 독자&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 글은 특히 Stream API에 대해서 어느정도는 알고 있지만, 활용이 쉽지 않으신 분 혹은 Stream에 대해서 사용하면서 익히고 싶은 분이 읽으시면 특히 좋을 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 학습목표&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;당연하게도 Stream API의 map에 대해서 공부하는게 목적입니다. 그런데 왜 하필 map이냐고요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;왜냐하면, 사실 제가 Stream 사용에 있어서 가장 이해가 안 되고 어려웠던 부분이 map이었습니다. map을 잘못 이해하고 사용하려다 보니 저의 Stream을 활용한 코드는 빨간 줄이 항상 가득했었어요. 그래서 많이 쓰는 Stream 코드를 아예 외워서 사용했었습니다. 이대로는 안 되겠다 싶어서 map에 대해서 쉽게 이해하려고 노력했고, 제가 이해한 부분을 저와 같은 고민을 하시는 분을 위해 공유하고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;map에 대해서 이해한 후에는, Stream을 사용하는 게 무척 편해지고 쉬워졌습니다. 사실 map 이후에 여러 작업들은 응용력도 물론 중요하지만, 메소드를 외워서 사용할 수 있는 부분이 많아서 그런 것 같아요. 그래서 저는 map을 자유롭게 사용할 수 있다면 Stream에 대해서 50% 이상 이해한 것이라고 감히 생각합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;이제부터 map에 대해서 설명해 볼 텐데요, Stream의 특징, 최종연산... 이런 부분에 대해서는 작성하지 않거나 설명하지 않고 넘어갈 계획입니다. 다른 게시글이나 블로그에 너무나도 잘 정리된 자료가 많기 때문이에요. 코드를 보면서 이해해 보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;그럼 이제부터 시작해 보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;2. map&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;그래서 map이 뭘까요? 공식문서의 map 메소드에 대한 설명은 아래와 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Returns a stream consisting of the results of applying the given function to the elements of this stream.&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;간단하게 직역해 보면 '이 스트림의 요소에 주어진 함수를 적용한 결과로 구성된 스트림을 반환합니다.'로 번역할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;제가 map 메소드에 대해서 헷갈렸던 이유는, 위의 stream에 대한 설명이 무엇인가 와닿지 않고 어렵게 느껴졌습니다. 거기에 더불어서 map이라는 용어가 프로그래밍에서 mapping의 의미로 많이 쓰이는데요, 이러한 추상적인 용어들이 뒤죽박죽 섞여서 제대로 이해하지 못하고 외워서 썼던 것 같아요. &lt;s&gt;(결국엔 공부를 대충 했다는 이야기입니다.)&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그래서 저는 이런 방식으로 생각했어요. '&lt;b&gt;map은 Stream의 요소들을 내가 사용할 형태로 바꾸거나, 사용할 요소를 뽑아내는 것'라고&lt;/b&gt; 생각했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코드를 보면서 같이 생각해 볼게요.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1 2 3 4 5 6 7 8 9 10&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;알고리즘에 많이 등장할 법한 input인데요, 이러한 input을 받아서 int array로 변환하는 stream을 사용해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1683808394614&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        // 많이 사용하는 방식
        int[] arrayWithStringTokenizer = new int[10];

        for (int i = 0; i &amp;lt; 10; i++) {
            arrayWithStringTokenizer[i] = Integer.parseInt(st.nextToken());
        }
        // stream으로 배열 만들기
        int[] arrayWithStreamAPI = Stream.of(br.readLine()
                                               .split(&quot; &quot;))
                                         .mapToInt(Integer::parseInt)
                                         .toArray();

        System.out.println(Arrays.toString(arrayWithStringTokenizer));
        System.out.println(Arrays.toString(arrayWithStreamAPI));
    }
}
=================================================================
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;JAVA를 사용해서 알고리즘 문제를 푸시는 분이라면 첫 번째 방식은 많이 익숙하시죠? 어떤 작업을 Stream으로 표현할 건지 쉽게 설명하려고 같은 동작을 하는 코드를 넣어봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 Stream에 대해서 차근차근 설명해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저, Stream.of를 통해서 Stream을 생성했어요. BufferedReader의 readLine으로 1줄을 String으로 입력받아서, &quot; &quot;를 기준으로 쪼개서 만든 배열을 &lt;b&gt;요소&lt;/b&gt;로 사용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&quot;1 2 3 4 5 6 7 8 9 10&quot;로 입력받아서,&amp;nbsp; {&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;7&quot;, &quot;8&quot;, &quot;9&quot;, &quot;10&quot;}로 만든 것을 요소로 사용함!&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, String array로 Stream을 생성했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그 뒤에, mapToInt(Integer::parseInt)를 사용했어요. 이 말이 무엇이냐면, 위에서의 &quot;1&quot;부터 &quot;10&quot;까지의 String array의 요소 하나하나를 Integer.parseInt를 적용해서 int로 바꾼 거예요. 풀어서 써보자면, 모든 요소에 대해서&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Interger.parseInt(&quot;1&quot;), Integer.paresInt(&quot;2&quot;).... Integer.parseInt(&quot;10&quot;)&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;의 작업을 해준 거예요. 위와 같은 작업을 통해서 'String'을 'int'로 바꿔 주었어요. 즉 내가 사용할 형태로 바꾼 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 사진을 보시면, mapToInt 전까지 Stream&amp;lt;String&amp;gt; 형태이고, mapToInt를 통해서 IntStream으로 바뀐 것을 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;111&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAZGWk/btseRgVOPyw/vasgbJurC8hchROiheBdW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAZGWk/btseRgVOPyw/vasgbJurC8hchROiheBdW1/img.png&quot; data-alt=&quot;IDE를 잘 활용하자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAZGWk/btseRgVOPyw/vasgbJurC8hchROiheBdW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAZGWk%2FbtseRgVOPyw%2FvasgbJurC8hchROiheBdW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;531&quot; height=&quot;111&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;111&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IDE를 잘 활용하자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;한 번으론 아쉬우니까, 간단한 예시를 한번 더 들어보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1683809807539&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.List;

public class Main {

    static class Product {
        String name;
        int price;

        public Product(String name, int price) {
            this.name = name;
            this.price = price;
        }

        public String getName() {
            return name;
        }
    }

    public static void main(String[] args) {

        Product product1 = new Product(&quot;하나&quot;, 1);
        Product product2 = new Product(&quot;둘&quot;, 2);
        Product product3 = new Product(&quot;셋&quot;, 3);

        List&amp;lt;Product&amp;gt; productList = List.of(product1, product2, product3);

        List&amp;lt;String&amp;gt; productNameList = productList.stream()
                                                  .map(Product::getName)
                                                  .toList();

        System.out.println(productNameList.toString());
    }
}
=========================================
[하나, 둘, 셋]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;급하게 간단한 코드를 만들어 보았습니다. 상품의 이름만 뽑아내서 List로 반환하는 코드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;map(Product::getName)을 통해서 이름을 뽑아내고, toList()로 반환했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;결국 map을 쉽게 이해하는 핵심은, 원하는 요소로 뽑아낸다고 생각하면 될 것 같아요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 정도면 map을 사용하는 데에 있어서 어려움은 크게 없다고 생각하는데... Stream의 map을 사용할 때마다 의문인 점이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TyZio/btseR9vaUFa/wNLyg9kHOQihIaZSJu0UX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TyZio/btseR9vaUFa/wNLyg9kHOQihIaZSJu0UX0/img.png&quot; data-alt=&quot;map의 파생상품들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TyZio/btseR9vaUFa/wNLyg9kHOQihIaZSJu0UX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTyZio%2FbtseR9vaUFa%2FwNLyg9kHOQihIaZSJu0UX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;380&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;map의 파생상품들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;바로 수많은 map의 파생상품들이에요. 얘내는 왜 존재하는 걸까요? 이거에 대해서도 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. map의 파생상품들&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 내용을 보면서, '아니 그럼, map만 있으면 되는 거 아닌가? mapToInt는 뭐지?'라는 생각을 하셨을 수도 있을 것 같습니다. 어느 정도는 맞는 말이에요. map 메서드로도 대부분의 작업을 수행할 수 있지만, mapToInt가 존재하는 이유는 '성능의 향상' 때문입니다. 여러 메서드가 있지만, 대표적인 mapToInt에 대해서 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3-1. mapToInt&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;mapToInt는 정수형(int) 매핑에 특화되어 있습니다. 그리고 이것은 성능의 향상 때문에 존재한다고 말씀드렸는데요, 왜 그런지 이유를 설명하기 전에 코드부터 보고 가겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1683812068685&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Main {

    public static void main(String[] args) {
            List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

            for (int i = 0; i &amp;lt; 10000000; i++) {
                list.add(i);
            }

            long startTime = System.currentTimeMillis();
            list.stream()
                .map(x -&amp;gt; x * x)
                .forEach(x -&amp;gt; {
                });
            long endTime = System.currentTimeMillis();
            System.out.println(&quot;map 소요시간 : &quot; + (endTime - startTime) + &quot; ms&quot;);

            startTime = System.currentTimeMillis();
            list.stream()
                .mapToInt(x -&amp;gt; x * x)
                .forEach(x -&amp;gt; {
                });
            endTime = System.currentTimeMillis();
            System.out.println(&quot;mapToInt 소요시간 : &quot; + (endTime - startTime) + &quot; ms&quot;);
    }
}
==================================================
map 소요시간 : 117 ms
mapToInt 소요시간 : 29 ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;동일한 작업을 하는 코드이고, 단지 map과 mapToInt의 차이일 뿐인데 속도 차이가 상당합니다. 왜냐하면 자바에서 원시타입(primitive type)과 참조타입(reference type(object))을 다루는 방식 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;map 메서드는 Stream의 요소를 뽑아내거나 변환한 뒤, ' Stream &amp;lt;T&amp;gt;' 형태로 반환합니다. 즉, 각각의 요소를 Integer로 boxing 하고 있어요. Integer는 객체이기 때문에 메모리 할당과 Garbage Collection에 상당한 시간을 소비하게 됩니다. 또한, 당연히 메모리 소비량도 높아지겠죠? (&lt;a href=&quot;https://s7won.tistory.com/4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://s7won.tistory.com/4&lt;/a&gt; 를 참조하시면 이해에 도움이 될 수도 있어요!)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;반면, mapToInt 메소드는 원시타입인 'int' 값을 반환하는 'IntStream'의 형태로 반환해요. 이는 앞에서의 Integer(객체) 생성과 관련된 오버헤드를 제거하므로 성능이 향상되는 거예요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, 원시타입을 다루는 mapToInt 메소드가 래퍼 클래스(wrapper class)를 다루는 map보다 더 효율적으로 요소를 처리하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;mapToInt 뿐만 아니라, mapToLong, mapToDouble 모두 원시타입을 다루기 위해 존재하는 것이에요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;mapToInt가 int를 다루는 Stream을 반환한다는 것을 알았으니, 위에서의 int array를 반환하는 코드가 List를 반환하게 해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1683812977847&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        // stream으로 List 만들기
        List&amp;lt;Integer&amp;gt; listWithStreamMapToInt = Stream.of(br.readLine()
                                                      .split(&quot; &quot;))
                                                .mapToInt(Integer::parseInt)
                                                .boxed() // 컬렉션을 만들기 위해 boxing 해주기
                                                .toList();

        List&amp;lt;Integer&amp;gt; listWithStreamMap = Stream.of(br.readLine()
                                                      .split(&quot; &quot;))
                                                .map(Integer::parseInt)
                                                .toList(); // map은 객체(여기서는 Integer)를 다루므로 바로 컬렉션으로 반환하기

        System.out.println(listWithStreamMapToInt.toString());
        System.out.println(listWithStreamMap.toString());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;첫 번째 Stream은 mapToInt를 사용하므로 원시타입인 int를 반환할 것이고, 이는 컬렉션 프레임워크의 제네릭 타입 인자로 직접 사용이 불가능하므로 boxed()를 통해서 Integer로 boxing 해주고, toList()를 통해 list로 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;두 번째 Stream은 map을 사용하므로 Stream&amp;lt;Integer&amp;gt;를 반환할 것이고, 이는 제네릭 타입 인자로 사용할 수 있으므로 바로 toList()로 list로 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rSgFi/btseQdyKX0L/Yl4ixBCaGndMnLlkRrO7p1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rSgFi/btseQdyKX0L/Yl4ixBCaGndMnLlkRrO7p1/img.png&quot; data-alt=&quot;어떤 스트림으로 생성되고 반환되는지 확인하세요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rSgFi/btseQdyKX0L/Yl4ixBCaGndMnLlkRrO7p1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrSgFi%2FbtseQdyKX0L%2FYl4ixBCaGndMnLlkRrO7p1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;305&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;어떤 스트림으로 생성되고 반환되는지 확인하세요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJsCRg/btseVN5Z8Er/jmSrsqB4nE01oGtaxEDLr0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJsCRg/btseVN5Z8Er/jmSrsqB4nE01oGtaxEDLr0/img.gif&quot; data-alt=&quot;Primitives vs Wrapper&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJsCRg/btseVN5Z8Er/jmSrsqB4nE01oGtaxEDLr0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bJsCRg/btseVN5Z8Er/jmSrsqB4nE01oGtaxEDLr0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;365&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;609&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Primitives vs Wrapper&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;추가로, 원시타입과 래퍼클래스의 실행 시간을 비교한 표입니다. 혹시 Stream으로 double을 다룰 일이 있으면 mapToDouble을 사용하셔야 할 것 같네요.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3-2. mapToObj&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;앞에서의 mapToInt와 그 친구들을 이해하셨다면, 이제 mapToObj는 이름만 들어도 유추하실 수 있을 것 같습니다. 앞에서의 역할과 반대로, 원시 타입 스트림을 참조(객체) 타입 스트림으로 변경합니다. 바로 코드로 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1683813731665&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int[] array = new int[10];
        for (int i = 0; i &amp;lt; 10; i++) {
            array[i] = i;
        }

        String joining = Arrays.stream(array)
                               .mapToObj(String::valueOf)
                               .collect(Collectors.joining(&quot;-&quot;));

        String joining2 = Arrays.stream(array)
                                .boxed()
                                .map(String::valueOf)
                                .collect(Collectors.joining(&quot;-&quot;));

        System.out.println(joining);
        System.out.println(joining2);
    }
}
=================================================
0-1-2-3-4-5-6-7-8-9
0-1-2-3-4-5-6-7-8-9&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;첫 번째 joining은 mapToObj로 원시 타입을 객체 타입 스트림으로 변환했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;두 번째 joining2는 map으로 변환하려고 하였으나, IntStream은 원시타입 스트림이므로 컴파일 에러가 발생해요. boxed()로 Stream&amp;lt;Integer&amp;gt;로 변환한 뒤, 작업을 수행했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y4vNc/btseRXV5tgW/3wBiKIwsDmgMdFSezogAk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y4vNc/btseRXV5tgW/3wBiKIwsDmgMdFSezogAk0/img.png&quot; data-alt=&quot;어떤 stream으로 생성되고 반환되는지를 확인하세요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y4vNc/btseRXV5tgW/3wBiKIwsDmgMdFSezogAk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy4vNc%2FbtseRXV5tgW%2F3wBiKIwsDmgMdFSezogAk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;222&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;어떤 stream으로 생성되고 반환되는지를 확인하세요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3-3 flatMap&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막으로 flatMap입니다. flatMap은 중첩된 스트림 구조를 펼쳐서 평면적인 구조로 만들어줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;라고 정의가 되어있지만... 솔직히 와닿지 않고 어렵습니다. 실제로도 자유자재로 사용하기가 어려운 것 같아요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;그러니 코드를 보면서 생각해 봅시다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1683815461749&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        List&amp;lt;String&amp;gt; greeting = Arrays.asList(&quot;hi&quot;, &quot;hello&quot;, &quot;bye&quot;);

        greeting.stream()
                .map(String::chars)
                .forEach(System.out::println);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;List의 각각의 단어들을 하나하나씩 쪼개서(hihellobye) 출력하고 싶어서 위와 같이 코드를 작성해 보았습니다. 그러기 위해서, character로 쪼개고 각각을 출력했어요. 제가 이걸 어떻게 할 수 있을지 생각해서 예전에 작성해 본 코드입니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아마 Stream에 익숙하신 분들은 절대 코드를 이렇게 작성하지 않으시겠지만, 그때의 저에게는 이게 한계였어요. 과연 이 코드는 어떤 결과를 출력할까요?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;java.util.stream.IntPipeline$Head@448139f0 &lt;br /&gt;java.util.stream.IntPipeline$Head@7cca494b &lt;br /&gt;java.util.stream.IntPipeline$Head@7ba4f24f&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;예상한 결과가 아닌, 무서운 결괏값이 나왔습니다. 왜 이런 결과가 나왔을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AoLaw/btseQj6Fkqj/1BmdVeqZHL69B2V2118CGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AoLaw/btseQj6Fkqj/1BmdVeqZHL69B2V2118CGk/img.png&quot; data-alt=&quot;IDE를 잘 확인하자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AoLaw/btseQj6Fkqj/1BmdVeqZHL69B2V2118CGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAoLaw%2FbtseQj6Fkqj%2F1BmdVeqZHL69B2V2118CGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;446&quot; height=&quot;141&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;141&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IDE를 잘 확인하자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;제가 예상한 결과와 다르게 map은 IntStream을 반환하고, forEach를 통해서 IntStream의 내부 구조를 나타내는 객체가 출력되었습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그럼, 처음으로 돌아와서 제가 원하는 결과를 어떻게 도출할 수 있을지 처음부터 생각해 볼게요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;우선, List의 각각의 요소에 접근해서 단어를 쪼개야 합니다. 그 뒤에, 쪼갠 단어들을 하나씩 출력해야 해요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, 배열 자체에 접근하는 스트림과 배열의 각 요소에 대한 스트림을 생성해야 하는 것이죠. 이럴 때 쓸 수 있는 게 flatMap입니다. flatMap을 사용해 볼게요.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1683816046166&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {

    public static void main(String[] args) throws IOException {

        List&amp;lt;String&amp;gt; greeting = Arrays.asList(&quot;hi&quot;, &quot;hello&quot;, &quot;bye&quot;);

        String str = greeting.stream()
                .flatMap(word -&amp;gt; Arrays.stream(word.split(&quot;&quot;)))
                .collect(Collectors.joining(&quot;,&quot;));

        System.out.println(str);
    }
}
=============================
h,i,h,e,l,l,o,b,y,e&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;드디어 원하는 결과가 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 코드에서 flatMap은 각 배열 요소에 대해서 word -&amp;gt; Arrays.stream(word.split(&quot;&quot;) 을 적용합니다. 그 뒤에, 이 String 배열을 가지고 스트림을 생성해요. 그렇게 생성된 스트림들을 평면화해서 하나의 스트림으로 합쳐줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, greeting.stream().flatMap(word -&amp;gt; Arrays.stream(word.split(&quot;&quot;))) 을 통해서 List에 접근하는 스트림과 배열 요소에 접근하는 스트림 두 개가 연결돼서 하나의 평면화된 스트림이 생성되는 거예요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막으로, 한 가지 예시만 더 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1683816427640&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {

    public static void main(String[] args) throws IOException {

        List&amp;lt;List&amp;lt;Integer&amp;gt;&amp;gt; nestedList = Arrays.asList(
          Arrays.asList(1, 2, 3),
          Arrays.asList(4, 5, 6),
          Arrays.asList(7, 8, 9)
        );

        List&amp;lt;Integer&amp;gt; listWithFlatMap = nestedList.stream()
                                                  .flatMap(List::stream)
                                                  .toList();

        System.out.println(listWithFlatMap.toString());
    }
}
=========================================
[1, 2, 3, 4, 5, 6, 7, 8, 9]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nestedList.stream()을 통해서 nestedList에 대한 Stream을 생성했어요. 그 뒤에, flatMap(List::stream)을 통해서 nestedList의 내부의 리스트를 개별 스트림으로 변환한 뒤 모든 요소들을 하나로 합친 단일 스트림으로 반환합니다.(평면화, 평탄화)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 뒤에, list로 반환하고 있어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cV4DyI/btseQtux1xw/Mo9QCHMHC5gBKCX3Lfg1P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cV4DyI/btseQtux1xw/Mo9QCHMHC5gBKCX3Lfg1P0/img.png&quot; data-alt=&quot;어떤 stream으로 생성되고 반환되는지 확인하세요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cV4DyI/btseQtux1xw/Mo9QCHMHC5gBKCX3Lfg1P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcV4DyI%2FbtseQtux1xw%2FMo9QCHMHC5gBKCX3Lfg1P0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;99&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;어떤 stream으로 생성되고 반환되는지 확인하세요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 든 2가지의 예시가 가장 대표적인 사용법입니다. flatMap은 이렇게 강력한 기능을 제공하지만 아무래도 많은 학습이 있지 않으면 IDE의 도움을 받아도 쉽게 사용은 어려운 것 같아요. 능숙하게 사용할 수 있도록 공부해서 다음에 flatMap에 대해서도 글을 작성해 보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. 마무리&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 Stream의 map에 대해서 알아보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 글을 가벼운 마음으로 작성하기 시작했는데요, 작성하다 보니 글이 너무 길어졌습니다. JAVA의 기본 내용인 원시타입, 참조타입, 래퍼클래스..부터 람다와 메서드참조, 객체 생성... 등등 많은 내용이 섞여서 글이 어렵게 느껴질 것 같아 걱정입니다. 다른 내용은 최소로 작성하고 오로지 map에 대해서만 작성하려 했는데 욕심이 나서 이런저런 내용이 추가되었어요. 글이 길어졌지만, 너그러운 마음으로 이해해 주시고 모쪼록 이 글이 여러분에게 도움이 되었으면 좋겠습니다.&lt;/p&gt;</description>
      <category>JAVA</category>
      <category>flatMap</category>
      <category>Java</category>
      <category>map</category>
      <category>mapToInt</category>
      <category>maptoobj</category>
      <category>Stream</category>
      <category>Stream API</category>
      <category>STREAM MAP</category>
      <author>s7won</author>
      <guid isPermaLink="true">https://s7won.tistory.com/5</guid>
      <comments>https://s7won.tistory.com/5#entry5comment</comments>
      <pubDate>Fri, 12 May 2023 00:01:18 +0900</pubDate>
    </item>
  </channel>
</rss>